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_create_access_tokens,
141 test_create_access_tokens_postgres,
142 test_create_access_tokens_sqlite
143);
144
145async fn test_create_access_tokens(db: &Arc<Database>) {
146 let user_1 = db
147 .create_user(
148 "u1@example.com",
149 None,
150 false,
151 NewUserParams {
152 github_login: "u1".into(),
153 github_user_id: 1,
154 },
155 )
156 .await
157 .unwrap()
158 .user_id;
159 let user_2 = db
160 .create_user(
161 "u2@example.com",
162 None,
163 false,
164 NewUserParams {
165 github_login: "u2".into(),
166 github_user_id: 2,
167 },
168 )
169 .await
170 .unwrap()
171 .user_id;
172
173 let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap();
174 let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap();
175 assert_eq!(
176 db.get_access_token(token_1).await.unwrap(),
177 access_token::Model {
178 id: token_1,
179 user_id: user_1,
180 impersonated_user_id: None,
181 hash: "h1".into(),
182 }
183 );
184 assert_eq!(
185 db.get_access_token(token_2).await.unwrap(),
186 access_token::Model {
187 id: token_2,
188 user_id: user_1,
189 impersonated_user_id: None,
190 hash: "h2".into()
191 }
192 );
193
194 let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap();
195 assert_eq!(
196 db.get_access_token(token_3).await.unwrap(),
197 access_token::Model {
198 id: token_3,
199 user_id: user_1,
200 impersonated_user_id: None,
201 hash: "h3".into()
202 }
203 );
204 assert_eq!(
205 db.get_access_token(token_2).await.unwrap(),
206 access_token::Model {
207 id: token_2,
208 user_id: user_1,
209 impersonated_user_id: None,
210 hash: "h2".into()
211 }
212 );
213 assert!(db.get_access_token(token_1).await.is_err());
214
215 let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap();
216 assert_eq!(
217 db.get_access_token(token_4).await.unwrap(),
218 access_token::Model {
219 id: token_4,
220 user_id: user_1,
221 impersonated_user_id: None,
222 hash: "h4".into()
223 }
224 );
225 assert_eq!(
226 db.get_access_token(token_3).await.unwrap(),
227 access_token::Model {
228 id: token_3,
229 user_id: user_1,
230 impersonated_user_id: None,
231 hash: "h3".into()
232 }
233 );
234 assert!(db.get_access_token(token_2).await.is_err());
235 assert!(db.get_access_token(token_1).await.is_err());
236
237 // An access token for user 2 impersonating user 1 does not
238 // count against user 1's access token limit (of 2).
239 let token_5 = db
240 .create_access_token(user_2, Some(user_1), "h5", 2)
241 .await
242 .unwrap();
243 assert_eq!(
244 db.get_access_token(token_5).await.unwrap(),
245 access_token::Model {
246 id: token_5,
247 user_id: user_2,
248 impersonated_user_id: Some(user_1),
249 hash: "h5".into()
250 }
251 );
252 assert_eq!(
253 db.get_access_token(token_3).await.unwrap(),
254 access_token::Model {
255 id: token_3,
256 user_id: user_1,
257 impersonated_user_id: None,
258 hash: "h3".into()
259 }
260 );
261
262 // Only a limited number (2) of access tokens are stored for user 2
263 // impersonating other users.
264 let token_6 = db
265 .create_access_token(user_2, Some(user_1), "h6", 2)
266 .await
267 .unwrap();
268 let token_7 = db
269 .create_access_token(user_2, Some(user_1), "h7", 2)
270 .await
271 .unwrap();
272 assert_eq!(
273 db.get_access_token(token_6).await.unwrap(),
274 access_token::Model {
275 id: token_6,
276 user_id: user_2,
277 impersonated_user_id: Some(user_1),
278 hash: "h6".into()
279 }
280 );
281 assert_eq!(
282 db.get_access_token(token_7).await.unwrap(),
283 access_token::Model {
284 id: token_7,
285 user_id: user_2,
286 impersonated_user_id: Some(user_1),
287 hash: "h7".into()
288 }
289 );
290 assert!(db.get_access_token(token_5).await.is_err());
291 assert_eq!(
292 db.get_access_token(token_3).await.unwrap(),
293 access_token::Model {
294 id: token_3,
295 user_id: user_1,
296 impersonated_user_id: None,
297 hash: "h3".into()
298 }
299 );
300}
301
302test_both_dbs!(
303 test_add_contacts,
304 test_add_contacts_postgres,
305 test_add_contacts_sqlite
306);
307
308async fn test_add_contacts(db: &Arc<Database>) {
309 let mut user_ids = Vec::new();
310 for i in 0..3 {
311 user_ids.push(
312 db.create_user(
313 &format!("user{i}@example.com"),
314 None,
315 false,
316 NewUserParams {
317 github_login: format!("user{i}"),
318 github_user_id: i,
319 },
320 )
321 .await
322 .unwrap()
323 .user_id,
324 );
325 }
326
327 let user_1 = user_ids[0];
328 let user_2 = user_ids[1];
329 let user_3 = user_ids[2];
330
331 // User starts with no contacts
332 assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
333
334 // User requests a contact. Both users see the pending request.
335 db.send_contact_request(user_1, user_2).await.unwrap();
336 assert!(!db.has_contact(user_1, user_2).await.unwrap());
337 assert!(!db.has_contact(user_2, user_1).await.unwrap());
338 assert_eq!(
339 db.get_contacts(user_1).await.unwrap(),
340 &[Contact::Outgoing { user_id: user_2 }],
341 );
342 assert_eq!(
343 db.get_contacts(user_2).await.unwrap(),
344 &[Contact::Incoming { user_id: user_1 }]
345 );
346
347 // User 2 dismisses the contact request notification without accepting or rejecting.
348 // We shouldn't notify them again.
349 db.dismiss_contact_notification(user_1, user_2)
350 .await
351 .unwrap_err();
352 db.dismiss_contact_notification(user_2, user_1)
353 .await
354 .unwrap();
355 assert_eq!(
356 db.get_contacts(user_2).await.unwrap(),
357 &[Contact::Incoming { user_id: user_1 }]
358 );
359
360 // User can't accept their own contact request
361 db.respond_to_contact_request(user_1, user_2, true)
362 .await
363 .unwrap_err();
364
365 // User accepts a contact request. Both users see the contact.
366 db.respond_to_contact_request(user_2, user_1, true)
367 .await
368 .unwrap();
369 assert_eq!(
370 db.get_contacts(user_1).await.unwrap(),
371 &[Contact::Accepted {
372 user_id: user_2,
373 busy: false,
374 }],
375 );
376 assert!(db.has_contact(user_1, user_2).await.unwrap());
377 assert!(db.has_contact(user_2, user_1).await.unwrap());
378 assert_eq!(
379 db.get_contacts(user_2).await.unwrap(),
380 &[Contact::Accepted {
381 user_id: user_1,
382 busy: false,
383 }]
384 );
385
386 // Users cannot re-request existing contacts.
387 db.send_contact_request(user_1, user_2).await.unwrap_err();
388 db.send_contact_request(user_2, user_1).await.unwrap_err();
389
390 // Users can't dismiss notifications of them accepting other users' requests.
391 db.dismiss_contact_notification(user_2, user_1)
392 .await
393 .unwrap_err();
394 assert_eq!(
395 db.get_contacts(user_1).await.unwrap(),
396 &[Contact::Accepted {
397 user_id: user_2,
398 busy: false,
399 }]
400 );
401
402 // Users can dismiss notifications of other users accepting their requests.
403 db.dismiss_contact_notification(user_1, user_2)
404 .await
405 .unwrap();
406 assert_eq!(
407 db.get_contacts(user_1).await.unwrap(),
408 &[Contact::Accepted {
409 user_id: user_2,
410 busy: false,
411 }]
412 );
413
414 // Users send each other concurrent contact requests and
415 // see that they are immediately accepted.
416 db.send_contact_request(user_1, user_3).await.unwrap();
417 db.send_contact_request(user_3, user_1).await.unwrap();
418 assert_eq!(
419 db.get_contacts(user_1).await.unwrap(),
420 &[
421 Contact::Accepted {
422 user_id: user_2,
423 busy: false,
424 },
425 Contact::Accepted {
426 user_id: user_3,
427 busy: false,
428 }
429 ]
430 );
431 assert_eq!(
432 db.get_contacts(user_3).await.unwrap(),
433 &[Contact::Accepted {
434 user_id: user_1,
435 busy: false,
436 }],
437 );
438
439 // User declines a contact request. Both users see that it is gone.
440 db.send_contact_request(user_2, user_3).await.unwrap();
441 db.respond_to_contact_request(user_3, user_2, false)
442 .await
443 .unwrap();
444 assert!(!db.has_contact(user_2, user_3).await.unwrap());
445 assert!(!db.has_contact(user_3, user_2).await.unwrap());
446 assert_eq!(
447 db.get_contacts(user_2).await.unwrap(),
448 &[Contact::Accepted {
449 user_id: user_1,
450 busy: false,
451 }]
452 );
453 assert_eq!(
454 db.get_contacts(user_3).await.unwrap(),
455 &[Contact::Accepted {
456 user_id: user_1,
457 busy: false,
458 }],
459 );
460}
461
462test_both_dbs!(
463 test_project_count,
464 test_project_count_postgres,
465 test_project_count_sqlite
466);
467
468async fn test_project_count(db: &Arc<Database>) {
469 let owner_id = db.create_server("test").await.unwrap().0 as u32;
470
471 let user1 = db
472 .create_user(
473 "admin@example.com",
474 None,
475 true,
476 NewUserParams {
477 github_login: "admin".into(),
478 github_user_id: 0,
479 },
480 )
481 .await
482 .unwrap();
483 let user2 = db
484 .create_user(
485 "user@example.com",
486 None,
487 false,
488 NewUserParams {
489 github_login: "user".into(),
490 github_user_id: 1,
491 },
492 )
493 .await
494 .unwrap();
495
496 let room_id = RoomId::from_proto(
497 db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
498 .await
499 .unwrap()
500 .id,
501 );
502 db.call(
503 room_id,
504 user1.user_id,
505 ConnectionId { owner_id, id: 0 },
506 user2.user_id,
507 None,
508 )
509 .await
510 .unwrap();
511 db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
512 .await
513 .unwrap();
514 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
515
516 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
517 .await
518 .unwrap();
519 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
520
521 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
522 .await
523 .unwrap();
524 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
525
526 // Projects shared by admins aren't counted.
527 db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
528 .await
529 .unwrap();
530 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
531
532 db.leave_room(ConnectionId { owner_id, id: 1 })
533 .await
534 .unwrap();
535 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
536}
537
538#[test]
539fn test_fuzzy_like_string() {
540 assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
541 assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
542 assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
543}
544
545#[gpui::test]
546async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
547 // In CI, only run postgres tests on Linux (where we have the postgres service).
548 // Locally, always run them (assuming postgres is available).
549 if std::env::var("CI").is_ok() && !cfg!(target_os = "linux") {
550 return;
551 }
552 let test_db = TestDb::postgres(cx.executor());
553 let db = test_db.db();
554 for (i, github_login) in [
555 "California",
556 "colorado",
557 "oregon",
558 "washington",
559 "florida",
560 "delaware",
561 "rhode-island",
562 ]
563 .into_iter()
564 .enumerate()
565 {
566 db.create_user(
567 &format!("{github_login}@example.com"),
568 None,
569 false,
570 NewUserParams {
571 github_login: github_login.into(),
572 github_user_id: i as i32,
573 },
574 )
575 .await
576 .unwrap();
577 }
578
579 assert_eq!(
580 fuzzy_search_user_names(db, "clr").await,
581 &["colorado", "California"]
582 );
583 assert_eq!(
584 fuzzy_search_user_names(db, "ro").await,
585 &["rhode-island", "colorado", "oregon"],
586 );
587
588 async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
589 db.fuzzy_search_users(query, 10)
590 .await
591 .unwrap()
592 .into_iter()
593 .map(|user| user.github_login)
594 .collect::<Vec<_>>()
595 }
596}
597
598test_both_dbs!(
599 test_upsert_shared_thread,
600 test_upsert_shared_thread_postgres,
601 test_upsert_shared_thread_sqlite
602);
603
604async fn test_upsert_shared_thread(db: &Arc<Database>) {
605 use collab::db::SharedThreadId;
606 use uuid::Uuid;
607
608 let user_id = new_test_user(db, "user1@example.com").await;
609
610 let thread_id = SharedThreadId(Uuid::new_v4());
611 let title = "My Test Thread";
612 let data = b"test thread data".to_vec();
613
614 db.upsert_shared_thread(thread_id, user_id, title, data.clone())
615 .await
616 .unwrap();
617
618 let result = db.get_shared_thread(thread_id).await.unwrap();
619 assert!(result.is_some(), "Should find the shared thread");
620
621 let (thread, username) = result.unwrap();
622 assert_eq!(thread.title, title);
623 assert_eq!(thread.data, data);
624 assert_eq!(thread.user_id, user_id);
625 assert_eq!(username, "user1");
626}
627
628test_both_dbs!(
629 test_upsert_shared_thread_updates_existing,
630 test_upsert_shared_thread_updates_existing_postgres,
631 test_upsert_shared_thread_updates_existing_sqlite
632);
633
634async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
635 use collab::db::SharedThreadId;
636 use uuid::Uuid;
637
638 let user_id = new_test_user(db, "user1@example.com").await;
639
640 let thread_id = SharedThreadId(Uuid::new_v4());
641
642 // Create initial thread.
643 db.upsert_shared_thread(
644 thread_id,
645 user_id,
646 "Original Title",
647 b"original data".to_vec(),
648 )
649 .await
650 .unwrap();
651
652 // Update the same thread.
653 db.upsert_shared_thread(
654 thread_id,
655 user_id,
656 "Updated Title",
657 b"updated data".to_vec(),
658 )
659 .await
660 .unwrap();
661
662 let result = db.get_shared_thread(thread_id).await.unwrap();
663 let (thread, _) = result.unwrap();
664
665 assert_eq!(thread.title, "Updated Title");
666 assert_eq!(thread.data, b"updated data".to_vec());
667}
668
669test_both_dbs!(
670 test_cannot_update_another_users_shared_thread,
671 test_cannot_update_another_users_shared_thread_postgres,
672 test_cannot_update_another_users_shared_thread_sqlite
673);
674
675async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
676 use collab::db::SharedThreadId;
677 use uuid::Uuid;
678
679 let user1_id = new_test_user(db, "user1@example.com").await;
680 let user2_id = new_test_user(db, "user2@example.com").await;
681
682 let thread_id = SharedThreadId(Uuid::new_v4());
683
684 db.upsert_shared_thread(thread_id, user1_id, "User 1 Thread", b"user1 data".to_vec())
685 .await
686 .unwrap();
687
688 let result = db
689 .upsert_shared_thread(thread_id, user2_id, "User 2 Title", b"user2 data".to_vec())
690 .await;
691
692 assert!(
693 result.is_err(),
694 "Should not allow updating another user's thread"
695 );
696}
697
698test_both_dbs!(
699 test_get_nonexistent_shared_thread,
700 test_get_nonexistent_shared_thread_postgres,
701 test_get_nonexistent_shared_thread_sqlite
702);
703
704async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
705 use collab::db::SharedThreadId;
706 use uuid::Uuid;
707
708 let result = db
709 .get_shared_thread(SharedThreadId(Uuid::new_v4()))
710 .await
711 .unwrap();
712
713 assert!(result.is_none(), "Should not find non-existent thread");
714}