1use crate::test_both_dbs;
2
3use super::*;
4use chrono::Utc;
5use collab::db::RoomId;
6use collab::db::*;
7use pretty_assertions::assert_eq;
8use rpc::ConnectionId;
9use std::sync::Arc;
10
11test_both_dbs!(
12 test_get_users,
13 test_get_users_by_ids_postgres,
14 test_get_users_by_ids_sqlite
15);
16
17async fn test_get_users(db: &Arc<Database>) {
18 let mut user_ids = Vec::new();
19 for i in 1..=4 {
20 let user = db
21 .create_user(
22 &format!("user{i}@example.com"),
23 None,
24 false,
25 NewUserParams {
26 github_login: format!("user{i}"),
27 github_user_id: i,
28 },
29 )
30 .await
31 .unwrap();
32 user_ids.push(user.user_id);
33 }
34
35 assert_eq!(
36 db.get_users_by_ids(user_ids.clone())
37 .await
38 .unwrap()
39 .into_iter()
40 .map(|user| (
41 user.id,
42 user.github_login,
43 user.github_user_id,
44 user.email_address
45 ))
46 .collect::<Vec<_>>(),
47 vec![
48 (
49 user_ids[0],
50 "user1".to_string(),
51 1,
52 Some("user1@example.com".to_string()),
53 ),
54 (
55 user_ids[1],
56 "user2".to_string(),
57 2,
58 Some("user2@example.com".to_string()),
59 ),
60 (
61 user_ids[2],
62 "user3".to_string(),
63 3,
64 Some("user3@example.com".to_string()),
65 ),
66 (
67 user_ids[3],
68 "user4".to_string(),
69 4,
70 Some("user4@example.com".to_string()),
71 )
72 ]
73 );
74}
75
76test_both_dbs!(
77 test_update_or_create_user_by_github_account,
78 test_update_or_create_user_by_github_account_postgres,
79 test_update_or_create_user_by_github_account_sqlite
80);
81
82async fn test_update_or_create_user_by_github_account(db: &Arc<Database>) {
83 db.create_user(
84 "user1@example.com",
85 None,
86 false,
87 NewUserParams {
88 github_login: "login1".into(),
89 github_user_id: 101,
90 },
91 )
92 .await
93 .unwrap();
94 let user_id2 = db
95 .create_user(
96 "user2@example.com",
97 None,
98 false,
99 NewUserParams {
100 github_login: "login2".into(),
101 github_user_id: 102,
102 },
103 )
104 .await
105 .unwrap()
106 .user_id;
107
108 let user = db
109 .update_or_create_user_by_github_account(
110 "the-new-login2",
111 102,
112 None,
113 None,
114 Utc::now(),
115 None,
116 )
117 .await
118 .unwrap();
119 assert_eq!(user.id, user_id2);
120 assert_eq!(&user.github_login, "the-new-login2");
121 assert_eq!(user.github_user_id, 102);
122
123 let user = db
124 .update_or_create_user_by_github_account(
125 "login3",
126 103,
127 Some("user3@example.com"),
128 None,
129 Utc::now(),
130 None,
131 )
132 .await
133 .unwrap();
134 assert_eq!(&user.github_login, "login3");
135 assert_eq!(user.github_user_id, 103);
136 assert_eq!(user.email_address, Some("user3@example.com".into()));
137}
138
139test_both_dbs!(
140 test_add_contacts,
141 test_add_contacts_postgres,
142 test_add_contacts_sqlite
143);
144
145async fn test_add_contacts(db: &Arc<Database>) {
146 let mut user_ids = Vec::new();
147 for i in 0..3 {
148 user_ids.push(
149 db.create_user(
150 &format!("user{i}@example.com"),
151 None,
152 false,
153 NewUserParams {
154 github_login: format!("user{i}"),
155 github_user_id: i,
156 },
157 )
158 .await
159 .unwrap()
160 .user_id,
161 );
162 }
163
164 let user_1 = user_ids[0];
165 let user_2 = user_ids[1];
166 let user_3 = user_ids[2];
167
168 // User starts with no contacts
169 assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
170
171 // User requests a contact. Both users see the pending request.
172 db.send_contact_request(user_1, user_2).await.unwrap();
173 assert!(!db.has_contact(user_1, user_2).await.unwrap());
174 assert!(!db.has_contact(user_2, user_1).await.unwrap());
175 assert_eq!(
176 db.get_contacts(user_1).await.unwrap(),
177 &[Contact::Outgoing { user_id: user_2 }],
178 );
179 assert_eq!(
180 db.get_contacts(user_2).await.unwrap(),
181 &[Contact::Incoming { user_id: user_1 }]
182 );
183
184 // User 2 dismisses the contact request notification without accepting or rejecting.
185 // We shouldn't notify them again.
186 db.dismiss_contact_notification(user_1, user_2)
187 .await
188 .unwrap_err();
189 db.dismiss_contact_notification(user_2, user_1)
190 .await
191 .unwrap();
192 assert_eq!(
193 db.get_contacts(user_2).await.unwrap(),
194 &[Contact::Incoming { user_id: user_1 }]
195 );
196
197 // User can't accept their own contact request
198 db.respond_to_contact_request(user_1, user_2, true)
199 .await
200 .unwrap_err();
201
202 // User accepts a contact request. Both users see the contact.
203 db.respond_to_contact_request(user_2, user_1, true)
204 .await
205 .unwrap();
206 assert_eq!(
207 db.get_contacts(user_1).await.unwrap(),
208 &[Contact::Accepted {
209 user_id: user_2,
210 busy: false,
211 }],
212 );
213 assert!(db.has_contact(user_1, user_2).await.unwrap());
214 assert!(db.has_contact(user_2, user_1).await.unwrap());
215 assert_eq!(
216 db.get_contacts(user_2).await.unwrap(),
217 &[Contact::Accepted {
218 user_id: user_1,
219 busy: false,
220 }]
221 );
222
223 // Users cannot re-request existing contacts.
224 db.send_contact_request(user_1, user_2).await.unwrap_err();
225 db.send_contact_request(user_2, user_1).await.unwrap_err();
226
227 // Users can't dismiss notifications of them accepting other users' requests.
228 db.dismiss_contact_notification(user_2, user_1)
229 .await
230 .unwrap_err();
231 assert_eq!(
232 db.get_contacts(user_1).await.unwrap(),
233 &[Contact::Accepted {
234 user_id: user_2,
235 busy: false,
236 }]
237 );
238
239 // Users can dismiss notifications of other users accepting their requests.
240 db.dismiss_contact_notification(user_1, user_2)
241 .await
242 .unwrap();
243 assert_eq!(
244 db.get_contacts(user_1).await.unwrap(),
245 &[Contact::Accepted {
246 user_id: user_2,
247 busy: false,
248 }]
249 );
250
251 // Users send each other concurrent contact requests and
252 // see that they are immediately accepted.
253 db.send_contact_request(user_1, user_3).await.unwrap();
254 db.send_contact_request(user_3, user_1).await.unwrap();
255 assert_eq!(
256 db.get_contacts(user_1).await.unwrap(),
257 &[
258 Contact::Accepted {
259 user_id: user_2,
260 busy: false,
261 },
262 Contact::Accepted {
263 user_id: user_3,
264 busy: false,
265 }
266 ]
267 );
268 assert_eq!(
269 db.get_contacts(user_3).await.unwrap(),
270 &[Contact::Accepted {
271 user_id: user_1,
272 busy: false,
273 }],
274 );
275
276 // User declines a contact request. Both users see that it is gone.
277 db.send_contact_request(user_2, user_3).await.unwrap();
278 db.respond_to_contact_request(user_3, user_2, false)
279 .await
280 .unwrap();
281 assert!(!db.has_contact(user_2, user_3).await.unwrap());
282 assert!(!db.has_contact(user_3, user_2).await.unwrap());
283 assert_eq!(
284 db.get_contacts(user_2).await.unwrap(),
285 &[Contact::Accepted {
286 user_id: user_1,
287 busy: false,
288 }]
289 );
290 assert_eq!(
291 db.get_contacts(user_3).await.unwrap(),
292 &[Contact::Accepted {
293 user_id: user_1,
294 busy: false,
295 }],
296 );
297}
298
299test_both_dbs!(
300 test_project_count,
301 test_project_count_postgres,
302 test_project_count_sqlite
303);
304
305async fn test_project_count(db: &Arc<Database>) {
306 let owner_id = db.create_server("test").await.unwrap().0 as u32;
307
308 let user1 = db
309 .create_user(
310 "admin@example.com",
311 None,
312 true,
313 NewUserParams {
314 github_login: "admin".into(),
315 github_user_id: 0,
316 },
317 )
318 .await
319 .unwrap();
320 let user2 = db
321 .create_user(
322 "user@example.com",
323 None,
324 false,
325 NewUserParams {
326 github_login: "user".into(),
327 github_user_id: 1,
328 },
329 )
330 .await
331 .unwrap();
332
333 let room_id = RoomId::from_proto(
334 db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
335 .await
336 .unwrap()
337 .id,
338 );
339 db.call(
340 room_id,
341 user1.user_id,
342 ConnectionId { owner_id, id: 0 },
343 user2.user_id,
344 None,
345 )
346 .await
347 .unwrap();
348 db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
349 .await
350 .unwrap();
351 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
352
353 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
354 .await
355 .unwrap();
356 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
357
358 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
359 .await
360 .unwrap();
361 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
362
363 // Projects shared by admins aren't counted.
364 db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
365 .await
366 .unwrap();
367 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
368
369 db.leave_room(ConnectionId { owner_id, id: 1 })
370 .await
371 .unwrap();
372 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
373}
374
375#[test]
376fn test_fuzzy_like_string() {
377 assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
378 assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
379 assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
380}
381
382#[gpui::test]
383async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
384 // In CI, only run postgres tests on Linux (where we have the postgres service).
385 // Locally, always run them (assuming postgres is available).
386 if std::env::var("CI").is_ok() && !cfg!(target_os = "linux") {
387 return;
388 }
389 let test_db = TestDb::postgres(cx.executor());
390 let db = test_db.db();
391 for (i, github_login) in [
392 "California",
393 "colorado",
394 "oregon",
395 "washington",
396 "florida",
397 "delaware",
398 "rhode-island",
399 ]
400 .into_iter()
401 .enumerate()
402 {
403 db.create_user(
404 &format!("{github_login}@example.com"),
405 None,
406 false,
407 NewUserParams {
408 github_login: github_login.into(),
409 github_user_id: i as i32,
410 },
411 )
412 .await
413 .unwrap();
414 }
415
416 assert_eq!(
417 fuzzy_search_user_names(db, "clr").await,
418 &["colorado", "California"]
419 );
420 assert_eq!(
421 fuzzy_search_user_names(db, "ro").await,
422 &["rhode-island", "colorado", "oregon"],
423 );
424
425 async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
426 db.fuzzy_search_users(query, 10)
427 .await
428 .unwrap()
429 .into_iter()
430 .map(|user| user.github_login)
431 .collect::<Vec<_>>()
432 }
433}
434
435test_both_dbs!(
436 test_upsert_shared_thread,
437 test_upsert_shared_thread_postgres,
438 test_upsert_shared_thread_sqlite
439);
440
441async fn test_upsert_shared_thread(db: &Arc<Database>) {
442 use collab::db::SharedThreadId;
443 use uuid::Uuid;
444
445 let user_id = new_test_user(db, "user1@example.com").await;
446
447 let thread_id = SharedThreadId(Uuid::new_v4());
448 let title = "My Test Thread";
449 let data = b"test thread data".to_vec();
450
451 db.upsert_shared_thread(thread_id, user_id, title, data.clone())
452 .await
453 .unwrap();
454
455 let result = db.get_shared_thread(thread_id).await.unwrap();
456 assert!(result.is_some(), "Should find the shared thread");
457
458 let (thread, username) = result.unwrap();
459 assert_eq!(thread.title, title);
460 assert_eq!(thread.data, data);
461 assert_eq!(thread.user_id, user_id);
462 assert_eq!(username, "user1");
463}
464
465test_both_dbs!(
466 test_upsert_shared_thread_updates_existing,
467 test_upsert_shared_thread_updates_existing_postgres,
468 test_upsert_shared_thread_updates_existing_sqlite
469);
470
471async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
472 use collab::db::SharedThreadId;
473 use uuid::Uuid;
474
475 let user_id = new_test_user(db, "user1@example.com").await;
476
477 let thread_id = SharedThreadId(Uuid::new_v4());
478
479 // Create initial thread.
480 db.upsert_shared_thread(
481 thread_id,
482 user_id,
483 "Original Title",
484 b"original data".to_vec(),
485 )
486 .await
487 .unwrap();
488
489 // Update the same thread.
490 db.upsert_shared_thread(
491 thread_id,
492 user_id,
493 "Updated Title",
494 b"updated data".to_vec(),
495 )
496 .await
497 .unwrap();
498
499 let result = db.get_shared_thread(thread_id).await.unwrap();
500 let (thread, _) = result.unwrap();
501
502 assert_eq!(thread.title, "Updated Title");
503 assert_eq!(thread.data, b"updated data".to_vec());
504}
505
506test_both_dbs!(
507 test_cannot_update_another_users_shared_thread,
508 test_cannot_update_another_users_shared_thread_postgres,
509 test_cannot_update_another_users_shared_thread_sqlite
510);
511
512async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
513 use collab::db::SharedThreadId;
514 use uuid::Uuid;
515
516 let user1_id = new_test_user(db, "user1@example.com").await;
517 let user2_id = new_test_user(db, "user2@example.com").await;
518
519 let thread_id = SharedThreadId(Uuid::new_v4());
520
521 db.upsert_shared_thread(thread_id, user1_id, "User 1 Thread", b"user1 data".to_vec())
522 .await
523 .unwrap();
524
525 let result = db
526 .upsert_shared_thread(thread_id, user2_id, "User 2 Title", b"user2 data".to_vec())
527 .await;
528
529 assert!(
530 result.is_err(),
531 "Should not allow updating another user's thread"
532 );
533}
534
535test_both_dbs!(
536 test_get_nonexistent_shared_thread,
537 test_get_nonexistent_shared_thread_postgres,
538 test_get_nonexistent_shared_thread_sqlite
539);
540
541async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
542 use collab::db::SharedThreadId;
543 use uuid::Uuid;
544
545 let result = db
546 .get_shared_thread(SharedThreadId(Uuid::new_v4()))
547 .await
548 .unwrap();
549
550 assert!(result.is_none(), "Should not find non-existent thread");
551}