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_add_contacts,
141    test_add_contacts_postgres,
142    test_add_contacts_sqlite
143);
144
145async fn test_add_contacts(db: &Arc<Database>) {
146    let mut user_ids = Vec::new();
147    for i in 0..3 {
148        user_ids.push(
149            db.create_user(
150                &format!("user{i}@example.com"),
151                None,
152                false,
153                NewUserParams {
154                    github_login: format!("user{i}"),
155                    github_user_id: i,
156                },
157            )
158            .await
159            .unwrap()
160            .user_id,
161        );
162    }
163
164    let user_1 = user_ids[0];
165    let user_2 = user_ids[1];
166    let user_3 = user_ids[2];
167
168    // User starts with no contacts
169    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
170
171    // User requests a contact. Both users see the pending request.
172    db.send_contact_request(user_1, user_2).await.unwrap();
173    assert!(!db.has_contact(user_1, user_2).await.unwrap());
174    assert!(!db.has_contact(user_2, user_1).await.unwrap());
175    assert_eq!(
176        db.get_contacts(user_1).await.unwrap(),
177        &[Contact::Outgoing { user_id: user_2 }],
178    );
179    assert_eq!(
180        db.get_contacts(user_2).await.unwrap(),
181        &[Contact::Incoming { user_id: user_1 }]
182    );
183
184    // User 2 dismisses the contact request notification without accepting or rejecting.
185    // We shouldn't notify them again.
186    db.dismiss_contact_notification(user_1, user_2)
187        .await
188        .unwrap_err();
189    db.dismiss_contact_notification(user_2, user_1)
190        .await
191        .unwrap();
192    assert_eq!(
193        db.get_contacts(user_2).await.unwrap(),
194        &[Contact::Incoming { user_id: user_1 }]
195    );
196
197    // User can't accept their own contact request
198    db.respond_to_contact_request(user_1, user_2, true)
199        .await
200        .unwrap_err();
201
202    // User accepts a contact request. Both users see the contact.
203    db.respond_to_contact_request(user_2, user_1, true)
204        .await
205        .unwrap();
206    assert_eq!(
207        db.get_contacts(user_1).await.unwrap(),
208        &[Contact::Accepted {
209            user_id: user_2,
210            busy: false,
211        }],
212    );
213    assert!(db.has_contact(user_1, user_2).await.unwrap());
214    assert!(db.has_contact(user_2, user_1).await.unwrap());
215    assert_eq!(
216        db.get_contacts(user_2).await.unwrap(),
217        &[Contact::Accepted {
218            user_id: user_1,
219            busy: false,
220        }]
221    );
222
223    // Users cannot re-request existing contacts.
224    db.send_contact_request(user_1, user_2).await.unwrap_err();
225    db.send_contact_request(user_2, user_1).await.unwrap_err();
226
227    // Users can't dismiss notifications of them accepting other users' requests.
228    db.dismiss_contact_notification(user_2, user_1)
229        .await
230        .unwrap_err();
231    assert_eq!(
232        db.get_contacts(user_1).await.unwrap(),
233        &[Contact::Accepted {
234            user_id: user_2,
235            busy: false,
236        }]
237    );
238
239    // Users can dismiss notifications of other users accepting their requests.
240    db.dismiss_contact_notification(user_1, user_2)
241        .await
242        .unwrap();
243    assert_eq!(
244        db.get_contacts(user_1).await.unwrap(),
245        &[Contact::Accepted {
246            user_id: user_2,
247            busy: false,
248        }]
249    );
250
251    // Users send each other concurrent contact requests and
252    // see that they are immediately accepted.
253    db.send_contact_request(user_1, user_3).await.unwrap();
254    db.send_contact_request(user_3, user_1).await.unwrap();
255    assert_eq!(
256        db.get_contacts(user_1).await.unwrap(),
257        &[
258            Contact::Accepted {
259                user_id: user_2,
260                busy: false,
261            },
262            Contact::Accepted {
263                user_id: user_3,
264                busy: false,
265            }
266        ]
267    );
268    assert_eq!(
269        db.get_contacts(user_3).await.unwrap(),
270        &[Contact::Accepted {
271            user_id: user_1,
272            busy: false,
273        }],
274    );
275
276    // User declines a contact request. Both users see that it is gone.
277    db.send_contact_request(user_2, user_3).await.unwrap();
278    db.respond_to_contact_request(user_3, user_2, false)
279        .await
280        .unwrap();
281    assert!(!db.has_contact(user_2, user_3).await.unwrap());
282    assert!(!db.has_contact(user_3, user_2).await.unwrap());
283    assert_eq!(
284        db.get_contacts(user_2).await.unwrap(),
285        &[Contact::Accepted {
286            user_id: user_1,
287            busy: false,
288        }]
289    );
290    assert_eq!(
291        db.get_contacts(user_3).await.unwrap(),
292        &[Contact::Accepted {
293            user_id: user_1,
294            busy: false,
295        }],
296    );
297}
298
299test_both_dbs!(
300    test_project_count,
301    test_project_count_postgres,
302    test_project_count_sqlite
303);
304
305async fn test_project_count(db: &Arc<Database>) {
306    let owner_id = db.create_server("test").await.unwrap().0 as u32;
307
308    let user1 = db
309        .create_user(
310            "admin@example.com",
311            None,
312            true,
313            NewUserParams {
314                github_login: "admin".into(),
315                github_user_id: 0,
316            },
317        )
318        .await
319        .unwrap();
320    let user2 = db
321        .create_user(
322            "user@example.com",
323            None,
324            false,
325            NewUserParams {
326                github_login: "user".into(),
327                github_user_id: 1,
328            },
329        )
330        .await
331        .unwrap();
332
333    let room_id = RoomId::from_proto(
334        db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
335            .await
336            .unwrap()
337            .id,
338    );
339    db.call(
340        room_id,
341        user1.user_id,
342        ConnectionId { owner_id, id: 0 },
343        user2.user_id,
344        None,
345    )
346    .await
347    .unwrap();
348    db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
349        .await
350        .unwrap();
351    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
352
353    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
354        .await
355        .unwrap();
356    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
357
358    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
359        .await
360        .unwrap();
361    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
362
363    // Projects shared by admins aren't counted.
364    db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
365        .await
366        .unwrap();
367    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
368
369    db.leave_room(ConnectionId { owner_id, id: 1 })
370        .await
371        .unwrap();
372    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
373}
374
375#[test]
376fn test_fuzzy_like_string() {
377    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
378    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
379    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
380}
381
382#[gpui::test]
383async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
384    // In CI, only run postgres tests on Linux (where we have the postgres service).
385    // Locally, always run them (assuming postgres is available).
386    if std::env::var("CI").is_ok() && !cfg!(target_os = "linux") {
387        return;
388    }
389    let test_db = TestDb::postgres(cx.executor());
390    let db = test_db.db();
391    for (i, github_login) in [
392        "California",
393        "colorado",
394        "oregon",
395        "washington",
396        "florida",
397        "delaware",
398        "rhode-island",
399    ]
400    .into_iter()
401    .enumerate()
402    {
403        db.create_user(
404            &format!("{github_login}@example.com"),
405            None,
406            false,
407            NewUserParams {
408                github_login: github_login.into(),
409                github_user_id: i as i32,
410            },
411        )
412        .await
413        .unwrap();
414    }
415
416    assert_eq!(
417        fuzzy_search_user_names(db, "clr").await,
418        &["colorado", "California"]
419    );
420    assert_eq!(
421        fuzzy_search_user_names(db, "ro").await,
422        &["rhode-island", "colorado", "oregon"],
423    );
424
425    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
426        db.fuzzy_search_users(query, 10)
427            .await
428            .unwrap()
429            .into_iter()
430            .map(|user| user.github_login)
431            .collect::<Vec<_>>()
432    }
433}
434
435test_both_dbs!(
436    test_upsert_shared_thread,
437    test_upsert_shared_thread_postgres,
438    test_upsert_shared_thread_sqlite
439);
440
441async fn test_upsert_shared_thread(db: &Arc<Database>) {
442    use collab::db::SharedThreadId;
443    use uuid::Uuid;
444
445    let user_id = new_test_user(db, "user1@example.com").await;
446
447    let thread_id = SharedThreadId(Uuid::new_v4());
448    let title = "My Test Thread";
449    let data = b"test thread data".to_vec();
450
451    db.upsert_shared_thread(thread_id, user_id, title, data.clone())
452        .await
453        .unwrap();
454
455    let result = db.get_shared_thread(thread_id).await.unwrap();
456    assert!(result.is_some(), "Should find the shared thread");
457
458    let (thread, username) = result.unwrap();
459    assert_eq!(thread.title, title);
460    assert_eq!(thread.data, data);
461    assert_eq!(thread.user_id, user_id);
462    assert_eq!(username, "user1");
463}
464
465test_both_dbs!(
466    test_upsert_shared_thread_updates_existing,
467    test_upsert_shared_thread_updates_existing_postgres,
468    test_upsert_shared_thread_updates_existing_sqlite
469);
470
471async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
472    use collab::db::SharedThreadId;
473    use uuid::Uuid;
474
475    let user_id = new_test_user(db, "user1@example.com").await;
476
477    let thread_id = SharedThreadId(Uuid::new_v4());
478
479    // Create initial thread.
480    db.upsert_shared_thread(
481        thread_id,
482        user_id,
483        "Original Title",
484        b"original data".to_vec(),
485    )
486    .await
487    .unwrap();
488
489    // Update the same thread.
490    db.upsert_shared_thread(
491        thread_id,
492        user_id,
493        "Updated Title",
494        b"updated data".to_vec(),
495    )
496    .await
497    .unwrap();
498
499    let result = db.get_shared_thread(thread_id).await.unwrap();
500    let (thread, _) = result.unwrap();
501
502    assert_eq!(thread.title, "Updated Title");
503    assert_eq!(thread.data, b"updated data".to_vec());
504}
505
506test_both_dbs!(
507    test_cannot_update_another_users_shared_thread,
508    test_cannot_update_another_users_shared_thread_postgres,
509    test_cannot_update_another_users_shared_thread_sqlite
510);
511
512async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
513    use collab::db::SharedThreadId;
514    use uuid::Uuid;
515
516    let user1_id = new_test_user(db, "user1@example.com").await;
517    let user2_id = new_test_user(db, "user2@example.com").await;
518
519    let thread_id = SharedThreadId(Uuid::new_v4());
520
521    db.upsert_shared_thread(thread_id, user1_id, "User 1 Thread", b"user1 data".to_vec())
522        .await
523        .unwrap();
524
525    let result = db
526        .upsert_shared_thread(thread_id, user2_id, "User 2 Title", b"user2 data".to_vec())
527        .await;
528
529    assert!(
530        result.is_err(),
531        "Should not allow updating another user's thread"
532    );
533}
534
535test_both_dbs!(
536    test_get_nonexistent_shared_thread,
537    test_get_nonexistent_shared_thread_postgres,
538    test_get_nonexistent_shared_thread_sqlite
539);
540
541async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
542    use collab::db::SharedThreadId;
543    use uuid::Uuid;
544
545    let result = db
546        .get_shared_thread(SharedThreadId(Uuid::new_v4()))
547        .await
548        .unwrap();
549
550    assert!(result.is_none(), "Should not find non-existent thread");
551}