1use super::*;
2use crate::test_both_dbs;
3use gpui::executor::{Background, Deterministic};
4use pretty_assertions::{assert_eq, assert_ne};
5use std::sync::Arc;
6use tests::TestDb;
7
8test_both_dbs!(
9 test_get_users,
10 test_get_users_by_ids_postgres,
11 test_get_users_by_ids_sqlite
12);
13
14async fn test_get_users(db: &Arc<Database>) {
15 let mut user_ids = Vec::new();
16 let mut user_metric_ids = Vec::new();
17 for i in 1..=4 {
18 let user = db
19 .create_user(
20 &format!("user{i}@example.com"),
21 false,
22 NewUserParams {
23 github_login: format!("user{i}"),
24 github_user_id: i,
25 invite_count: 0,
26 },
27 )
28 .await
29 .unwrap();
30 user_ids.push(user.user_id);
31 user_metric_ids.push(user.metrics_id);
32 }
33
34 assert_eq!(
35 db.get_users_by_ids(user_ids.clone()).await.unwrap(),
36 vec![
37 User {
38 id: user_ids[0],
39 github_login: "user1".to_string(),
40 github_user_id: Some(1),
41 email_address: Some("user1@example.com".to_string()),
42 admin: false,
43 metrics_id: user_metric_ids[0].parse().unwrap(),
44 ..Default::default()
45 },
46 User {
47 id: user_ids[1],
48 github_login: "user2".to_string(),
49 github_user_id: Some(2),
50 email_address: Some("user2@example.com".to_string()),
51 admin: false,
52 metrics_id: user_metric_ids[1].parse().unwrap(),
53 ..Default::default()
54 },
55 User {
56 id: user_ids[2],
57 github_login: "user3".to_string(),
58 github_user_id: Some(3),
59 email_address: Some("user3@example.com".to_string()),
60 admin: false,
61 metrics_id: user_metric_ids[2].parse().unwrap(),
62 ..Default::default()
63 },
64 User {
65 id: user_ids[3],
66 github_login: "user4".to_string(),
67 github_user_id: Some(4),
68 email_address: Some("user4@example.com".to_string()),
69 admin: false,
70 metrics_id: user_metric_ids[3].parse().unwrap(),
71 ..Default::default()
72 }
73 ]
74 );
75}
76
77test_both_dbs!(
78 test_get_or_create_user_by_github_account,
79 test_get_or_create_user_by_github_account_postgres,
80 test_get_or_create_user_by_github_account_sqlite
81);
82
83async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
84 let user_id1 = db
85 .create_user(
86 "user1@example.com",
87 false,
88 NewUserParams {
89 github_login: "login1".into(),
90 github_user_id: 101,
91 invite_count: 0,
92 },
93 )
94 .await
95 .unwrap()
96 .user_id;
97 let user_id2 = db
98 .create_user(
99 "user2@example.com",
100 false,
101 NewUserParams {
102 github_login: "login2".into(),
103 github_user_id: 102,
104 invite_count: 0,
105 },
106 )
107 .await
108 .unwrap()
109 .user_id;
110
111 let user = db
112 .get_or_create_user_by_github_account("login1", None, None)
113 .await
114 .unwrap()
115 .unwrap();
116 assert_eq!(user.id, user_id1);
117 assert_eq!(&user.github_login, "login1");
118 assert_eq!(user.github_user_id, Some(101));
119
120 assert!(db
121 .get_or_create_user_by_github_account("non-existent-login", None, None)
122 .await
123 .unwrap()
124 .is_none());
125
126 let user = db
127 .get_or_create_user_by_github_account("the-new-login2", Some(102), None)
128 .await
129 .unwrap()
130 .unwrap();
131 assert_eq!(user.id, user_id2);
132 assert_eq!(&user.github_login, "the-new-login2");
133 assert_eq!(user.github_user_id, Some(102));
134
135 let user = db
136 .get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
137 .await
138 .unwrap()
139 .unwrap();
140 assert_eq!(&user.github_login, "login3");
141 assert_eq!(user.github_user_id, Some(103));
142 assert_eq!(user.email_address, Some("user3@example.com".into()));
143}
144
145test_both_dbs!(
146 test_create_access_tokens,
147 test_create_access_tokens_postgres,
148 test_create_access_tokens_sqlite
149);
150
151async fn test_create_access_tokens(db: &Arc<Database>) {
152 let user = db
153 .create_user(
154 "u1@example.com",
155 false,
156 NewUserParams {
157 github_login: "u1".into(),
158 github_user_id: 1,
159 invite_count: 0,
160 },
161 )
162 .await
163 .unwrap()
164 .user_id;
165
166 let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
167 let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
168 assert_eq!(
169 db.get_access_token(token_1).await.unwrap(),
170 access_token::Model {
171 id: token_1,
172 user_id: user,
173 hash: "h1".into(),
174 }
175 );
176 assert_eq!(
177 db.get_access_token(token_2).await.unwrap(),
178 access_token::Model {
179 id: token_2,
180 user_id: user,
181 hash: "h2".into()
182 }
183 );
184
185 let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
186 assert_eq!(
187 db.get_access_token(token_3).await.unwrap(),
188 access_token::Model {
189 id: token_3,
190 user_id: user,
191 hash: "h3".into()
192 }
193 );
194 assert_eq!(
195 db.get_access_token(token_2).await.unwrap(),
196 access_token::Model {
197 id: token_2,
198 user_id: user,
199 hash: "h2".into()
200 }
201 );
202 assert!(db.get_access_token(token_1).await.is_err());
203
204 let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
205 assert_eq!(
206 db.get_access_token(token_4).await.unwrap(),
207 access_token::Model {
208 id: token_4,
209 user_id: user,
210 hash: "h4".into()
211 }
212 );
213 assert_eq!(
214 db.get_access_token(token_3).await.unwrap(),
215 access_token::Model {
216 id: token_3,
217 user_id: user,
218 hash: "h3".into()
219 }
220 );
221 assert!(db.get_access_token(token_2).await.is_err());
222 assert!(db.get_access_token(token_1).await.is_err());
223}
224
225test_both_dbs!(
226 test_add_contacts,
227 test_add_contacts_postgres,
228 test_add_contacts_sqlite
229);
230
231async fn test_add_contacts(db: &Arc<Database>) {
232 let mut user_ids = Vec::new();
233 for i in 0..3 {
234 user_ids.push(
235 db.create_user(
236 &format!("user{i}@example.com"),
237 false,
238 NewUserParams {
239 github_login: format!("user{i}"),
240 github_user_id: i,
241 invite_count: 0,
242 },
243 )
244 .await
245 .unwrap()
246 .user_id,
247 );
248 }
249
250 let user_1 = user_ids[0];
251 let user_2 = user_ids[1];
252 let user_3 = user_ids[2];
253
254 // User starts with no contacts
255 assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
256
257 // User requests a contact. Both users see the pending request.
258 db.send_contact_request(user_1, user_2).await.unwrap();
259 assert!(!db.has_contact(user_1, user_2).await.unwrap());
260 assert!(!db.has_contact(user_2, user_1).await.unwrap());
261 assert_eq!(
262 db.get_contacts(user_1).await.unwrap(),
263 &[Contact::Outgoing { user_id: user_2 }],
264 );
265 assert_eq!(
266 db.get_contacts(user_2).await.unwrap(),
267 &[Contact::Incoming {
268 user_id: user_1,
269 should_notify: true
270 }]
271 );
272
273 // User 2 dismisses the contact request notification without accepting or rejecting.
274 // We shouldn't notify them again.
275 db.dismiss_contact_notification(user_1, user_2)
276 .await
277 .unwrap_err();
278 db.dismiss_contact_notification(user_2, user_1)
279 .await
280 .unwrap();
281 assert_eq!(
282 db.get_contacts(user_2).await.unwrap(),
283 &[Contact::Incoming {
284 user_id: user_1,
285 should_notify: false
286 }]
287 );
288
289 // User can't accept their own contact request
290 db.respond_to_contact_request(user_1, user_2, true)
291 .await
292 .unwrap_err();
293
294 // User accepts a contact request. Both users see the contact.
295 db.respond_to_contact_request(user_2, user_1, true)
296 .await
297 .unwrap();
298 assert_eq!(
299 db.get_contacts(user_1).await.unwrap(),
300 &[Contact::Accepted {
301 user_id: user_2,
302 should_notify: true,
303 busy: false,
304 }],
305 );
306 assert!(db.has_contact(user_1, user_2).await.unwrap());
307 assert!(db.has_contact(user_2, user_1).await.unwrap());
308 assert_eq!(
309 db.get_contacts(user_2).await.unwrap(),
310 &[Contact::Accepted {
311 user_id: user_1,
312 should_notify: false,
313 busy: false,
314 }]
315 );
316
317 // Users cannot re-request existing contacts.
318 db.send_contact_request(user_1, user_2).await.unwrap_err();
319 db.send_contact_request(user_2, user_1).await.unwrap_err();
320
321 // Users can't dismiss notifications of them accepting other users' requests.
322 db.dismiss_contact_notification(user_2, user_1)
323 .await
324 .unwrap_err();
325 assert_eq!(
326 db.get_contacts(user_1).await.unwrap(),
327 &[Contact::Accepted {
328 user_id: user_2,
329 should_notify: true,
330 busy: false,
331 }]
332 );
333
334 // Users can dismiss notifications of other users accepting their requests.
335 db.dismiss_contact_notification(user_1, user_2)
336 .await
337 .unwrap();
338 assert_eq!(
339 db.get_contacts(user_1).await.unwrap(),
340 &[Contact::Accepted {
341 user_id: user_2,
342 should_notify: false,
343 busy: false,
344 }]
345 );
346
347 // Users send each other concurrent contact requests and
348 // see that they are immediately accepted.
349 db.send_contact_request(user_1, user_3).await.unwrap();
350 db.send_contact_request(user_3, user_1).await.unwrap();
351 assert_eq!(
352 db.get_contacts(user_1).await.unwrap(),
353 &[
354 Contact::Accepted {
355 user_id: user_2,
356 should_notify: false,
357 busy: false,
358 },
359 Contact::Accepted {
360 user_id: user_3,
361 should_notify: false,
362 busy: false,
363 }
364 ]
365 );
366 assert_eq!(
367 db.get_contacts(user_3).await.unwrap(),
368 &[Contact::Accepted {
369 user_id: user_1,
370 should_notify: false,
371 busy: false,
372 }],
373 );
374
375 // User declines a contact request. Both users see that it is gone.
376 db.send_contact_request(user_2, user_3).await.unwrap();
377 db.respond_to_contact_request(user_3, user_2, false)
378 .await
379 .unwrap();
380 assert!(!db.has_contact(user_2, user_3).await.unwrap());
381 assert!(!db.has_contact(user_3, user_2).await.unwrap());
382 assert_eq!(
383 db.get_contacts(user_2).await.unwrap(),
384 &[Contact::Accepted {
385 user_id: user_1,
386 should_notify: false,
387 busy: false,
388 }]
389 );
390 assert_eq!(
391 db.get_contacts(user_3).await.unwrap(),
392 &[Contact::Accepted {
393 user_id: user_1,
394 should_notify: false,
395 busy: false,
396 }],
397 );
398}
399
400test_both_dbs!(
401 test_metrics_id,
402 test_metrics_id_postgres,
403 test_metrics_id_sqlite
404);
405
406async fn test_metrics_id(db: &Arc<Database>) {
407 let NewUserResult {
408 user_id: user1,
409 metrics_id: metrics_id1,
410 ..
411 } = db
412 .create_user(
413 "person1@example.com",
414 false,
415 NewUserParams {
416 github_login: "person1".into(),
417 github_user_id: 101,
418 invite_count: 5,
419 },
420 )
421 .await
422 .unwrap();
423 let NewUserResult {
424 user_id: user2,
425 metrics_id: metrics_id2,
426 ..
427 } = db
428 .create_user(
429 "person2@example.com",
430 false,
431 NewUserParams {
432 github_login: "person2".into(),
433 github_user_id: 102,
434 invite_count: 5,
435 },
436 )
437 .await
438 .unwrap();
439
440 assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
441 assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
442 assert_eq!(metrics_id1.len(), 36);
443 assert_eq!(metrics_id2.len(), 36);
444 assert_ne!(metrics_id1, metrics_id2);
445}
446
447test_both_dbs!(
448 test_project_count,
449 test_project_count_postgres,
450 test_project_count_sqlite
451);
452
453async fn test_project_count(db: &Arc<Database>) {
454 let owner_id = db.create_server("test").await.unwrap().0 as u32;
455
456 let user1 = db
457 .create_user(
458 &format!("admin@example.com"),
459 true,
460 NewUserParams {
461 github_login: "admin".into(),
462 github_user_id: 0,
463 invite_count: 0,
464 },
465 )
466 .await
467 .unwrap();
468 let user2 = db
469 .create_user(
470 &format!("user@example.com"),
471 false,
472 NewUserParams {
473 github_login: "user".into(),
474 github_user_id: 1,
475 invite_count: 0,
476 },
477 )
478 .await
479 .unwrap();
480
481 let room_id = RoomId::from_proto(
482 db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
483 .await
484 .unwrap()
485 .id,
486 );
487 db.call(
488 room_id,
489 user1.user_id,
490 ConnectionId { owner_id, id: 0 },
491 user2.user_id,
492 None,
493 )
494 .await
495 .unwrap();
496 db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
497 .await
498 .unwrap();
499 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
500
501 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
502 .await
503 .unwrap();
504 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
505
506 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
507 .await
508 .unwrap();
509 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
510
511 // Projects shared by admins aren't counted.
512 db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
513 .await
514 .unwrap();
515 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
516
517 db.leave_room(ConnectionId { owner_id, id: 1 })
518 .await
519 .unwrap();
520 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
521}
522
523#[test]
524fn test_fuzzy_like_string() {
525 assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
526 assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
527 assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
528}
529
530#[gpui::test]
531async fn test_fuzzy_search_users() {
532 let test_db = TestDb::postgres(build_background_executor());
533 let db = test_db.db();
534 for (i, github_login) in [
535 "California",
536 "colorado",
537 "oregon",
538 "washington",
539 "florida",
540 "delaware",
541 "rhode-island",
542 ]
543 .into_iter()
544 .enumerate()
545 {
546 db.create_user(
547 &format!("{github_login}@example.com"),
548 false,
549 NewUserParams {
550 github_login: github_login.into(),
551 github_user_id: i as i32,
552 invite_count: 0,
553 },
554 )
555 .await
556 .unwrap();
557 }
558
559 assert_eq!(
560 fuzzy_search_user_names(db, "clr").await,
561 &["colorado", "California"]
562 );
563 assert_eq!(
564 fuzzy_search_user_names(db, "ro").await,
565 &["rhode-island", "colorado", "oregon"],
566 );
567
568 async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
569 db.fuzzy_search_users(query, 10)
570 .await
571 .unwrap()
572 .into_iter()
573 .map(|user| user.github_login)
574 .collect::<Vec<_>>()
575 }
576}
577
578#[gpui::test]
579async fn test_invite_codes() {
580 let test_db = TestDb::postgres(build_background_executor());
581 let db = test_db.db();
582
583 let NewUserResult { user_id: user1, .. } = db
584 .create_user(
585 "user1@example.com",
586 false,
587 NewUserParams {
588 github_login: "user1".into(),
589 github_user_id: 0,
590 invite_count: 0,
591 },
592 )
593 .await
594 .unwrap();
595
596 // Initially, user 1 has no invite code
597 assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
598
599 // Setting invite count to 0 when no code is assigned does not assign a new code
600 db.set_invite_count_for_user(user1, 0).await.unwrap();
601 assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
602
603 // User 1 creates an invite code that can be used twice.
604 db.set_invite_count_for_user(user1, 2).await.unwrap();
605 let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
606 assert_eq!(invite_count, 2);
607
608 // User 2 redeems the invite code and becomes a contact of user 1.
609 let user2_invite = db
610 .create_invite_from_code(
611 &invite_code,
612 "user2@example.com",
613 Some("user-2-device-id"),
614 true,
615 )
616 .await
617 .unwrap();
618 let NewUserResult {
619 user_id: user2,
620 inviting_user_id,
621 signup_device_id,
622 metrics_id,
623 } = db
624 .create_user_from_invite(
625 &user2_invite,
626 NewUserParams {
627 github_login: "user2".into(),
628 github_user_id: 2,
629 invite_count: 7,
630 },
631 )
632 .await
633 .unwrap()
634 .unwrap();
635 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
636 assert_eq!(invite_count, 1);
637 assert_eq!(inviting_user_id, Some(user1));
638 assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
639 assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
640 assert_eq!(
641 db.get_contacts(user1).await.unwrap(),
642 [Contact::Accepted {
643 user_id: user2,
644 should_notify: true,
645 busy: false,
646 }]
647 );
648 assert_eq!(
649 db.get_contacts(user2).await.unwrap(),
650 [Contact::Accepted {
651 user_id: user1,
652 should_notify: false,
653 busy: false,
654 }]
655 );
656 assert!(db.has_contact(user1, user2).await.unwrap());
657 assert!(db.has_contact(user2, user1).await.unwrap());
658 assert_eq!(
659 db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
660 7
661 );
662
663 // User 3 redeems the invite code and becomes a contact of user 1.
664 let user3_invite = db
665 .create_invite_from_code(&invite_code, "user3@example.com", None, true)
666 .await
667 .unwrap();
668 let NewUserResult {
669 user_id: user3,
670 inviting_user_id,
671 signup_device_id,
672 ..
673 } = db
674 .create_user_from_invite(
675 &user3_invite,
676 NewUserParams {
677 github_login: "user-3".into(),
678 github_user_id: 3,
679 invite_count: 3,
680 },
681 )
682 .await
683 .unwrap()
684 .unwrap();
685 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
686 assert_eq!(invite_count, 0);
687 assert_eq!(inviting_user_id, Some(user1));
688 assert!(signup_device_id.is_none());
689 assert_eq!(
690 db.get_contacts(user1).await.unwrap(),
691 [
692 Contact::Accepted {
693 user_id: user2,
694 should_notify: true,
695 busy: false,
696 },
697 Contact::Accepted {
698 user_id: user3,
699 should_notify: true,
700 busy: false,
701 }
702 ]
703 );
704 assert_eq!(
705 db.get_contacts(user3).await.unwrap(),
706 [Contact::Accepted {
707 user_id: user1,
708 should_notify: false,
709 busy: false,
710 }]
711 );
712 assert!(db.has_contact(user1, user3).await.unwrap());
713 assert!(db.has_contact(user3, user1).await.unwrap());
714 assert_eq!(
715 db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
716 3
717 );
718
719 // Trying to reedem the code for the third time results in an error.
720 db.create_invite_from_code(
721 &invite_code,
722 "user4@example.com",
723 Some("user-4-device-id"),
724 true,
725 )
726 .await
727 .unwrap_err();
728
729 // Invite count can be updated after the code has been created.
730 db.set_invite_count_for_user(user1, 2).await.unwrap();
731 let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
732 assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
733 assert_eq!(invite_count, 2);
734
735 // User 4 can now redeem the invite code and becomes a contact of user 1.
736 let user4_invite = db
737 .create_invite_from_code(
738 &invite_code,
739 "user4@example.com",
740 Some("user-4-device-id"),
741 true,
742 )
743 .await
744 .unwrap();
745 let user4 = db
746 .create_user_from_invite(
747 &user4_invite,
748 NewUserParams {
749 github_login: "user-4".into(),
750 github_user_id: 4,
751 invite_count: 5,
752 },
753 )
754 .await
755 .unwrap()
756 .unwrap()
757 .user_id;
758
759 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
760 assert_eq!(invite_count, 1);
761 assert_eq!(
762 db.get_contacts(user1).await.unwrap(),
763 [
764 Contact::Accepted {
765 user_id: user2,
766 should_notify: true,
767 busy: false,
768 },
769 Contact::Accepted {
770 user_id: user3,
771 should_notify: true,
772 busy: false,
773 },
774 Contact::Accepted {
775 user_id: user4,
776 should_notify: true,
777 busy: false,
778 }
779 ]
780 );
781 assert_eq!(
782 db.get_contacts(user4).await.unwrap(),
783 [Contact::Accepted {
784 user_id: user1,
785 should_notify: false,
786 busy: false,
787 }]
788 );
789 assert!(db.has_contact(user1, user4).await.unwrap());
790 assert!(db.has_contact(user4, user1).await.unwrap());
791 assert_eq!(
792 db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
793 5
794 );
795
796 // An existing user cannot redeem invite codes.
797 db.create_invite_from_code(
798 &invite_code,
799 "user2@example.com",
800 Some("user-2-device-id"),
801 true,
802 )
803 .await
804 .unwrap_err();
805 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
806 assert_eq!(invite_count, 1);
807
808 // A newer user can invite an existing one via a different email address
809 // than the one they used to sign up.
810 let user5 = db
811 .create_user(
812 "user5@example.com",
813 false,
814 NewUserParams {
815 github_login: "user5".into(),
816 github_user_id: 5,
817 invite_count: 0,
818 },
819 )
820 .await
821 .unwrap()
822 .user_id;
823 db.set_invite_count_for_user(user5, 5).await.unwrap();
824 let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
825 let user5_invite_to_user1 = db
826 .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
827 .await
828 .unwrap();
829 let user1_2 = db
830 .create_user_from_invite(
831 &user5_invite_to_user1,
832 NewUserParams {
833 github_login: "user1".into(),
834 github_user_id: 1,
835 invite_count: 5,
836 },
837 )
838 .await
839 .unwrap()
840 .unwrap()
841 .user_id;
842 assert_eq!(user1_2, user1);
843 assert_eq!(
844 db.get_contacts(user1).await.unwrap(),
845 [
846 Contact::Accepted {
847 user_id: user2,
848 should_notify: true,
849 busy: false,
850 },
851 Contact::Accepted {
852 user_id: user3,
853 should_notify: true,
854 busy: false,
855 },
856 Contact::Accepted {
857 user_id: user4,
858 should_notify: true,
859 busy: false,
860 },
861 Contact::Accepted {
862 user_id: user5,
863 should_notify: false,
864 busy: false,
865 }
866 ]
867 );
868 assert_eq!(
869 db.get_contacts(user5).await.unwrap(),
870 [Contact::Accepted {
871 user_id: user1,
872 should_notify: true,
873 busy: false,
874 }]
875 );
876 assert!(db.has_contact(user1, user5).await.unwrap());
877 assert!(db.has_contact(user5, user1).await.unwrap());
878}
879
880test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
881
882async fn test_channels(db: &Arc<Database>) {
883 let a_id = db
884 .create_user(
885 "user1@example.com",
886 false,
887 NewUserParams {
888 github_login: "user1".into(),
889 github_user_id: 5,
890 invite_count: 0,
891 },
892 )
893 .await
894 .unwrap()
895 .user_id;
896
897 let b_id = db
898 .create_user(
899 "user2@example.com",
900 false,
901 NewUserParams {
902 github_login: "user2".into(),
903 github_user_id: 6,
904 invite_count: 0,
905 },
906 )
907 .await
908 .unwrap()
909 .user_id;
910
911 let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
912
913 // Make sure that people cannot read channels they haven't been invited to
914 assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
915
916 db.invite_channel_member(zed_id, b_id, a_id, false)
917 .await
918 .unwrap();
919
920 db.respond_to_channel_invite(zed_id, b_id, true)
921 .await
922 .unwrap();
923
924 let crdb_id = db
925 .create_channel("crdb", Some(zed_id), "2", a_id)
926 .await
927 .unwrap();
928 let livestreaming_id = db
929 .create_channel("livestreaming", Some(zed_id), "3", a_id)
930 .await
931 .unwrap();
932 let replace_id = db
933 .create_channel("replace", Some(zed_id), "4", a_id)
934 .await
935 .unwrap();
936
937 let mut members = db.get_channel_members(replace_id).await.unwrap();
938 members.sort();
939 assert_eq!(members, &[a_id, b_id]);
940
941 let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap();
942 let cargo_id = db
943 .create_channel("cargo", Some(rust_id), "6", a_id)
944 .await
945 .unwrap();
946
947 let cargo_ra_id = db
948 .create_channel("cargo-ra", Some(cargo_id), "7", a_id)
949 .await
950 .unwrap();
951
952 let result = db.get_channels_for_user(a_id).await.unwrap();
953 assert_eq!(
954 result.channels,
955 vec![
956 Channel {
957 id: zed_id,
958 name: "zed".to_string(),
959 parent_id: None,
960 },
961 Channel {
962 id: crdb_id,
963 name: "crdb".to_string(),
964 parent_id: Some(zed_id),
965 },
966 Channel {
967 id: livestreaming_id,
968 name: "livestreaming".to_string(),
969 parent_id: Some(zed_id),
970 },
971 Channel {
972 id: replace_id,
973 name: "replace".to_string(),
974 parent_id: Some(zed_id),
975 },
976 Channel {
977 id: rust_id,
978 name: "rust".to_string(),
979 parent_id: None,
980 },
981 Channel {
982 id: cargo_id,
983 name: "cargo".to_string(),
984 parent_id: Some(rust_id),
985 },
986 Channel {
987 id: cargo_ra_id,
988 name: "cargo-ra".to_string(),
989 parent_id: Some(cargo_id),
990 }
991 ]
992 );
993
994 let result = db.get_channels_for_user(b_id).await.unwrap();
995 assert_eq!(
996 result.channels,
997 vec![
998 Channel {
999 id: zed_id,
1000 name: "zed".to_string(),
1001 parent_id: None,
1002 },
1003 Channel {
1004 id: crdb_id,
1005 name: "crdb".to_string(),
1006 parent_id: Some(zed_id),
1007 },
1008 Channel {
1009 id: livestreaming_id,
1010 name: "livestreaming".to_string(),
1011 parent_id: Some(zed_id),
1012 },
1013 Channel {
1014 id: replace_id,
1015 name: "replace".to_string(),
1016 parent_id: Some(zed_id),
1017 },
1018 ]
1019 );
1020
1021 // Update member permissions
1022 let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
1023 assert!(set_subchannel_admin.is_err());
1024 let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
1025 assert!(set_channel_admin.is_ok());
1026
1027 let result = db.get_channels_for_user(b_id).await.unwrap();
1028 assert_eq!(
1029 result.channels,
1030 vec![
1031 Channel {
1032 id: zed_id,
1033 name: "zed".to_string(),
1034 parent_id: None,
1035 },
1036 Channel {
1037 id: crdb_id,
1038 name: "crdb".to_string(),
1039 parent_id: Some(zed_id),
1040 },
1041 Channel {
1042 id: livestreaming_id,
1043 name: "livestreaming".to_string(),
1044 parent_id: Some(zed_id),
1045 },
1046 Channel {
1047 id: replace_id,
1048 name: "replace".to_string(),
1049 parent_id: Some(zed_id),
1050 },
1051 ]
1052 );
1053
1054 // Remove a single channel
1055 db.remove_channel(crdb_id, a_id).await.unwrap();
1056 assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
1057
1058 // Remove a channel tree
1059 let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
1060 channel_ids.sort();
1061 assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
1062 assert_eq!(user_ids, &[a_id]);
1063
1064 assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
1065 assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
1066 assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
1067}
1068
1069test_both_dbs!(
1070 test_joining_channels,
1071 test_joining_channels_postgres,
1072 test_joining_channels_sqlite
1073);
1074
1075async fn test_joining_channels(db: &Arc<Database>) {
1076 let owner_id = db.create_server("test").await.unwrap().0 as u32;
1077
1078 let user_1 = db
1079 .create_user(
1080 "user1@example.com",
1081 false,
1082 NewUserParams {
1083 github_login: "user1".into(),
1084 github_user_id: 5,
1085 invite_count: 0,
1086 },
1087 )
1088 .await
1089 .unwrap()
1090 .user_id;
1091 let user_2 = db
1092 .create_user(
1093 "user2@example.com",
1094 false,
1095 NewUserParams {
1096 github_login: "user2".into(),
1097 github_user_id: 6,
1098 invite_count: 0,
1099 },
1100 )
1101 .await
1102 .unwrap()
1103 .user_id;
1104
1105 let channel_1 = db
1106 .create_root_channel("channel_1", "1", user_1)
1107 .await
1108 .unwrap();
1109 let room_1 = db.room_id_for_channel(channel_1).await.unwrap();
1110
1111 // can join a room with membership to its channel
1112 let joined_room = db
1113 .join_room(room_1, user_1, ConnectionId { owner_id, id: 1 })
1114 .await
1115 .unwrap();
1116 assert_eq!(joined_room.room.participants.len(), 1);
1117
1118 drop(joined_room);
1119 // cannot join a room without membership to its channel
1120 assert!(db
1121 .join_room(room_1, user_2, ConnectionId { owner_id, id: 1 })
1122 .await
1123 .is_err());
1124}
1125
1126test_both_dbs!(
1127 test_channel_invites,
1128 test_channel_invites_postgres,
1129 test_channel_invites_sqlite
1130);
1131
1132async fn test_channel_invites(db: &Arc<Database>) {
1133 db.create_server("test").await.unwrap();
1134
1135 let user_1 = db
1136 .create_user(
1137 "user1@example.com",
1138 false,
1139 NewUserParams {
1140 github_login: "user1".into(),
1141 github_user_id: 5,
1142 invite_count: 0,
1143 },
1144 )
1145 .await
1146 .unwrap()
1147 .user_id;
1148 let user_2 = db
1149 .create_user(
1150 "user2@example.com",
1151 false,
1152 NewUserParams {
1153 github_login: "user2".into(),
1154 github_user_id: 6,
1155 invite_count: 0,
1156 },
1157 )
1158 .await
1159 .unwrap()
1160 .user_id;
1161
1162 let user_3 = db
1163 .create_user(
1164 "user3@example.com",
1165 false,
1166 NewUserParams {
1167 github_login: "user3".into(),
1168 github_user_id: 7,
1169 invite_count: 0,
1170 },
1171 )
1172 .await
1173 .unwrap()
1174 .user_id;
1175
1176 let channel_1_1 = db
1177 .create_root_channel("channel_1", "1", user_1)
1178 .await
1179 .unwrap();
1180
1181 let channel_1_2 = db
1182 .create_root_channel("channel_2", "2", user_1)
1183 .await
1184 .unwrap();
1185
1186 db.invite_channel_member(channel_1_1, user_2, user_1, false)
1187 .await
1188 .unwrap();
1189 db.invite_channel_member(channel_1_2, user_2, user_1, false)
1190 .await
1191 .unwrap();
1192 db.invite_channel_member(channel_1_1, user_3, user_1, true)
1193 .await
1194 .unwrap();
1195
1196 let user_2_invites = db
1197 .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
1198 .await
1199 .unwrap()
1200 .into_iter()
1201 .map(|channel| channel.id)
1202 .collect::<Vec<_>>();
1203
1204 assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
1205
1206 let user_3_invites = db
1207 .get_channel_invites_for_user(user_3) // -> [channel_1_1]
1208 .await
1209 .unwrap()
1210 .into_iter()
1211 .map(|channel| channel.id)
1212 .collect::<Vec<_>>();
1213
1214 assert_eq!(user_3_invites, &[channel_1_1]);
1215
1216 let members = db
1217 .get_channel_member_details(channel_1_1, user_1)
1218 .await
1219 .unwrap();
1220 assert_eq!(
1221 members,
1222 &[
1223 proto::ChannelMember {
1224 user_id: user_1.to_proto(),
1225 kind: proto::channel_member::Kind::Member.into(),
1226 admin: true,
1227 },
1228 proto::ChannelMember {
1229 user_id: user_2.to_proto(),
1230 kind: proto::channel_member::Kind::Invitee.into(),
1231 admin: false,
1232 },
1233 proto::ChannelMember {
1234 user_id: user_3.to_proto(),
1235 kind: proto::channel_member::Kind::Invitee.into(),
1236 admin: true,
1237 },
1238 ]
1239 );
1240
1241 db.respond_to_channel_invite(channel_1_1, user_2, true)
1242 .await
1243 .unwrap();
1244
1245 let channel_1_3 = db
1246 .create_channel("channel_3", Some(channel_1_1), "1", user_1)
1247 .await
1248 .unwrap();
1249
1250 let members = db
1251 .get_channel_member_details(channel_1_3, user_1)
1252 .await
1253 .unwrap();
1254 assert_eq!(
1255 members,
1256 &[
1257 proto::ChannelMember {
1258 user_id: user_1.to_proto(),
1259 kind: proto::channel_member::Kind::Member.into(),
1260 admin: true,
1261 },
1262 proto::ChannelMember {
1263 user_id: user_2.to_proto(),
1264 kind: proto::channel_member::Kind::AncestorMember.into(),
1265 admin: false,
1266 },
1267 ]
1268 );
1269}
1270
1271test_both_dbs!(
1272 test_channel_renames,
1273 test_channel_renames_postgres,
1274 test_channel_renames_sqlite
1275);
1276
1277async fn test_channel_renames(db: &Arc<Database>) {
1278 db.create_server("test").await.unwrap();
1279
1280 let user_1 = db
1281 .create_user(
1282 "user1@example.com",
1283 false,
1284 NewUserParams {
1285 github_login: "user1".into(),
1286 github_user_id: 5,
1287 invite_count: 0,
1288 },
1289 )
1290 .await
1291 .unwrap()
1292 .user_id;
1293
1294 let user_2 = db
1295 .create_user(
1296 "user2@example.com",
1297 false,
1298 NewUserParams {
1299 github_login: "user2".into(),
1300 github_user_id: 6,
1301 invite_count: 0,
1302 },
1303 )
1304 .await
1305 .unwrap()
1306 .user_id;
1307
1308 let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap();
1309
1310 db.rename_channel(zed_id, user_1, "#zed-archive")
1311 .await
1312 .unwrap();
1313
1314 let zed_archive_id = zed_id;
1315
1316 let (channel, _) = db
1317 .get_channel(zed_archive_id, user_1)
1318 .await
1319 .unwrap()
1320 .unwrap();
1321 assert_eq!(channel.name, "zed-archive");
1322
1323 let non_permissioned_rename = db
1324 .rename_channel(zed_archive_id, user_2, "hacked-lol")
1325 .await;
1326 assert!(non_permissioned_rename.is_err());
1327
1328 let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
1329 assert!(bad_name_rename.is_err())
1330}
1331
1332#[gpui::test]
1333async fn test_multiple_signup_overwrite() {
1334 let test_db = TestDb::postgres(build_background_executor());
1335 let db = test_db.db();
1336
1337 let email_address = "user_1@example.com".to_string();
1338
1339 let initial_signup_created_at_milliseconds = 0;
1340
1341 let initial_signup = NewSignup {
1342 email_address: email_address.clone(),
1343 platform_mac: false,
1344 platform_linux: true,
1345 platform_windows: false,
1346 editor_features: vec!["speed".into()],
1347 programming_languages: vec!["rust".into(), "c".into()],
1348 device_id: Some(format!("device_id")),
1349 added_to_mailing_list: false,
1350 created_at: Some(
1351 DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
1352 ),
1353 };
1354
1355 db.create_signup(&initial_signup).await.unwrap();
1356
1357 let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
1358
1359 assert_eq!(
1360 initial_signup_from_db.clone(),
1361 signup::Model {
1362 email_address: initial_signup.email_address,
1363 platform_mac: initial_signup.platform_mac,
1364 platform_linux: initial_signup.platform_linux,
1365 platform_windows: initial_signup.platform_windows,
1366 editor_features: Some(initial_signup.editor_features),
1367 programming_languages: Some(initial_signup.programming_languages),
1368 added_to_mailing_list: initial_signup.added_to_mailing_list,
1369 ..initial_signup_from_db
1370 }
1371 );
1372
1373 let subsequent_signup = NewSignup {
1374 email_address: email_address.clone(),
1375 platform_mac: true,
1376 platform_linux: false,
1377 platform_windows: true,
1378 editor_features: vec!["git integration".into(), "clean design".into()],
1379 programming_languages: vec!["d".into(), "elm".into()],
1380 device_id: Some(format!("different_device_id")),
1381 added_to_mailing_list: true,
1382 // subsequent signup happens next day
1383 created_at: Some(
1384 DateTime::from_timestamp_millis(
1385 initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
1386 )
1387 .unwrap(),
1388 ),
1389 };
1390
1391 db.create_signup(&subsequent_signup).await.unwrap();
1392
1393 let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
1394
1395 assert_eq!(
1396 subsequent_signup_from_db.clone(),
1397 signup::Model {
1398 platform_mac: subsequent_signup.platform_mac,
1399 platform_linux: subsequent_signup.platform_linux,
1400 platform_windows: subsequent_signup.platform_windows,
1401 editor_features: Some(subsequent_signup.editor_features),
1402 programming_languages: Some(subsequent_signup.programming_languages),
1403 device_id: subsequent_signup.device_id,
1404 added_to_mailing_list: subsequent_signup.added_to_mailing_list,
1405 // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
1406 created_at: initial_signup_from_db.created_at,
1407 ..subsequent_signup_from_db
1408 }
1409 );
1410}
1411
1412#[gpui::test]
1413async fn test_signups() {
1414 let test_db = TestDb::postgres(build_background_executor());
1415 let db = test_db.db();
1416
1417 let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
1418
1419 let all_signups = usernames
1420 .iter()
1421 .enumerate()
1422 .map(|(i, username)| NewSignup {
1423 email_address: format!("{username}@example.com"),
1424 platform_mac: true,
1425 platform_linux: i % 2 == 0,
1426 platform_windows: i % 4 == 0,
1427 editor_features: vec!["speed".into()],
1428 programming_languages: vec!["rust".into(), "c".into()],
1429 device_id: Some(format!("device_id_{i}")),
1430 added_to_mailing_list: i != 0, // One user failed to subscribe
1431 created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
1432 })
1433 .collect::<Vec<NewSignup>>();
1434
1435 // people sign up on the waitlist
1436 for signup in &all_signups {
1437 // users can sign up multiple times without issues
1438 for _ in 0..2 {
1439 db.create_signup(&signup).await.unwrap();
1440 }
1441 }
1442
1443 assert_eq!(
1444 db.get_waitlist_summary().await.unwrap(),
1445 WaitlistSummary {
1446 count: 8,
1447 mac_count: 8,
1448 linux_count: 4,
1449 windows_count: 2,
1450 unknown_count: 0,
1451 }
1452 );
1453
1454 // retrieve the next batch of signup emails to send
1455 let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1456 let addresses = signups_batch1
1457 .iter()
1458 .map(|s| &s.email_address)
1459 .collect::<Vec<_>>();
1460 assert_eq!(
1461 addresses,
1462 &[
1463 all_signups[0].email_address.as_str(),
1464 all_signups[1].email_address.as_str(),
1465 all_signups[2].email_address.as_str()
1466 ]
1467 );
1468 assert_ne!(
1469 signups_batch1[0].email_confirmation_code,
1470 signups_batch1[1].email_confirmation_code
1471 );
1472
1473 // the waitlist isn't updated until we record that the emails
1474 // were successfully sent.
1475 let signups_batch = db.get_unsent_invites(3).await.unwrap();
1476 assert_eq!(signups_batch, signups_batch1);
1477
1478 // once the emails go out, we can retrieve the next batch
1479 // of signups.
1480 db.record_sent_invites(&signups_batch1).await.unwrap();
1481 let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1482 let addresses = signups_batch2
1483 .iter()
1484 .map(|s| &s.email_address)
1485 .collect::<Vec<_>>();
1486 assert_eq!(
1487 addresses,
1488 &[
1489 all_signups[3].email_address.as_str(),
1490 all_signups[4].email_address.as_str(),
1491 all_signups[5].email_address.as_str()
1492 ]
1493 );
1494
1495 // the sent invites are excluded from the summary.
1496 assert_eq!(
1497 db.get_waitlist_summary().await.unwrap(),
1498 WaitlistSummary {
1499 count: 5,
1500 mac_count: 5,
1501 linux_count: 2,
1502 windows_count: 1,
1503 unknown_count: 0,
1504 }
1505 );
1506
1507 // user completes the signup process by providing their
1508 // github account.
1509 let NewUserResult {
1510 user_id,
1511 inviting_user_id,
1512 signup_device_id,
1513 ..
1514 } = db
1515 .create_user_from_invite(
1516 &Invite {
1517 ..signups_batch1[0].clone()
1518 },
1519 NewUserParams {
1520 github_login: usernames[0].clone(),
1521 github_user_id: 0,
1522 invite_count: 5,
1523 },
1524 )
1525 .await
1526 .unwrap()
1527 .unwrap();
1528 let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1529 assert!(inviting_user_id.is_none());
1530 assert_eq!(user.github_login, usernames[0]);
1531 assert_eq!(
1532 user.email_address,
1533 Some(all_signups[0].email_address.clone())
1534 );
1535 assert_eq!(user.invite_count, 5);
1536 assert_eq!(signup_device_id.unwrap(), "device_id_0");
1537
1538 // cannot redeem the same signup again.
1539 assert!(db
1540 .create_user_from_invite(
1541 &Invite {
1542 email_address: signups_batch1[0].email_address.clone(),
1543 email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1544 },
1545 NewUserParams {
1546 github_login: "some-other-github_account".into(),
1547 github_user_id: 1,
1548 invite_count: 5,
1549 },
1550 )
1551 .await
1552 .unwrap()
1553 .is_none());
1554
1555 // cannot redeem a signup with the wrong confirmation code.
1556 db.create_user_from_invite(
1557 &Invite {
1558 email_address: signups_batch1[1].email_address.clone(),
1559 email_confirmation_code: "the-wrong-code".to_string(),
1560 },
1561 NewUserParams {
1562 github_login: usernames[1].clone(),
1563 github_user_id: 2,
1564 invite_count: 5,
1565 },
1566 )
1567 .await
1568 .unwrap_err();
1569}
1570
1571fn build_background_executor() -> Arc<Background> {
1572 Deterministic::new(0).build_background()
1573}