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