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