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(
354        room_id,
355        ConnectionId { owner_id, id: 1 },
356        &[],
357        false,
358        false,
359        &[],
360    )
361    .await
362    .unwrap();
363    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
364
365    db.share_project(
366        room_id,
367        ConnectionId { owner_id, id: 1 },
368        &[],
369        false,
370        false,
371        &[],
372    )
373    .await
374    .unwrap();
375    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
376
377    // Projects shared by admins aren't counted.
378    db.share_project(
379        room_id,
380        ConnectionId { owner_id, id: 0 },
381        &[],
382        false,
383        false,
384        &[],
385    )
386    .await
387    .unwrap();
388    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
389
390    db.leave_room(ConnectionId { owner_id, id: 1 })
391        .await
392        .unwrap();
393    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
394}
395
396#[test]
397fn test_fuzzy_like_string() {
398    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
399    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
400    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
401}
402
403#[gpui::test]
404async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
405    // In CI, only run postgres tests on Linux (where we have the postgres service).
406    // Locally, always run them (assuming postgres is available).
407    if std::env::var("CI").is_ok() && !cfg!(target_os = "linux") {
408        return;
409    }
410    let test_db = TestDb::postgres(cx.executor());
411    let db = test_db.db();
412    for (i, github_login) in [
413        "California",
414        "colorado",
415        "oregon",
416        "washington",
417        "florida",
418        "delaware",
419        "rhode-island",
420    ]
421    .into_iter()
422    .enumerate()
423    {
424        db.create_user(
425            &format!("{github_login}@example.com"),
426            None,
427            false,
428            NewUserParams {
429                github_login: github_login.into(),
430                github_user_id: i as i32,
431            },
432        )
433        .await
434        .unwrap();
435    }
436
437    assert_eq!(
438        fuzzy_search_user_names(db, "clr").await,
439        &["colorado", "California"]
440    );
441    assert_eq!(
442        fuzzy_search_user_names(db, "ro").await,
443        &["rhode-island", "colorado", "oregon"],
444    );
445
446    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
447        db.fuzzy_search_users(query, 10)
448            .await
449            .unwrap()
450            .into_iter()
451            .map(|user| user.github_login)
452            .collect::<Vec<_>>()
453    }
454}
455
456test_both_dbs!(
457    test_upsert_shared_thread,
458    test_upsert_shared_thread_postgres,
459    test_upsert_shared_thread_sqlite
460);
461
462async fn test_upsert_shared_thread(db: &Arc<Database>) {
463    use collab::db::SharedThreadId;
464    use uuid::Uuid;
465
466    let user_id = new_test_user(db, "user1@example.com").await;
467
468    let thread_id = SharedThreadId(Uuid::new_v4());
469    let title = "My Test Thread";
470    let data = b"test thread data".to_vec();
471
472    db.upsert_shared_thread(thread_id, user_id, title, data.clone())
473        .await
474        .unwrap();
475
476    let result = db.get_shared_thread(thread_id).await.unwrap();
477    assert!(result.is_some(), "Should find the shared thread");
478
479    let (thread, username) = result.unwrap();
480    assert_eq!(thread.title, title);
481    assert_eq!(thread.data, data);
482    assert_eq!(thread.user_id, user_id);
483    assert_eq!(username, "user1");
484}
485
486test_both_dbs!(
487    test_upsert_shared_thread_updates_existing,
488    test_upsert_shared_thread_updates_existing_postgres,
489    test_upsert_shared_thread_updates_existing_sqlite
490);
491
492async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
493    use collab::db::SharedThreadId;
494    use uuid::Uuid;
495
496    let user_id = new_test_user(db, "user1@example.com").await;
497
498    let thread_id = SharedThreadId(Uuid::new_v4());
499
500    // Create initial thread.
501    db.upsert_shared_thread(
502        thread_id,
503        user_id,
504        "Original Title",
505        b"original data".to_vec(),
506    )
507    .await
508    .unwrap();
509
510    // Update the same thread.
511    db.upsert_shared_thread(
512        thread_id,
513        user_id,
514        "Updated Title",
515        b"updated data".to_vec(),
516    )
517    .await
518    .unwrap();
519
520    let result = db.get_shared_thread(thread_id).await.unwrap();
521    let (thread, _) = result.unwrap();
522
523    assert_eq!(thread.title, "Updated Title");
524    assert_eq!(thread.data, b"updated data".to_vec());
525}
526
527test_both_dbs!(
528    test_cannot_update_another_users_shared_thread,
529    test_cannot_update_another_users_shared_thread_postgres,
530    test_cannot_update_another_users_shared_thread_sqlite
531);
532
533async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
534    use collab::db::SharedThreadId;
535    use uuid::Uuid;
536
537    let user1_id = new_test_user(db, "user1@example.com").await;
538    let user2_id = new_test_user(db, "user2@example.com").await;
539
540    let thread_id = SharedThreadId(Uuid::new_v4());
541
542    db.upsert_shared_thread(thread_id, user1_id, "User 1 Thread", b"user1 data".to_vec())
543        .await
544        .unwrap();
545
546    let result = db
547        .upsert_shared_thread(thread_id, user2_id, "User 2 Title", b"user2 data".to_vec())
548        .await;
549
550    assert!(
551        result.is_err(),
552        "Should not allow updating another user's thread"
553    );
554}
555
556test_both_dbs!(
557    test_get_nonexistent_shared_thread,
558    test_get_nonexistent_shared_thread_postgres,
559    test_get_nonexistent_shared_thread_sqlite
560);
561
562async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
563    use collab::db::SharedThreadId;
564    use uuid::Uuid;
565
566    let result = db
567        .get_shared_thread(SharedThreadId(Uuid::new_v4()))
568        .await
569        .unwrap();
570
571    assert!(result.is_none(), "Should not find non-existent thread");
572}