db_tests.rs

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