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}