db_tests.rs

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