db_tests.rs

  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}