1use super::db::*;
2use collections::HashMap;
3use gpui::executor::{Background, Deterministic};
4use std::{sync::Arc, time::Duration};
5use time::OffsetDateTime;
6
7#[tokio::test(flavor = "multi_thread")]
8async fn test_get_users_by_ids() {
9 for test_db in [
10 TestDb::postgres().await,
11 TestDb::fake(build_background_executor()),
12 ] {
13 let db = test_db.db();
14
15 let mut user_ids = Vec::new();
16 for i in 1..=4 {
17 user_ids.push(
18 db.create_user(
19 &format!("user{i}@example.com"),
20 false,
21 NewUserParams {
22 github_login: format!("user{i}"),
23 github_user_id: i,
24 invite_count: 0,
25 },
26 )
27 .await
28 .unwrap()
29 .user_id,
30 );
31 }
32
33 assert_eq!(
34 db.get_users_by_ids(user_ids.clone()).await.unwrap(),
35 vec![
36 User {
37 id: user_ids[0],
38 github_login: "user1".to_string(),
39 github_user_id: Some(1),
40 email_address: Some("user1@example.com".to_string()),
41 admin: false,
42 ..Default::default()
43 },
44 User {
45 id: user_ids[1],
46 github_login: "user2".to_string(),
47 github_user_id: Some(2),
48 email_address: Some("user2@example.com".to_string()),
49 admin: false,
50 ..Default::default()
51 },
52 User {
53 id: user_ids[2],
54 github_login: "user3".to_string(),
55 github_user_id: Some(3),
56 email_address: Some("user3@example.com".to_string()),
57 admin: false,
58 ..Default::default()
59 },
60 User {
61 id: user_ids[3],
62 github_login: "user4".to_string(),
63 github_user_id: Some(4),
64 email_address: Some("user4@example.com".to_string()),
65 admin: false,
66 ..Default::default()
67 }
68 ]
69 );
70 }
71}
72
73#[tokio::test(flavor = "multi_thread")]
74async fn test_get_user_by_github_account() {
75 for test_db in [
76 TestDb::postgres().await,
77 TestDb::fake(build_background_executor()),
78 ] {
79 let db = test_db.db();
80 let user_id1 = db
81 .create_user(
82 "user1@example.com",
83 false,
84 NewUserParams {
85 github_login: "login1".into(),
86 github_user_id: 101,
87 invite_count: 0,
88 },
89 )
90 .await
91 .unwrap()
92 .user_id;
93 let user_id2 = db
94 .create_user(
95 "user2@example.com",
96 false,
97 NewUserParams {
98 github_login: "login2".into(),
99 github_user_id: 102,
100 invite_count: 0,
101 },
102 )
103 .await
104 .unwrap()
105 .user_id;
106
107 let user = db
108 .get_user_by_github_account("login1", None)
109 .await
110 .unwrap()
111 .unwrap();
112 assert_eq!(user.id, user_id1);
113 assert_eq!(&user.github_login, "login1");
114 assert_eq!(user.github_user_id, Some(101));
115
116 assert!(db
117 .get_user_by_github_account("non-existent-login", None)
118 .await
119 .unwrap()
120 .is_none());
121
122 let user = db
123 .get_user_by_github_account("the-new-login2", Some(102))
124 .await
125 .unwrap()
126 .unwrap();
127 assert_eq!(user.id, user_id2);
128 assert_eq!(&user.github_login, "the-new-login2");
129 assert_eq!(user.github_user_id, Some(102));
130 }
131}
132
133#[tokio::test(flavor = "multi_thread")]
134async fn test_worktree_extensions() {
135 let test_db = TestDb::postgres().await;
136 let db = test_db.db();
137
138 let user = db
139 .create_user(
140 "u1@example.com",
141 false,
142 NewUserParams {
143 github_login: "u1".into(),
144 github_user_id: 0,
145 invite_count: 0,
146 },
147 )
148 .await
149 .unwrap()
150 .user_id;
151 let project = db.register_project(user).await.unwrap();
152
153 db.update_worktree_extensions(project, 100, Default::default())
154 .await
155 .unwrap();
156 db.update_worktree_extensions(
157 project,
158 100,
159 [("rs".to_string(), 5), ("md".to_string(), 3)]
160 .into_iter()
161 .collect(),
162 )
163 .await
164 .unwrap();
165 db.update_worktree_extensions(
166 project,
167 100,
168 [("rs".to_string(), 6), ("md".to_string(), 5)]
169 .into_iter()
170 .collect(),
171 )
172 .await
173 .unwrap();
174 db.update_worktree_extensions(
175 project,
176 101,
177 [("ts".to_string(), 2), ("md".to_string(), 1)]
178 .into_iter()
179 .collect(),
180 )
181 .await
182 .unwrap();
183
184 assert_eq!(
185 db.get_project_extensions(project).await.unwrap(),
186 [
187 (
188 100,
189 [("rs".into(), 6), ("md".into(), 5),]
190 .into_iter()
191 .collect::<HashMap<_, _>>()
192 ),
193 (
194 101,
195 [("ts".into(), 2), ("md".into(), 1),]
196 .into_iter()
197 .collect::<HashMap<_, _>>()
198 )
199 ]
200 .into_iter()
201 .collect()
202 );
203}
204
205#[tokio::test(flavor = "multi_thread")]
206async fn test_user_activity() {
207 let test_db = TestDb::postgres().await;
208 let db = test_db.db();
209
210 let mut user_ids = Vec::new();
211 for i in 0..=2 {
212 user_ids.push(
213 db.create_user(
214 &format!("user{i}@example.com"),
215 false,
216 NewUserParams {
217 github_login: format!("user{i}"),
218 github_user_id: i,
219 invite_count: 0,
220 },
221 )
222 .await
223 .unwrap()
224 .user_id,
225 );
226 }
227
228 let project_1 = db.register_project(user_ids[0]).await.unwrap();
229 db.update_worktree_extensions(
230 project_1,
231 1,
232 HashMap::from_iter([("rs".into(), 5), ("md".into(), 7)]),
233 )
234 .await
235 .unwrap();
236 let project_2 = db.register_project(user_ids[1]).await.unwrap();
237 let t0 = OffsetDateTime::now_utc() - Duration::from_secs(60 * 60);
238
239 // User 2 opens a project
240 let t1 = t0 + Duration::from_secs(10);
241 db.record_user_activity(t0..t1, &[(user_ids[1], project_2)])
242 .await
243 .unwrap();
244
245 let t2 = t1 + Duration::from_secs(10);
246 db.record_user_activity(t1..t2, &[(user_ids[1], project_2)])
247 .await
248 .unwrap();
249
250 // User 1 joins the project
251 let t3 = t2 + Duration::from_secs(10);
252 db.record_user_activity(
253 t2..t3,
254 &[(user_ids[1], project_2), (user_ids[0], project_2)],
255 )
256 .await
257 .unwrap();
258
259 // User 1 opens another project
260 let t4 = t3 + Duration::from_secs(10);
261 db.record_user_activity(
262 t3..t4,
263 &[
264 (user_ids[1], project_2),
265 (user_ids[0], project_2),
266 (user_ids[0], project_1),
267 ],
268 )
269 .await
270 .unwrap();
271
272 // User 3 joins that project
273 let t5 = t4 + Duration::from_secs(10);
274 db.record_user_activity(
275 t4..t5,
276 &[
277 (user_ids[1], project_2),
278 (user_ids[0], project_2),
279 (user_ids[0], project_1),
280 (user_ids[2], project_1),
281 ],
282 )
283 .await
284 .unwrap();
285
286 // User 2 leaves
287 let t6 = t5 + Duration::from_secs(5);
288 db.record_user_activity(
289 t5..t6,
290 &[(user_ids[0], project_1), (user_ids[2], project_1)],
291 )
292 .await
293 .unwrap();
294
295 let t7 = t6 + Duration::from_secs(60);
296 let t8 = t7 + Duration::from_secs(10);
297 db.record_user_activity(t7..t8, &[(user_ids[0], project_1)])
298 .await
299 .unwrap();
300
301 assert_eq!(
302 db.get_top_users_activity_summary(t0..t6, 10).await.unwrap(),
303 &[
304 UserActivitySummary {
305 id: user_ids[0],
306 github_login: "user0".to_string(),
307 project_activity: vec![
308 ProjectActivitySummary {
309 id: project_1,
310 duration: Duration::from_secs(25),
311 max_collaborators: 2
312 },
313 ProjectActivitySummary {
314 id: project_2,
315 duration: Duration::from_secs(30),
316 max_collaborators: 2
317 }
318 ]
319 },
320 UserActivitySummary {
321 id: user_ids[1],
322 github_login: "user1".to_string(),
323 project_activity: vec![ProjectActivitySummary {
324 id: project_2,
325 duration: Duration::from_secs(50),
326 max_collaborators: 2
327 }]
328 },
329 UserActivitySummary {
330 id: user_ids[2],
331 github_login: "user2".to_string(),
332 project_activity: vec![ProjectActivitySummary {
333 id: project_1,
334 duration: Duration::from_secs(15),
335 max_collaborators: 2
336 }]
337 },
338 ]
339 );
340
341 assert_eq!(
342 db.get_active_user_count(t0..t6, Duration::from_secs(56), false)
343 .await
344 .unwrap(),
345 0
346 );
347 assert_eq!(
348 db.get_active_user_count(t0..t6, Duration::from_secs(56), true)
349 .await
350 .unwrap(),
351 0
352 );
353 assert_eq!(
354 db.get_active_user_count(t0..t6, Duration::from_secs(54), false)
355 .await
356 .unwrap(),
357 1
358 );
359 assert_eq!(
360 db.get_active_user_count(t0..t6, Duration::from_secs(54), true)
361 .await
362 .unwrap(),
363 1
364 );
365 assert_eq!(
366 db.get_active_user_count(t0..t6, Duration::from_secs(30), false)
367 .await
368 .unwrap(),
369 2
370 );
371 assert_eq!(
372 db.get_active_user_count(t0..t6, Duration::from_secs(30), true)
373 .await
374 .unwrap(),
375 2
376 );
377 assert_eq!(
378 db.get_active_user_count(t0..t6, Duration::from_secs(10), false)
379 .await
380 .unwrap(),
381 3
382 );
383 assert_eq!(
384 db.get_active_user_count(t0..t6, Duration::from_secs(10), true)
385 .await
386 .unwrap(),
387 3
388 );
389 assert_eq!(
390 db.get_active_user_count(t0..t1, Duration::from_secs(5), false)
391 .await
392 .unwrap(),
393 1
394 );
395 assert_eq!(
396 db.get_active_user_count(t0..t1, Duration::from_secs(5), true)
397 .await
398 .unwrap(),
399 0
400 );
401
402 assert_eq!(
403 db.get_user_activity_timeline(t3..t6, user_ids[0])
404 .await
405 .unwrap(),
406 &[
407 UserActivityPeriod {
408 project_id: project_1,
409 start: t3,
410 end: t6,
411 extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
412 },
413 UserActivityPeriod {
414 project_id: project_2,
415 start: t3,
416 end: t5,
417 extensions: Default::default(),
418 },
419 ]
420 );
421 assert_eq!(
422 db.get_user_activity_timeline(t0..t8, user_ids[0])
423 .await
424 .unwrap(),
425 &[
426 UserActivityPeriod {
427 project_id: project_2,
428 start: t2,
429 end: t5,
430 extensions: Default::default(),
431 },
432 UserActivityPeriod {
433 project_id: project_1,
434 start: t3,
435 end: t6,
436 extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
437 },
438 UserActivityPeriod {
439 project_id: project_1,
440 start: t7,
441 end: t8,
442 extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
443 },
444 ]
445 );
446}
447
448#[tokio::test(flavor = "multi_thread")]
449async fn test_recent_channel_messages() {
450 for test_db in [
451 TestDb::postgres().await,
452 TestDb::fake(build_background_executor()),
453 ] {
454 let db = test_db.db();
455 let user = db
456 .create_user(
457 "u@example.com",
458 false,
459 NewUserParams {
460 github_login: "u".into(),
461 github_user_id: 1,
462 invite_count: 0,
463 },
464 )
465 .await
466 .unwrap()
467 .user_id;
468 let org = db.create_org("org", "org").await.unwrap();
469 let channel = db.create_org_channel(org, "channel").await.unwrap();
470 for i in 0..10 {
471 db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i)
472 .await
473 .unwrap();
474 }
475
476 let messages = db.get_channel_messages(channel, 5, None).await.unwrap();
477 assert_eq!(
478 messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
479 ["5", "6", "7", "8", "9"]
480 );
481
482 let prev_messages = db
483 .get_channel_messages(channel, 4, Some(messages[0].id))
484 .await
485 .unwrap();
486 assert_eq!(
487 prev_messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
488 ["1", "2", "3", "4"]
489 );
490 }
491}
492
493#[tokio::test(flavor = "multi_thread")]
494async fn test_channel_message_nonces() {
495 for test_db in [
496 TestDb::postgres().await,
497 TestDb::fake(build_background_executor()),
498 ] {
499 let db = test_db.db();
500 let user = db
501 .create_user(
502 "user@example.com",
503 false,
504 NewUserParams {
505 github_login: "user".into(),
506 github_user_id: 1,
507 invite_count: 0,
508 },
509 )
510 .await
511 .unwrap()
512 .user_id;
513 let org = db.create_org("org", "org").await.unwrap();
514 let channel = db.create_org_channel(org, "channel").await.unwrap();
515
516 let msg1_id = db
517 .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1)
518 .await
519 .unwrap();
520 let msg2_id = db
521 .create_channel_message(channel, user, "2", OffsetDateTime::now_utc(), 2)
522 .await
523 .unwrap();
524 let msg3_id = db
525 .create_channel_message(channel, user, "3", OffsetDateTime::now_utc(), 1)
526 .await
527 .unwrap();
528 let msg4_id = db
529 .create_channel_message(channel, user, "4", OffsetDateTime::now_utc(), 2)
530 .await
531 .unwrap();
532
533 assert_ne!(msg1_id, msg2_id);
534 assert_eq!(msg1_id, msg3_id);
535 assert_eq!(msg2_id, msg4_id);
536 }
537}
538
539#[tokio::test(flavor = "multi_thread")]
540async fn test_create_access_tokens() {
541 let test_db = TestDb::postgres().await;
542 let db = test_db.db();
543 let user = db
544 .create_user(
545 "u1@example.com",
546 false,
547 NewUserParams {
548 github_login: "u1".into(),
549 github_user_id: 1,
550 invite_count: 0,
551 },
552 )
553 .await
554 .unwrap()
555 .user_id;
556
557 db.create_access_token_hash(user, "h1", 3).await.unwrap();
558 db.create_access_token_hash(user, "h2", 3).await.unwrap();
559 assert_eq!(
560 db.get_access_token_hashes(user).await.unwrap(),
561 &["h2".to_string(), "h1".to_string()]
562 );
563
564 db.create_access_token_hash(user, "h3", 3).await.unwrap();
565 assert_eq!(
566 db.get_access_token_hashes(user).await.unwrap(),
567 &["h3".to_string(), "h2".to_string(), "h1".to_string(),]
568 );
569
570 db.create_access_token_hash(user, "h4", 3).await.unwrap();
571 assert_eq!(
572 db.get_access_token_hashes(user).await.unwrap(),
573 &["h4".to_string(), "h3".to_string(), "h2".to_string(),]
574 );
575
576 db.create_access_token_hash(user, "h5", 3).await.unwrap();
577 assert_eq!(
578 db.get_access_token_hashes(user).await.unwrap(),
579 &["h5".to_string(), "h4".to_string(), "h3".to_string()]
580 );
581}
582
583#[test]
584fn test_fuzzy_like_string() {
585 assert_eq!(PostgresDb::fuzzy_like_string("abcd"), "%a%b%c%d%");
586 assert_eq!(PostgresDb::fuzzy_like_string("x y"), "%x%y%");
587 assert_eq!(PostgresDb::fuzzy_like_string(" z "), "%z%");
588}
589
590#[tokio::test(flavor = "multi_thread")]
591async fn test_fuzzy_search_users() {
592 let test_db = TestDb::postgres().await;
593 let db = test_db.db();
594 for (i, github_login) in [
595 "California",
596 "colorado",
597 "oregon",
598 "washington",
599 "florida",
600 "delaware",
601 "rhode-island",
602 ]
603 .into_iter()
604 .enumerate()
605 {
606 db.create_user(
607 &format!("{github_login}@example.com"),
608 false,
609 NewUserParams {
610 github_login: github_login.into(),
611 github_user_id: i as i32,
612 invite_count: 0,
613 },
614 )
615 .await
616 .unwrap();
617 }
618
619 assert_eq!(
620 fuzzy_search_user_names(db, "clr").await,
621 &["colorado", "California"]
622 );
623 assert_eq!(
624 fuzzy_search_user_names(db, "ro").await,
625 &["rhode-island", "colorado", "oregon"],
626 );
627
628 async fn fuzzy_search_user_names(db: &Arc<dyn Db>, query: &str) -> Vec<String> {
629 db.fuzzy_search_users(query, 10)
630 .await
631 .unwrap()
632 .into_iter()
633 .map(|user| user.github_login)
634 .collect::<Vec<_>>()
635 }
636}
637
638#[tokio::test(flavor = "multi_thread")]
639async fn test_add_contacts() {
640 for test_db in [
641 TestDb::postgres().await,
642 TestDb::fake(build_background_executor()),
643 ] {
644 let db = test_db.db();
645
646 let mut user_ids = Vec::new();
647 for i in 0..3 {
648 user_ids.push(
649 db.create_user(
650 &format!("user{i}@example.com"),
651 false,
652 NewUserParams {
653 github_login: format!("user{i}"),
654 github_user_id: i,
655 invite_count: 0,
656 },
657 )
658 .await
659 .unwrap()
660 .user_id,
661 );
662 }
663
664 let user_1 = user_ids[0];
665 let user_2 = user_ids[1];
666 let user_3 = user_ids[2];
667
668 // User starts with no contacts
669 assert_eq!(
670 db.get_contacts(user_1).await.unwrap(),
671 vec![Contact::Accepted {
672 user_id: user_1,
673 should_notify: false
674 }],
675 );
676
677 // User requests a contact. Both users see the pending request.
678 db.send_contact_request(user_1, user_2).await.unwrap();
679 assert!(!db.has_contact(user_1, user_2).await.unwrap());
680 assert!(!db.has_contact(user_2, user_1).await.unwrap());
681 assert_eq!(
682 db.get_contacts(user_1).await.unwrap(),
683 &[
684 Contact::Accepted {
685 user_id: user_1,
686 should_notify: false
687 },
688 Contact::Outgoing { user_id: user_2 }
689 ],
690 );
691 assert_eq!(
692 db.get_contacts(user_2).await.unwrap(),
693 &[
694 Contact::Incoming {
695 user_id: user_1,
696 should_notify: true
697 },
698 Contact::Accepted {
699 user_id: user_2,
700 should_notify: false
701 },
702 ]
703 );
704
705 // User 2 dismisses the contact request notification without accepting or rejecting.
706 // We shouldn't notify them again.
707 db.dismiss_contact_notification(user_1, user_2)
708 .await
709 .unwrap_err();
710 db.dismiss_contact_notification(user_2, user_1)
711 .await
712 .unwrap();
713 assert_eq!(
714 db.get_contacts(user_2).await.unwrap(),
715 &[
716 Contact::Incoming {
717 user_id: user_1,
718 should_notify: false
719 },
720 Contact::Accepted {
721 user_id: user_2,
722 should_notify: false
723 },
724 ]
725 );
726
727 // User can't accept their own contact request
728 db.respond_to_contact_request(user_1, user_2, true)
729 .await
730 .unwrap_err();
731
732 // User accepts a contact request. Both users see the contact.
733 db.respond_to_contact_request(user_2, user_1, true)
734 .await
735 .unwrap();
736 assert_eq!(
737 db.get_contacts(user_1).await.unwrap(),
738 &[
739 Contact::Accepted {
740 user_id: user_1,
741 should_notify: false
742 },
743 Contact::Accepted {
744 user_id: user_2,
745 should_notify: true
746 }
747 ],
748 );
749 assert!(db.has_contact(user_1, user_2).await.unwrap());
750 assert!(db.has_contact(user_2, user_1).await.unwrap());
751 assert_eq!(
752 db.get_contacts(user_2).await.unwrap(),
753 &[
754 Contact::Accepted {
755 user_id: user_1,
756 should_notify: false,
757 },
758 Contact::Accepted {
759 user_id: user_2,
760 should_notify: false,
761 },
762 ]
763 );
764
765 // Users cannot re-request existing contacts.
766 db.send_contact_request(user_1, user_2).await.unwrap_err();
767 db.send_contact_request(user_2, user_1).await.unwrap_err();
768
769 // Users can't dismiss notifications of them accepting other users' requests.
770 db.dismiss_contact_notification(user_2, user_1)
771 .await
772 .unwrap_err();
773 assert_eq!(
774 db.get_contacts(user_1).await.unwrap(),
775 &[
776 Contact::Accepted {
777 user_id: user_1,
778 should_notify: false
779 },
780 Contact::Accepted {
781 user_id: user_2,
782 should_notify: true,
783 },
784 ]
785 );
786
787 // Users can dismiss notifications of other users accepting their requests.
788 db.dismiss_contact_notification(user_1, user_2)
789 .await
790 .unwrap();
791 assert_eq!(
792 db.get_contacts(user_1).await.unwrap(),
793 &[
794 Contact::Accepted {
795 user_id: user_1,
796 should_notify: false
797 },
798 Contact::Accepted {
799 user_id: user_2,
800 should_notify: false,
801 },
802 ]
803 );
804
805 // Users send each other concurrent contact requests and
806 // see that they are immediately accepted.
807 db.send_contact_request(user_1, user_3).await.unwrap();
808 db.send_contact_request(user_3, user_1).await.unwrap();
809 assert_eq!(
810 db.get_contacts(user_1).await.unwrap(),
811 &[
812 Contact::Accepted {
813 user_id: user_1,
814 should_notify: false
815 },
816 Contact::Accepted {
817 user_id: user_2,
818 should_notify: false,
819 },
820 Contact::Accepted {
821 user_id: user_3,
822 should_notify: false
823 },
824 ]
825 );
826 assert_eq!(
827 db.get_contacts(user_3).await.unwrap(),
828 &[
829 Contact::Accepted {
830 user_id: user_1,
831 should_notify: false
832 },
833 Contact::Accepted {
834 user_id: user_3,
835 should_notify: false
836 }
837 ],
838 );
839
840 // User declines a contact request. Both users see that it is gone.
841 db.send_contact_request(user_2, user_3).await.unwrap();
842 db.respond_to_contact_request(user_3, user_2, false)
843 .await
844 .unwrap();
845 assert!(!db.has_contact(user_2, user_3).await.unwrap());
846 assert!(!db.has_contact(user_3, user_2).await.unwrap());
847 assert_eq!(
848 db.get_contacts(user_2).await.unwrap(),
849 &[
850 Contact::Accepted {
851 user_id: user_1,
852 should_notify: false
853 },
854 Contact::Accepted {
855 user_id: user_2,
856 should_notify: false
857 }
858 ]
859 );
860 assert_eq!(
861 db.get_contacts(user_3).await.unwrap(),
862 &[
863 Contact::Accepted {
864 user_id: user_1,
865 should_notify: false
866 },
867 Contact::Accepted {
868 user_id: user_3,
869 should_notify: false
870 }
871 ],
872 );
873 }
874}
875
876#[tokio::test(flavor = "multi_thread")]
877async fn test_invite_codes() {
878 let postgres = TestDb::postgres().await;
879 let db = postgres.db();
880 let NewUserResult { user_id: user1, .. } = db
881 .create_user(
882 "user1@example.com",
883 false,
884 NewUserParams {
885 github_login: "user1".into(),
886 github_user_id: 0,
887 invite_count: 0,
888 },
889 )
890 .await
891 .unwrap();
892
893 // Initially, user 1 has no invite code
894 assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
895
896 // Setting invite count to 0 when no code is assigned does not assign a new code
897 db.set_invite_count_for_user(user1, 0).await.unwrap();
898 assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
899
900 // User 1 creates an invite code that can be used twice.
901 db.set_invite_count_for_user(user1, 2).await.unwrap();
902 let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
903 assert_eq!(invite_count, 2);
904
905 // User 2 redeems the invite code and becomes a contact of user 1.
906 let user2_invite = db
907 .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
908 .await
909 .unwrap();
910 let NewUserResult {
911 user_id: user2,
912 inviting_user_id,
913 signup_device_id,
914 metrics_id,
915 } = db
916 .create_user_from_invite(
917 &user2_invite,
918 NewUserParams {
919 github_login: "user2".into(),
920 github_user_id: 2,
921 invite_count: 7,
922 },
923 )
924 .await
925 .unwrap();
926 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
927 assert_eq!(invite_count, 1);
928 assert_eq!(inviting_user_id, Some(user1));
929 assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
930 assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
931 assert_eq!(
932 db.get_contacts(user1).await.unwrap(),
933 [
934 Contact::Accepted {
935 user_id: user1,
936 should_notify: false
937 },
938 Contact::Accepted {
939 user_id: user2,
940 should_notify: true
941 }
942 ]
943 );
944 assert_eq!(
945 db.get_contacts(user2).await.unwrap(),
946 [
947 Contact::Accepted {
948 user_id: user1,
949 should_notify: false
950 },
951 Contact::Accepted {
952 user_id: user2,
953 should_notify: false
954 }
955 ]
956 );
957 assert_eq!(
958 db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
959 7
960 );
961
962 // User 3 redeems the invite code and becomes a contact of user 1.
963 let user3_invite = db
964 .create_invite_from_code(&invite_code, "user3@example.com", None)
965 .await
966 .unwrap();
967 let NewUserResult {
968 user_id: user3,
969 inviting_user_id,
970 signup_device_id,
971 ..
972 } = db
973 .create_user_from_invite(
974 &user3_invite,
975 NewUserParams {
976 github_login: "user-3".into(),
977 github_user_id: 3,
978 invite_count: 3,
979 },
980 )
981 .await
982 .unwrap();
983 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
984 assert_eq!(invite_count, 0);
985 assert_eq!(inviting_user_id, Some(user1));
986 assert!(signup_device_id.is_none());
987 assert_eq!(
988 db.get_contacts(user1).await.unwrap(),
989 [
990 Contact::Accepted {
991 user_id: user1,
992 should_notify: false
993 },
994 Contact::Accepted {
995 user_id: user2,
996 should_notify: true
997 },
998 Contact::Accepted {
999 user_id: user3,
1000 should_notify: true
1001 }
1002 ]
1003 );
1004 assert_eq!(
1005 db.get_contacts(user3).await.unwrap(),
1006 [
1007 Contact::Accepted {
1008 user_id: user1,
1009 should_notify: false
1010 },
1011 Contact::Accepted {
1012 user_id: user3,
1013 should_notify: false
1014 },
1015 ]
1016 );
1017 assert_eq!(
1018 db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
1019 3
1020 );
1021
1022 // Trying to reedem the code for the third time results in an error.
1023 db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
1024 .await
1025 .unwrap_err();
1026
1027 // Invite count can be updated after the code has been created.
1028 db.set_invite_count_for_user(user1, 2).await.unwrap();
1029 let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
1030 assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
1031 assert_eq!(invite_count, 2);
1032
1033 // User 4 can now redeem the invite code and becomes a contact of user 1.
1034 let user4_invite = db
1035 .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
1036 .await
1037 .unwrap();
1038 let user4 = db
1039 .create_user_from_invite(
1040 &user4_invite,
1041 NewUserParams {
1042 github_login: "user-4".into(),
1043 github_user_id: 4,
1044 invite_count: 5,
1045 },
1046 )
1047 .await
1048 .unwrap()
1049 .user_id;
1050
1051 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
1052 assert_eq!(invite_count, 1);
1053 assert_eq!(
1054 db.get_contacts(user1).await.unwrap(),
1055 [
1056 Contact::Accepted {
1057 user_id: user1,
1058 should_notify: false
1059 },
1060 Contact::Accepted {
1061 user_id: user2,
1062 should_notify: true
1063 },
1064 Contact::Accepted {
1065 user_id: user3,
1066 should_notify: true
1067 },
1068 Contact::Accepted {
1069 user_id: user4,
1070 should_notify: true
1071 }
1072 ]
1073 );
1074 assert_eq!(
1075 db.get_contacts(user4).await.unwrap(),
1076 [
1077 Contact::Accepted {
1078 user_id: user1,
1079 should_notify: false
1080 },
1081 Contact::Accepted {
1082 user_id: user4,
1083 should_notify: false
1084 },
1085 ]
1086 );
1087 assert_eq!(
1088 db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
1089 5
1090 );
1091
1092 // An existing user cannot redeem invite codes.
1093 db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
1094 .await
1095 .unwrap_err();
1096 let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
1097 assert_eq!(invite_count, 1);
1098}
1099
1100#[tokio::test(flavor = "multi_thread")]
1101async fn test_signups() {
1102 let postgres = TestDb::postgres().await;
1103 let db = postgres.db();
1104
1105 // people sign up on the waitlist
1106 for i in 0..8 {
1107 db.create_signup(Signup {
1108 email_address: format!("person-{i}@example.com"),
1109 platform_mac: true,
1110 platform_linux: i % 2 == 0,
1111 platform_windows: i % 4 == 0,
1112 editor_features: vec!["speed".into()],
1113 programming_languages: vec!["rust".into(), "c".into()],
1114 device_id: Some(format!("device_id_{i}")),
1115 })
1116 .await
1117 .unwrap();
1118 }
1119
1120 assert_eq!(
1121 db.get_waitlist_summary().await.unwrap(),
1122 WaitlistSummary {
1123 count: 8,
1124 mac_count: 8,
1125 linux_count: 4,
1126 windows_count: 2,
1127 }
1128 );
1129
1130 // retrieve the next batch of signup emails to send
1131 let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1132 let addresses = signups_batch1
1133 .iter()
1134 .map(|s| &s.email_address)
1135 .collect::<Vec<_>>();
1136 assert_eq!(
1137 addresses,
1138 &[
1139 "person-0@example.com",
1140 "person-1@example.com",
1141 "person-2@example.com"
1142 ]
1143 );
1144 assert_ne!(
1145 signups_batch1[0].email_confirmation_code,
1146 signups_batch1[1].email_confirmation_code
1147 );
1148
1149 // the waitlist isn't updated until we record that the emails
1150 // were successfully sent.
1151 let signups_batch = db.get_unsent_invites(3).await.unwrap();
1152 assert_eq!(signups_batch, signups_batch1);
1153
1154 // once the emails go out, we can retrieve the next batch
1155 // of signups.
1156 db.record_sent_invites(&signups_batch1).await.unwrap();
1157 let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1158 let addresses = signups_batch2
1159 .iter()
1160 .map(|s| &s.email_address)
1161 .collect::<Vec<_>>();
1162 assert_eq!(
1163 addresses,
1164 &[
1165 "person-3@example.com",
1166 "person-4@example.com",
1167 "person-5@example.com"
1168 ]
1169 );
1170
1171 // the sent invites are excluded from the summary.
1172 assert_eq!(
1173 db.get_waitlist_summary().await.unwrap(),
1174 WaitlistSummary {
1175 count: 5,
1176 mac_count: 5,
1177 linux_count: 2,
1178 windows_count: 1,
1179 }
1180 );
1181
1182 // user completes the signup process by providing their
1183 // github account.
1184 let NewUserResult {
1185 user_id,
1186 inviting_user_id,
1187 signup_device_id,
1188 ..
1189 } = db
1190 .create_user_from_invite(
1191 &Invite {
1192 email_address: signups_batch1[0].email_address.clone(),
1193 email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1194 },
1195 NewUserParams {
1196 github_login: "person-0".into(),
1197 github_user_id: 0,
1198 invite_count: 5,
1199 },
1200 )
1201 .await
1202 .unwrap();
1203 let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1204 assert!(inviting_user_id.is_none());
1205 assert_eq!(user.github_login, "person-0");
1206 assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
1207 assert_eq!(user.invite_count, 5);
1208 assert_eq!(signup_device_id.unwrap(), "device_id_0");
1209
1210 // cannot redeem the same signup again.
1211 db.create_user_from_invite(
1212 &Invite {
1213 email_address: signups_batch1[0].email_address.clone(),
1214 email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1215 },
1216 NewUserParams {
1217 github_login: "some-other-github_account".into(),
1218 github_user_id: 1,
1219 invite_count: 5,
1220 },
1221 )
1222 .await
1223 .unwrap_err();
1224
1225 // cannot redeem a signup with the wrong confirmation code.
1226 db.create_user_from_invite(
1227 &Invite {
1228 email_address: signups_batch1[1].email_address.clone(),
1229 email_confirmation_code: "the-wrong-code".to_string(),
1230 },
1231 NewUserParams {
1232 github_login: "person-1".into(),
1233 github_user_id: 2,
1234 invite_count: 5,
1235 },
1236 )
1237 .await
1238 .unwrap_err();
1239}
1240
1241#[tokio::test(flavor = "multi_thread")]
1242async fn test_metrics_id() {
1243 let postgres = TestDb::postgres().await;
1244 let db = postgres.db();
1245
1246 let NewUserResult {
1247 user_id: user1,
1248 metrics_id: metrics_id1,
1249 ..
1250 } = db
1251 .create_user(
1252 "person1@example.com",
1253 false,
1254 NewUserParams {
1255 github_login: "person1".into(),
1256 github_user_id: 101,
1257 invite_count: 5,
1258 },
1259 )
1260 .await
1261 .unwrap();
1262 let NewUserResult {
1263 user_id: user2,
1264 metrics_id: metrics_id2,
1265 ..
1266 } = db
1267 .create_user(
1268 "person2@example.com",
1269 false,
1270 NewUserParams {
1271 github_login: "person2".into(),
1272 github_user_id: 102,
1273 invite_count: 5,
1274 },
1275 )
1276 .await
1277 .unwrap();
1278
1279 assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
1280 assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
1281 assert_eq!(metrics_id1.len(), 36);
1282 assert_eq!(metrics_id2.len(), 36);
1283 assert_ne!(metrics_id1, metrics_id2);
1284}
1285
1286fn build_background_executor() -> Arc<Background> {
1287 Deterministic::new(0).build_background()
1288}