1use super::*;
2use crate::test_both_dbs;
3use chrono::Utc;
4use pretty_assertions::assert_eq;
5use std::sync::Arc;
6
7test_both_dbs!(
8 test_get_users,
9 test_get_users_by_ids_postgres,
10 test_get_users_by_ids_sqlite
11);
12
13async fn test_get_users(db: &Arc<Database>) {
14 let mut user_ids = Vec::new();
15 for i in 1..=4 {
16 let user = db
17 .create_user(
18 &format!("user{i}@example.com"),
19 None,
20 false,
21 NewUserParams {
22 github_login: format!("user{i}"),
23 github_user_id: i,
24 },
25 )
26 .await
27 .unwrap();
28 user_ids.push(user.user_id);
29 }
30
31 assert_eq!(
32 db.get_users_by_ids(user_ids.clone())
33 .await
34 .unwrap()
35 .into_iter()
36 .map(|user| (
37 user.id,
38 user.github_login,
39 user.github_user_id,
40 user.email_address
41 ))
42 .collect::<Vec<_>>(),
43 vec![
44 (
45 user_ids[0],
46 "user1".to_string(),
47 1,
48 Some("user1@example.com".to_string()),
49 ),
50 (
51 user_ids[1],
52 "user2".to_string(),
53 2,
54 Some("user2@example.com".to_string()),
55 ),
56 (
57 user_ids[2],
58 "user3".to_string(),
59 3,
60 Some("user3@example.com".to_string()),
61 ),
62 (
63 user_ids[3],
64 "user4".to_string(),
65 4,
66 Some("user4@example.com".to_string()),
67 )
68 ]
69 );
70}
71
72test_both_dbs!(
73 test_update_or_create_user_by_github_account,
74 test_update_or_create_user_by_github_account_postgres,
75 test_update_or_create_user_by_github_account_sqlite
76);
77
78async fn test_update_or_create_user_by_github_account(db: &Arc<Database>) {
79 db.create_user(
80 "user1@example.com",
81 None,
82 false,
83 NewUserParams {
84 github_login: "login1".into(),
85 github_user_id: 101,
86 },
87 )
88 .await
89 .unwrap();
90 let user_id2 = db
91 .create_user(
92 "user2@example.com",
93 None,
94 false,
95 NewUserParams {
96 github_login: "login2".into(),
97 github_user_id: 102,
98 },
99 )
100 .await
101 .unwrap()
102 .user_id;
103
104 let user = db
105 .update_or_create_user_by_github_account(
106 "the-new-login2",
107 102,
108 None,
109 None,
110 Utc::now(),
111 None,
112 )
113 .await
114 .unwrap();
115 assert_eq!(user.id, user_id2);
116 assert_eq!(&user.github_login, "the-new-login2");
117 assert_eq!(user.github_user_id, 102);
118
119 let user = db
120 .update_or_create_user_by_github_account(
121 "login3",
122 103,
123 Some("user3@example.com"),
124 None,
125 Utc::now(),
126 None,
127 )
128 .await
129 .unwrap();
130 assert_eq!(&user.github_login, "login3");
131 assert_eq!(user.github_user_id, 103);
132 assert_eq!(user.email_address, Some("user3@example.com".into()));
133}
134
135test_both_dbs!(
136 test_create_access_tokens,
137 test_create_access_tokens_postgres,
138 test_create_access_tokens_sqlite
139);
140
141async fn test_create_access_tokens(db: &Arc<Database>) {
142 let user_1 = db
143 .create_user(
144 "u1@example.com",
145 None,
146 false,
147 NewUserParams {
148 github_login: "u1".into(),
149 github_user_id: 1,
150 },
151 )
152 .await
153 .unwrap()
154 .user_id;
155 let user_2 = db
156 .create_user(
157 "u2@example.com",
158 None,
159 false,
160 NewUserParams {
161 github_login: "u2".into(),
162 github_user_id: 2,
163 },
164 )
165 .await
166 .unwrap()
167 .user_id;
168
169 let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap();
170 let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap();
171 assert_eq!(
172 db.get_access_token(token_1).await.unwrap(),
173 access_token::Model {
174 id: token_1,
175 user_id: user_1,
176 impersonated_user_id: None,
177 hash: "h1".into(),
178 }
179 );
180 assert_eq!(
181 db.get_access_token(token_2).await.unwrap(),
182 access_token::Model {
183 id: token_2,
184 user_id: user_1,
185 impersonated_user_id: None,
186 hash: "h2".into()
187 }
188 );
189
190 let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap();
191 assert_eq!(
192 db.get_access_token(token_3).await.unwrap(),
193 access_token::Model {
194 id: token_3,
195 user_id: user_1,
196 impersonated_user_id: None,
197 hash: "h3".into()
198 }
199 );
200 assert_eq!(
201 db.get_access_token(token_2).await.unwrap(),
202 access_token::Model {
203 id: token_2,
204 user_id: user_1,
205 impersonated_user_id: None,
206 hash: "h2".into()
207 }
208 );
209 assert!(db.get_access_token(token_1).await.is_err());
210
211 let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap();
212 assert_eq!(
213 db.get_access_token(token_4).await.unwrap(),
214 access_token::Model {
215 id: token_4,
216 user_id: user_1,
217 impersonated_user_id: None,
218 hash: "h4".into()
219 }
220 );
221 assert_eq!(
222 db.get_access_token(token_3).await.unwrap(),
223 access_token::Model {
224 id: token_3,
225 user_id: user_1,
226 impersonated_user_id: None,
227 hash: "h3".into()
228 }
229 );
230 assert!(db.get_access_token(token_2).await.is_err());
231 assert!(db.get_access_token(token_1).await.is_err());
232
233 // An access token for user 2 impersonating user 1 does not
234 // count against user 1's access token limit (of 2).
235 let token_5 = db
236 .create_access_token(user_2, Some(user_1), "h5", 2)
237 .await
238 .unwrap();
239 assert_eq!(
240 db.get_access_token(token_5).await.unwrap(),
241 access_token::Model {
242 id: token_5,
243 user_id: user_2,
244 impersonated_user_id: Some(user_1),
245 hash: "h5".into()
246 }
247 );
248 assert_eq!(
249 db.get_access_token(token_3).await.unwrap(),
250 access_token::Model {
251 id: token_3,
252 user_id: user_1,
253 impersonated_user_id: None,
254 hash: "h3".into()
255 }
256 );
257
258 // Only a limited number (2) of access tokens are stored for user 2
259 // impersonating other users.
260 let token_6 = db
261 .create_access_token(user_2, Some(user_1), "h6", 2)
262 .await
263 .unwrap();
264 let token_7 = db
265 .create_access_token(user_2, Some(user_1), "h7", 2)
266 .await
267 .unwrap();
268 assert_eq!(
269 db.get_access_token(token_6).await.unwrap(),
270 access_token::Model {
271 id: token_6,
272 user_id: user_2,
273 impersonated_user_id: Some(user_1),
274 hash: "h6".into()
275 }
276 );
277 assert_eq!(
278 db.get_access_token(token_7).await.unwrap(),
279 access_token::Model {
280 id: token_7,
281 user_id: user_2,
282 impersonated_user_id: Some(user_1),
283 hash: "h7".into()
284 }
285 );
286 assert!(db.get_access_token(token_5).await.is_err());
287 assert_eq!(
288 db.get_access_token(token_3).await.unwrap(),
289 access_token::Model {
290 id: token_3,
291 user_id: user_1,
292 impersonated_user_id: None,
293 hash: "h3".into()
294 }
295 );
296}
297
298test_both_dbs!(
299 test_add_contacts,
300 test_add_contacts_postgres,
301 test_add_contacts_sqlite
302);
303
304async fn test_add_contacts(db: &Arc<Database>) {
305 let mut user_ids = Vec::new();
306 for i in 0..3 {
307 user_ids.push(
308 db.create_user(
309 &format!("user{i}@example.com"),
310 None,
311 false,
312 NewUserParams {
313 github_login: format!("user{i}"),
314 github_user_id: i,
315 },
316 )
317 .await
318 .unwrap()
319 .user_id,
320 );
321 }
322
323 let user_1 = user_ids[0];
324 let user_2 = user_ids[1];
325 let user_3 = user_ids[2];
326
327 // User starts with no contacts
328 assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
329
330 // User requests a contact. Both users see the pending request.
331 db.send_contact_request(user_1, user_2).await.unwrap();
332 assert!(!db.has_contact(user_1, user_2).await.unwrap());
333 assert!(!db.has_contact(user_2, user_1).await.unwrap());
334 assert_eq!(
335 db.get_contacts(user_1).await.unwrap(),
336 &[Contact::Outgoing { user_id: user_2 }],
337 );
338 assert_eq!(
339 db.get_contacts(user_2).await.unwrap(),
340 &[Contact::Incoming { user_id: user_1 }]
341 );
342
343 // User 2 dismisses the contact request notification without accepting or rejecting.
344 // We shouldn't notify them again.
345 db.dismiss_contact_notification(user_1, user_2)
346 .await
347 .unwrap_err();
348 db.dismiss_contact_notification(user_2, user_1)
349 .await
350 .unwrap();
351 assert_eq!(
352 db.get_contacts(user_2).await.unwrap(),
353 &[Contact::Incoming { user_id: user_1 }]
354 );
355
356 // User can't accept their own contact request
357 db.respond_to_contact_request(user_1, user_2, true)
358 .await
359 .unwrap_err();
360
361 // User accepts a contact request. Both users see the contact.
362 db.respond_to_contact_request(user_2, user_1, true)
363 .await
364 .unwrap();
365 assert_eq!(
366 db.get_contacts(user_1).await.unwrap(),
367 &[Contact::Accepted {
368 user_id: user_2,
369 busy: false,
370 }],
371 );
372 assert!(db.has_contact(user_1, user_2).await.unwrap());
373 assert!(db.has_contact(user_2, user_1).await.unwrap());
374 assert_eq!(
375 db.get_contacts(user_2).await.unwrap(),
376 &[Contact::Accepted {
377 user_id: user_1,
378 busy: false,
379 }]
380 );
381
382 // Users cannot re-request existing contacts.
383 db.send_contact_request(user_1, user_2).await.unwrap_err();
384 db.send_contact_request(user_2, user_1).await.unwrap_err();
385
386 // Users can't dismiss notifications of them accepting other users' requests.
387 db.dismiss_contact_notification(user_2, user_1)
388 .await
389 .unwrap_err();
390 assert_eq!(
391 db.get_contacts(user_1).await.unwrap(),
392 &[Contact::Accepted {
393 user_id: user_2,
394 busy: false,
395 }]
396 );
397
398 // Users can dismiss notifications of other users accepting their requests.
399 db.dismiss_contact_notification(user_1, user_2)
400 .await
401 .unwrap();
402 assert_eq!(
403 db.get_contacts(user_1).await.unwrap(),
404 &[Contact::Accepted {
405 user_id: user_2,
406 busy: false,
407 }]
408 );
409
410 // Users send each other concurrent contact requests and
411 // see that they are immediately accepted.
412 db.send_contact_request(user_1, user_3).await.unwrap();
413 db.send_contact_request(user_3, user_1).await.unwrap();
414 assert_eq!(
415 db.get_contacts(user_1).await.unwrap(),
416 &[
417 Contact::Accepted {
418 user_id: user_2,
419 busy: false,
420 },
421 Contact::Accepted {
422 user_id: user_3,
423 busy: false,
424 }
425 ]
426 );
427 assert_eq!(
428 db.get_contacts(user_3).await.unwrap(),
429 &[Contact::Accepted {
430 user_id: user_1,
431 busy: false,
432 }],
433 );
434
435 // User declines a contact request. Both users see that it is gone.
436 db.send_contact_request(user_2, user_3).await.unwrap();
437 db.respond_to_contact_request(user_3, user_2, false)
438 .await
439 .unwrap();
440 assert!(!db.has_contact(user_2, user_3).await.unwrap());
441 assert!(!db.has_contact(user_3, user_2).await.unwrap());
442 assert_eq!(
443 db.get_contacts(user_2).await.unwrap(),
444 &[Contact::Accepted {
445 user_id: user_1,
446 busy: false,
447 }]
448 );
449 assert_eq!(
450 db.get_contacts(user_3).await.unwrap(),
451 &[Contact::Accepted {
452 user_id: user_1,
453 busy: false,
454 }],
455 );
456}
457
458test_both_dbs!(
459 test_project_count,
460 test_project_count_postgres,
461 test_project_count_sqlite
462);
463
464async fn test_project_count(db: &Arc<Database>) {
465 let owner_id = db.create_server("test").await.unwrap().0 as u32;
466
467 let user1 = db
468 .create_user(
469 "admin@example.com",
470 None,
471 true,
472 NewUserParams {
473 github_login: "admin".into(),
474 github_user_id: 0,
475 },
476 )
477 .await
478 .unwrap();
479 let user2 = db
480 .create_user(
481 "user@example.com",
482 None,
483 false,
484 NewUserParams {
485 github_login: "user".into(),
486 github_user_id: 1,
487 },
488 )
489 .await
490 .unwrap();
491
492 let room_id = RoomId::from_proto(
493 db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
494 .await
495 .unwrap()
496 .id,
497 );
498 db.call(
499 room_id,
500 user1.user_id,
501 ConnectionId { owner_id, id: 0 },
502 user2.user_id,
503 None,
504 )
505 .await
506 .unwrap();
507 db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
508 .await
509 .unwrap();
510 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
511
512 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
513 .await
514 .unwrap();
515 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
516
517 db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
518 .await
519 .unwrap();
520 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
521
522 // Projects shared by admins aren't counted.
523 db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
524 .await
525 .unwrap();
526 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
527
528 db.leave_room(ConnectionId { owner_id, id: 1 })
529 .await
530 .unwrap();
531 assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
532}
533
534#[test]
535fn test_fuzzy_like_string() {
536 assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
537 assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
538 assert_eq!(Database::fuzzy_like_string(" z "), "%z%");
539}
540
541#[cfg(target_os = "macos")]
542#[gpui::test]
543async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
544 let test_db = tests::TestDb::postgres(cx.executor());
545 let db = test_db.db();
546 for (i, github_login) in [
547 "California",
548 "colorado",
549 "oregon",
550 "washington",
551 "florida",
552 "delaware",
553 "rhode-island",
554 ]
555 .into_iter()
556 .enumerate()
557 {
558 db.create_user(
559 &format!("{github_login}@example.com"),
560 None,
561 false,
562 NewUserParams {
563 github_login: github_login.into(),
564 github_user_id: i as i32,
565 },
566 )
567 .await
568 .unwrap();
569 }
570
571 assert_eq!(
572 fuzzy_search_user_names(db, "clr").await,
573 &["colorado", "California"]
574 );
575 assert_eq!(
576 fuzzy_search_user_names(db, "ro").await,
577 &["rhode-island", "colorado", "oregon"],
578 );
579
580 async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
581 db.fuzzy_search_users(query, 10)
582 .await
583 .unwrap()
584 .into_iter()
585 .map(|user| user.github_login)
586 .collect::<Vec<_>>()
587 }
588}
589
590test_both_dbs!(
591 test_upsert_shared_thread,
592 test_upsert_shared_thread_postgres,
593 test_upsert_shared_thread_sqlite
594);
595
596async fn test_upsert_shared_thread(db: &Arc<Database>) {
597 use crate::db::SharedThreadId;
598 use uuid::Uuid;
599
600 let user_id = new_test_user(db, "user1@example.com").await;
601
602 let thread_id = SharedThreadId(Uuid::new_v4());
603 let title = "My Test Thread";
604 let data = b"test thread data".to_vec();
605
606 db.upsert_shared_thread(thread_id, user_id, title, data.clone())
607 .await
608 .unwrap();
609
610 let result = db.get_shared_thread(thread_id).await.unwrap();
611 assert!(result.is_some(), "Should find the shared thread");
612
613 let (thread, username) = result.unwrap();
614 assert_eq!(thread.title, title);
615 assert_eq!(thread.data, data);
616 assert_eq!(thread.user_id, user_id);
617 assert_eq!(username, "user1");
618}
619
620test_both_dbs!(
621 test_upsert_shared_thread_updates_existing,
622 test_upsert_shared_thread_updates_existing_postgres,
623 test_upsert_shared_thread_updates_existing_sqlite
624);
625
626async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
627 use crate::db::SharedThreadId;
628 use uuid::Uuid;
629
630 let user_id = new_test_user(db, "user1@example.com").await;
631
632 let thread_id = SharedThreadId(Uuid::new_v4());
633
634 // Create initial thread.
635 db.upsert_shared_thread(
636 thread_id,
637 user_id,
638 "Original Title",
639 b"original data".to_vec(),
640 )
641 .await
642 .unwrap();
643
644 // Update the same thread.
645 db.upsert_shared_thread(
646 thread_id,
647 user_id,
648 "Updated Title",
649 b"updated data".to_vec(),
650 )
651 .await
652 .unwrap();
653
654 let result = db.get_shared_thread(thread_id).await.unwrap();
655 let (thread, _) = result.unwrap();
656
657 assert_eq!(thread.title, "Updated Title");
658 assert_eq!(thread.data, b"updated data".to_vec());
659}
660
661test_both_dbs!(
662 test_cannot_update_another_users_shared_thread,
663 test_cannot_update_another_users_shared_thread_postgres,
664 test_cannot_update_another_users_shared_thread_sqlite
665);
666
667async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
668 use crate::db::SharedThreadId;
669 use uuid::Uuid;
670
671 let user1_id = new_test_user(db, "user1@example.com").await;
672 let user2_id = new_test_user(db, "user2@example.com").await;
673
674 let thread_id = SharedThreadId(Uuid::new_v4());
675
676 db.upsert_shared_thread(thread_id, user1_id, "User 1 Thread", b"user1 data".to_vec())
677 .await
678 .unwrap();
679
680 let result = db
681 .upsert_shared_thread(thread_id, user2_id, "User 2 Title", b"user2 data".to_vec())
682 .await;
683
684 assert!(
685 result.is_err(),
686 "Should not allow updating another user's thread"
687 );
688}
689
690test_both_dbs!(
691 test_get_nonexistent_shared_thread,
692 test_get_nonexistent_shared_thread_postgres,
693 test_get_nonexistent_shared_thread_sqlite
694);
695
696async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
697 use crate::db::SharedThreadId;
698 use uuid::Uuid;
699
700 let result = db
701 .get_shared_thread(SharedThreadId(Uuid::new_v4()))
702 .await
703 .unwrap();
704
705 assert!(result.is_none(), "Should not find non-existent thread");
706}