tests.rs

  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}