db_tests.rs

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