db_tests.rs

   1use super::*;
   2use crate::test_both_dbs;
   3use gpui::executor::{Background, Deterministic};
   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                    invite_count: 0,
  26                },
  27            )
  28            .await
  29            .unwrap();
  30        user_ids.push(user.user_id);
  31        user_metric_ids.push(user.metrics_id);
  32    }
  33
  34    assert_eq!(
  35        db.get_users_by_ids(user_ids.clone()).await.unwrap(),
  36        vec![
  37            User {
  38                id: user_ids[0],
  39                github_login: "user1".to_string(),
  40                github_user_id: Some(1),
  41                email_address: Some("user1@example.com".to_string()),
  42                admin: false,
  43                metrics_id: user_metric_ids[0].parse().unwrap(),
  44                ..Default::default()
  45            },
  46            User {
  47                id: user_ids[1],
  48                github_login: "user2".to_string(),
  49                github_user_id: Some(2),
  50                email_address: Some("user2@example.com".to_string()),
  51                admin: false,
  52                metrics_id: user_metric_ids[1].parse().unwrap(),
  53                ..Default::default()
  54            },
  55            User {
  56                id: user_ids[2],
  57                github_login: "user3".to_string(),
  58                github_user_id: Some(3),
  59                email_address: Some("user3@example.com".to_string()),
  60                admin: false,
  61                metrics_id: user_metric_ids[2].parse().unwrap(),
  62                ..Default::default()
  63            },
  64            User {
  65                id: user_ids[3],
  66                github_login: "user4".to_string(),
  67                github_user_id: Some(4),
  68                email_address: Some("user4@example.com".to_string()),
  69                admin: false,
  70                metrics_id: user_metric_ids[3].parse().unwrap(),
  71                ..Default::default()
  72            }
  73        ]
  74    );
  75}
  76
  77test_both_dbs!(
  78    test_get_or_create_user_by_github_account,
  79    test_get_or_create_user_by_github_account_postgres,
  80    test_get_or_create_user_by_github_account_sqlite
  81);
  82
  83async fn test_get_or_create_user_by_github_account(db: &Arc<Database>) {
  84    let user_id1 = db
  85        .create_user(
  86            "user1@example.com",
  87            false,
  88            NewUserParams {
  89                github_login: "login1".into(),
  90                github_user_id: 101,
  91                invite_count: 0,
  92            },
  93        )
  94        .await
  95        .unwrap()
  96        .user_id;
  97    let user_id2 = db
  98        .create_user(
  99            "user2@example.com",
 100            false,
 101            NewUserParams {
 102                github_login: "login2".into(),
 103                github_user_id: 102,
 104                invite_count: 0,
 105            },
 106        )
 107        .await
 108        .unwrap()
 109        .user_id;
 110
 111    let user = db
 112        .get_or_create_user_by_github_account("login1", None, None)
 113        .await
 114        .unwrap()
 115        .unwrap();
 116    assert_eq!(user.id, user_id1);
 117    assert_eq!(&user.github_login, "login1");
 118    assert_eq!(user.github_user_id, Some(101));
 119
 120    assert!(db
 121        .get_or_create_user_by_github_account("non-existent-login", None, None)
 122        .await
 123        .unwrap()
 124        .is_none());
 125
 126    let user = db
 127        .get_or_create_user_by_github_account("the-new-login2", Some(102), None)
 128        .await
 129        .unwrap()
 130        .unwrap();
 131    assert_eq!(user.id, user_id2);
 132    assert_eq!(&user.github_login, "the-new-login2");
 133    assert_eq!(user.github_user_id, Some(102));
 134
 135    let user = db
 136        .get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
 137        .await
 138        .unwrap()
 139        .unwrap();
 140    assert_eq!(&user.github_login, "login3");
 141    assert_eq!(user.github_user_id, Some(103));
 142    assert_eq!(user.email_address, Some("user3@example.com".into()));
 143}
 144
 145test_both_dbs!(
 146    test_create_access_tokens,
 147    test_create_access_tokens_postgres,
 148    test_create_access_tokens_sqlite
 149);
 150
 151async fn test_create_access_tokens(db: &Arc<Database>) {
 152    let user = db
 153        .create_user(
 154            "u1@example.com",
 155            false,
 156            NewUserParams {
 157                github_login: "u1".into(),
 158                github_user_id: 1,
 159                invite_count: 0,
 160            },
 161        )
 162        .await
 163        .unwrap()
 164        .user_id;
 165
 166    let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
 167    let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
 168    assert_eq!(
 169        db.get_access_token(token_1).await.unwrap(),
 170        access_token::Model {
 171            id: token_1,
 172            user_id: user,
 173            hash: "h1".into(),
 174        }
 175    );
 176    assert_eq!(
 177        db.get_access_token(token_2).await.unwrap(),
 178        access_token::Model {
 179            id: token_2,
 180            user_id: user,
 181            hash: "h2".into()
 182        }
 183    );
 184
 185    let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
 186    assert_eq!(
 187        db.get_access_token(token_3).await.unwrap(),
 188        access_token::Model {
 189            id: token_3,
 190            user_id: user,
 191            hash: "h3".into()
 192        }
 193    );
 194    assert_eq!(
 195        db.get_access_token(token_2).await.unwrap(),
 196        access_token::Model {
 197            id: token_2,
 198            user_id: user,
 199            hash: "h2".into()
 200        }
 201    );
 202    assert!(db.get_access_token(token_1).await.is_err());
 203
 204    let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
 205    assert_eq!(
 206        db.get_access_token(token_4).await.unwrap(),
 207        access_token::Model {
 208            id: token_4,
 209            user_id: user,
 210            hash: "h4".into()
 211        }
 212    );
 213    assert_eq!(
 214        db.get_access_token(token_3).await.unwrap(),
 215        access_token::Model {
 216            id: token_3,
 217            user_id: user,
 218            hash: "h3".into()
 219        }
 220    );
 221    assert!(db.get_access_token(token_2).await.is_err());
 222    assert!(db.get_access_token(token_1).await.is_err());
 223}
 224
 225test_both_dbs!(
 226    test_add_contacts,
 227    test_add_contacts_postgres,
 228    test_add_contacts_sqlite
 229);
 230
 231async fn test_add_contacts(db: &Arc<Database>) {
 232    let mut user_ids = Vec::new();
 233    for i in 0..3 {
 234        user_ids.push(
 235            db.create_user(
 236                &format!("user{i}@example.com"),
 237                false,
 238                NewUserParams {
 239                    github_login: format!("user{i}"),
 240                    github_user_id: i,
 241                    invite_count: 0,
 242                },
 243            )
 244            .await
 245            .unwrap()
 246            .user_id,
 247        );
 248    }
 249
 250    let user_1 = user_ids[0];
 251    let user_2 = user_ids[1];
 252    let user_3 = user_ids[2];
 253
 254    // User starts with no contacts
 255    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
 256
 257    // User requests a contact. Both users see the pending request.
 258    db.send_contact_request(user_1, user_2).await.unwrap();
 259    assert!(!db.has_contact(user_1, user_2).await.unwrap());
 260    assert!(!db.has_contact(user_2, user_1).await.unwrap());
 261    assert_eq!(
 262        db.get_contacts(user_1).await.unwrap(),
 263        &[Contact::Outgoing { user_id: user_2 }],
 264    );
 265    assert_eq!(
 266        db.get_contacts(user_2).await.unwrap(),
 267        &[Contact::Incoming {
 268            user_id: user_1,
 269            should_notify: true
 270        }]
 271    );
 272
 273    // User 2 dismisses the contact request notification without accepting or rejecting.
 274    // We shouldn't notify them again.
 275    db.dismiss_contact_notification(user_1, user_2)
 276        .await
 277        .unwrap_err();
 278    db.dismiss_contact_notification(user_2, user_1)
 279        .await
 280        .unwrap();
 281    assert_eq!(
 282        db.get_contacts(user_2).await.unwrap(),
 283        &[Contact::Incoming {
 284            user_id: user_1,
 285            should_notify: false
 286        }]
 287    );
 288
 289    // User can't accept their own contact request
 290    db.respond_to_contact_request(user_1, user_2, true)
 291        .await
 292        .unwrap_err();
 293
 294    // User accepts a contact request. Both users see the contact.
 295    db.respond_to_contact_request(user_2, user_1, true)
 296        .await
 297        .unwrap();
 298    assert_eq!(
 299        db.get_contacts(user_1).await.unwrap(),
 300        &[Contact::Accepted {
 301            user_id: user_2,
 302            should_notify: true,
 303            busy: false,
 304        }],
 305    );
 306    assert!(db.has_contact(user_1, user_2).await.unwrap());
 307    assert!(db.has_contact(user_2, user_1).await.unwrap());
 308    assert_eq!(
 309        db.get_contacts(user_2).await.unwrap(),
 310        &[Contact::Accepted {
 311            user_id: user_1,
 312            should_notify: false,
 313            busy: false,
 314        }]
 315    );
 316
 317    // Users cannot re-request existing contacts.
 318    db.send_contact_request(user_1, user_2).await.unwrap_err();
 319    db.send_contact_request(user_2, user_1).await.unwrap_err();
 320
 321    // Users can't dismiss notifications of them accepting other users' requests.
 322    db.dismiss_contact_notification(user_2, user_1)
 323        .await
 324        .unwrap_err();
 325    assert_eq!(
 326        db.get_contacts(user_1).await.unwrap(),
 327        &[Contact::Accepted {
 328            user_id: user_2,
 329            should_notify: true,
 330            busy: false,
 331        }]
 332    );
 333
 334    // Users can dismiss notifications of other users accepting their requests.
 335    db.dismiss_contact_notification(user_1, user_2)
 336        .await
 337        .unwrap();
 338    assert_eq!(
 339        db.get_contacts(user_1).await.unwrap(),
 340        &[Contact::Accepted {
 341            user_id: user_2,
 342            should_notify: false,
 343            busy: false,
 344        }]
 345    );
 346
 347    // Users send each other concurrent contact requests and
 348    // see that they are immediately accepted.
 349    db.send_contact_request(user_1, user_3).await.unwrap();
 350    db.send_contact_request(user_3, user_1).await.unwrap();
 351    assert_eq!(
 352        db.get_contacts(user_1).await.unwrap(),
 353        &[
 354            Contact::Accepted {
 355                user_id: user_2,
 356                should_notify: false,
 357                busy: false,
 358            },
 359            Contact::Accepted {
 360                user_id: user_3,
 361                should_notify: false,
 362                busy: false,
 363            }
 364        ]
 365    );
 366    assert_eq!(
 367        db.get_contacts(user_3).await.unwrap(),
 368        &[Contact::Accepted {
 369            user_id: user_1,
 370            should_notify: false,
 371            busy: false,
 372        }],
 373    );
 374
 375    // User declines a contact request. Both users see that it is gone.
 376    db.send_contact_request(user_2, user_3).await.unwrap();
 377    db.respond_to_contact_request(user_3, user_2, false)
 378        .await
 379        .unwrap();
 380    assert!(!db.has_contact(user_2, user_3).await.unwrap());
 381    assert!(!db.has_contact(user_3, user_2).await.unwrap());
 382    assert_eq!(
 383        db.get_contacts(user_2).await.unwrap(),
 384        &[Contact::Accepted {
 385            user_id: user_1,
 386            should_notify: false,
 387            busy: false,
 388        }]
 389    );
 390    assert_eq!(
 391        db.get_contacts(user_3).await.unwrap(),
 392        &[Contact::Accepted {
 393            user_id: user_1,
 394            should_notify: false,
 395            busy: false,
 396        }],
 397    );
 398}
 399
 400test_both_dbs!(
 401    test_metrics_id,
 402    test_metrics_id_postgres,
 403    test_metrics_id_sqlite
 404);
 405
 406async fn test_metrics_id(db: &Arc<Database>) {
 407    let NewUserResult {
 408        user_id: user1,
 409        metrics_id: metrics_id1,
 410        ..
 411    } = db
 412        .create_user(
 413            "person1@example.com",
 414            false,
 415            NewUserParams {
 416                github_login: "person1".into(),
 417                github_user_id: 101,
 418                invite_count: 5,
 419            },
 420        )
 421        .await
 422        .unwrap();
 423    let NewUserResult {
 424        user_id: user2,
 425        metrics_id: metrics_id2,
 426        ..
 427    } = db
 428        .create_user(
 429            "person2@example.com",
 430            false,
 431            NewUserParams {
 432                github_login: "person2".into(),
 433                github_user_id: 102,
 434                invite_count: 5,
 435            },
 436        )
 437        .await
 438        .unwrap();
 439
 440    assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
 441    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
 442    assert_eq!(metrics_id1.len(), 36);
 443    assert_eq!(metrics_id2.len(), 36);
 444    assert_ne!(metrics_id1, metrics_id2);
 445}
 446
 447test_both_dbs!(
 448    test_project_count,
 449    test_project_count_postgres,
 450    test_project_count_sqlite
 451);
 452
 453async fn test_project_count(db: &Arc<Database>) {
 454    let owner_id = db.create_server("test").await.unwrap().0 as u32;
 455
 456    let user1 = db
 457        .create_user(
 458            &format!("admin@example.com"),
 459            true,
 460            NewUserParams {
 461                github_login: "admin".into(),
 462                github_user_id: 0,
 463                invite_count: 0,
 464            },
 465        )
 466        .await
 467        .unwrap();
 468    let user2 = db
 469        .create_user(
 470            &format!("user@example.com"),
 471            false,
 472            NewUserParams {
 473                github_login: "user".into(),
 474                github_user_id: 1,
 475                invite_count: 0,
 476            },
 477        )
 478        .await
 479        .unwrap();
 480
 481    let room_id = RoomId::from_proto(
 482        db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
 483            .await
 484            .unwrap()
 485            .id,
 486    );
 487    db.call(
 488        room_id,
 489        user1.user_id,
 490        ConnectionId { owner_id, id: 0 },
 491        user2.user_id,
 492        None,
 493    )
 494    .await
 495    .unwrap();
 496    db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
 497        .await
 498        .unwrap();
 499    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 500
 501    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 502        .await
 503        .unwrap();
 504    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
 505
 506    db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 507        .await
 508        .unwrap();
 509    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 510
 511    // Projects shared by admins aren't counted.
 512    db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
 513        .await
 514        .unwrap();
 515    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 516
 517    db.leave_room(ConnectionId { owner_id, id: 1 })
 518        .await
 519        .unwrap();
 520    assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 521}
 522
 523#[test]
 524fn test_fuzzy_like_string() {
 525    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
 526    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
 527    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
 528}
 529
 530#[gpui::test]
 531async fn test_fuzzy_search_users() {
 532    let test_db = TestDb::postgres(build_background_executor());
 533    let db = test_db.db();
 534    for (i, github_login) in [
 535        "California",
 536        "colorado",
 537        "oregon",
 538        "washington",
 539        "florida",
 540        "delaware",
 541        "rhode-island",
 542    ]
 543    .into_iter()
 544    .enumerate()
 545    {
 546        db.create_user(
 547            &format!("{github_login}@example.com"),
 548            false,
 549            NewUserParams {
 550                github_login: github_login.into(),
 551                github_user_id: i as i32,
 552                invite_count: 0,
 553            },
 554        )
 555        .await
 556        .unwrap();
 557    }
 558
 559    assert_eq!(
 560        fuzzy_search_user_names(db, "clr").await,
 561        &["colorado", "California"]
 562    );
 563    assert_eq!(
 564        fuzzy_search_user_names(db, "ro").await,
 565        &["rhode-island", "colorado", "oregon"],
 566    );
 567
 568    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
 569        db.fuzzy_search_users(query, 10)
 570            .await
 571            .unwrap()
 572            .into_iter()
 573            .map(|user| user.github_login)
 574            .collect::<Vec<_>>()
 575    }
 576}
 577
 578#[gpui::test]
 579async fn test_invite_codes() {
 580    let test_db = TestDb::postgres(build_background_executor());
 581    let db = test_db.db();
 582
 583    let NewUserResult { user_id: user1, .. } = db
 584        .create_user(
 585            "user1@example.com",
 586            false,
 587            NewUserParams {
 588                github_login: "user1".into(),
 589                github_user_id: 0,
 590                invite_count: 0,
 591            },
 592        )
 593        .await
 594        .unwrap();
 595
 596    // Initially, user 1 has no invite code
 597    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 598
 599    // Setting invite count to 0 when no code is assigned does not assign a new code
 600    db.set_invite_count_for_user(user1, 0).await.unwrap();
 601    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 602
 603    // User 1 creates an invite code that can be used twice.
 604    db.set_invite_count_for_user(user1, 2).await.unwrap();
 605    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 606    assert_eq!(invite_count, 2);
 607
 608    // User 2 redeems the invite code and becomes a contact of user 1.
 609    let user2_invite = db
 610        .create_invite_from_code(
 611            &invite_code,
 612            "user2@example.com",
 613            Some("user-2-device-id"),
 614            true,
 615        )
 616        .await
 617        .unwrap();
 618    let NewUserResult {
 619        user_id: user2,
 620        inviting_user_id,
 621        signup_device_id,
 622        metrics_id,
 623    } = db
 624        .create_user_from_invite(
 625            &user2_invite,
 626            NewUserParams {
 627                github_login: "user2".into(),
 628                github_user_id: 2,
 629                invite_count: 7,
 630            },
 631        )
 632        .await
 633        .unwrap()
 634        .unwrap();
 635    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 636    assert_eq!(invite_count, 1);
 637    assert_eq!(inviting_user_id, Some(user1));
 638    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 639    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 640    assert_eq!(
 641        db.get_contacts(user1).await.unwrap(),
 642        [Contact::Accepted {
 643            user_id: user2,
 644            should_notify: true,
 645            busy: false,
 646        }]
 647    );
 648    assert_eq!(
 649        db.get_contacts(user2).await.unwrap(),
 650        [Contact::Accepted {
 651            user_id: user1,
 652            should_notify: false,
 653            busy: false,
 654        }]
 655    );
 656    assert!(db.has_contact(user1, user2).await.unwrap());
 657    assert!(db.has_contact(user2, user1).await.unwrap());
 658    assert_eq!(
 659        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 660        7
 661    );
 662
 663    // User 3 redeems the invite code and becomes a contact of user 1.
 664    let user3_invite = db
 665        .create_invite_from_code(&invite_code, "user3@example.com", None, true)
 666        .await
 667        .unwrap();
 668    let NewUserResult {
 669        user_id: user3,
 670        inviting_user_id,
 671        signup_device_id,
 672        ..
 673    } = db
 674        .create_user_from_invite(
 675            &user3_invite,
 676            NewUserParams {
 677                github_login: "user-3".into(),
 678                github_user_id: 3,
 679                invite_count: 3,
 680            },
 681        )
 682        .await
 683        .unwrap()
 684        .unwrap();
 685    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 686    assert_eq!(invite_count, 0);
 687    assert_eq!(inviting_user_id, Some(user1));
 688    assert!(signup_device_id.is_none());
 689    assert_eq!(
 690        db.get_contacts(user1).await.unwrap(),
 691        [
 692            Contact::Accepted {
 693                user_id: user2,
 694                should_notify: true,
 695                busy: false,
 696            },
 697            Contact::Accepted {
 698                user_id: user3,
 699                should_notify: true,
 700                busy: false,
 701            }
 702        ]
 703    );
 704    assert_eq!(
 705        db.get_contacts(user3).await.unwrap(),
 706        [Contact::Accepted {
 707            user_id: user1,
 708            should_notify: false,
 709            busy: false,
 710        }]
 711    );
 712    assert!(db.has_contact(user1, user3).await.unwrap());
 713    assert!(db.has_contact(user3, user1).await.unwrap());
 714    assert_eq!(
 715        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 716        3
 717    );
 718
 719    // Trying to reedem the code for the third time results in an error.
 720    db.create_invite_from_code(
 721        &invite_code,
 722        "user4@example.com",
 723        Some("user-4-device-id"),
 724        true,
 725    )
 726    .await
 727    .unwrap_err();
 728
 729    // Invite count can be updated after the code has been created.
 730    db.set_invite_count_for_user(user1, 2).await.unwrap();
 731    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 732    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 733    assert_eq!(invite_count, 2);
 734
 735    // User 4 can now redeem the invite code and becomes a contact of user 1.
 736    let user4_invite = db
 737        .create_invite_from_code(
 738            &invite_code,
 739            "user4@example.com",
 740            Some("user-4-device-id"),
 741            true,
 742        )
 743        .await
 744        .unwrap();
 745    let user4 = db
 746        .create_user_from_invite(
 747            &user4_invite,
 748            NewUserParams {
 749                github_login: "user-4".into(),
 750                github_user_id: 4,
 751                invite_count: 5,
 752            },
 753        )
 754        .await
 755        .unwrap()
 756        .unwrap()
 757        .user_id;
 758
 759    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 760    assert_eq!(invite_count, 1);
 761    assert_eq!(
 762        db.get_contacts(user1).await.unwrap(),
 763        [
 764            Contact::Accepted {
 765                user_id: user2,
 766                should_notify: true,
 767                busy: false,
 768            },
 769            Contact::Accepted {
 770                user_id: user3,
 771                should_notify: true,
 772                busy: false,
 773            },
 774            Contact::Accepted {
 775                user_id: user4,
 776                should_notify: true,
 777                busy: false,
 778            }
 779        ]
 780    );
 781    assert_eq!(
 782        db.get_contacts(user4).await.unwrap(),
 783        [Contact::Accepted {
 784            user_id: user1,
 785            should_notify: false,
 786            busy: false,
 787        }]
 788    );
 789    assert!(db.has_contact(user1, user4).await.unwrap());
 790    assert!(db.has_contact(user4, user1).await.unwrap());
 791    assert_eq!(
 792        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 793        5
 794    );
 795
 796    // An existing user cannot redeem invite codes.
 797    db.create_invite_from_code(
 798        &invite_code,
 799        "user2@example.com",
 800        Some("user-2-device-id"),
 801        true,
 802    )
 803    .await
 804    .unwrap_err();
 805    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 806    assert_eq!(invite_count, 1);
 807
 808    // A newer user can invite an existing one via a different email address
 809    // than the one they used to sign up.
 810    let user5 = db
 811        .create_user(
 812            "user5@example.com",
 813            false,
 814            NewUserParams {
 815                github_login: "user5".into(),
 816                github_user_id: 5,
 817                invite_count: 0,
 818            },
 819        )
 820        .await
 821        .unwrap()
 822        .user_id;
 823    db.set_invite_count_for_user(user5, 5).await.unwrap();
 824    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 825    let user5_invite_to_user1 = db
 826        .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
 827        .await
 828        .unwrap();
 829    let user1_2 = db
 830        .create_user_from_invite(
 831            &user5_invite_to_user1,
 832            NewUserParams {
 833                github_login: "user1".into(),
 834                github_user_id: 1,
 835                invite_count: 5,
 836            },
 837        )
 838        .await
 839        .unwrap()
 840        .unwrap()
 841        .user_id;
 842    assert_eq!(user1_2, user1);
 843    assert_eq!(
 844        db.get_contacts(user1).await.unwrap(),
 845        [
 846            Contact::Accepted {
 847                user_id: user2,
 848                should_notify: true,
 849                busy: false,
 850            },
 851            Contact::Accepted {
 852                user_id: user3,
 853                should_notify: true,
 854                busy: false,
 855            },
 856            Contact::Accepted {
 857                user_id: user4,
 858                should_notify: true,
 859                busy: false,
 860            },
 861            Contact::Accepted {
 862                user_id: user5,
 863                should_notify: false,
 864                busy: false,
 865            }
 866        ]
 867    );
 868    assert_eq!(
 869        db.get_contacts(user5).await.unwrap(),
 870        [Contact::Accepted {
 871            user_id: user1,
 872            should_notify: true,
 873            busy: false,
 874        }]
 875    );
 876    assert!(db.has_contact(user1, user5).await.unwrap());
 877    assert!(db.has_contact(user5, user1).await.unwrap());
 878}
 879
 880test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
 881
 882async fn test_channels(db: &Arc<Database>) {
 883    let a_id = db
 884        .create_user(
 885            "user1@example.com",
 886            false,
 887            NewUserParams {
 888                github_login: "user1".into(),
 889                github_user_id: 5,
 890                invite_count: 0,
 891            },
 892        )
 893        .await
 894        .unwrap()
 895        .user_id;
 896
 897    let b_id = db
 898        .create_user(
 899            "user2@example.com",
 900            false,
 901            NewUserParams {
 902                github_login: "user2".into(),
 903                github_user_id: 6,
 904                invite_count: 0,
 905            },
 906        )
 907        .await
 908        .unwrap()
 909        .user_id;
 910
 911    let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
 912
 913    // Make sure that people cannot read channels they haven't been invited to
 914    assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
 915
 916    db.invite_channel_member(zed_id, b_id, a_id, false)
 917        .await
 918        .unwrap();
 919
 920    db.respond_to_channel_invite(zed_id, b_id, true)
 921        .await
 922        .unwrap();
 923
 924    let crdb_id = db
 925        .create_channel("crdb", Some(zed_id), "2", a_id)
 926        .await
 927        .unwrap();
 928    let livestreaming_id = db
 929        .create_channel("livestreaming", Some(zed_id), "3", a_id)
 930        .await
 931        .unwrap();
 932    let replace_id = db
 933        .create_channel("replace", Some(zed_id), "4", a_id)
 934        .await
 935        .unwrap();
 936
 937    let mut members = db.get_channel_members(replace_id).await.unwrap();
 938    members.sort();
 939    assert_eq!(members, &[a_id, b_id]);
 940
 941    let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap();
 942    let cargo_id = db
 943        .create_channel("cargo", Some(rust_id), "6", a_id)
 944        .await
 945        .unwrap();
 946
 947    let cargo_ra_id = db
 948        .create_channel("cargo-ra", Some(cargo_id), "7", a_id)
 949        .await
 950        .unwrap();
 951
 952    let result = db.get_channels_for_user(a_id).await.unwrap();
 953    assert_eq!(
 954        result.channels,
 955        vec![
 956            Channel {
 957                id: zed_id,
 958                name: "zed".to_string(),
 959                parent_id: None,
 960            },
 961            Channel {
 962                id: crdb_id,
 963                name: "crdb".to_string(),
 964                parent_id: Some(zed_id),
 965            },
 966            Channel {
 967                id: livestreaming_id,
 968                name: "livestreaming".to_string(),
 969                parent_id: Some(zed_id),
 970            },
 971            Channel {
 972                id: replace_id,
 973                name: "replace".to_string(),
 974                parent_id: Some(zed_id),
 975            },
 976            Channel {
 977                id: rust_id,
 978                name: "rust".to_string(),
 979                parent_id: None,
 980            },
 981            Channel {
 982                id: cargo_id,
 983                name: "cargo".to_string(),
 984                parent_id: Some(rust_id),
 985            },
 986            Channel {
 987                id: cargo_ra_id,
 988                name: "cargo-ra".to_string(),
 989                parent_id: Some(cargo_id),
 990            }
 991        ]
 992    );
 993
 994    let result = db.get_channels_for_user(b_id).await.unwrap();
 995    assert_eq!(
 996        result.channels,
 997        vec![
 998            Channel {
 999                id: zed_id,
1000                name: "zed".to_string(),
1001                parent_id: None,
1002            },
1003            Channel {
1004                id: crdb_id,
1005                name: "crdb".to_string(),
1006                parent_id: Some(zed_id),
1007            },
1008            Channel {
1009                id: livestreaming_id,
1010                name: "livestreaming".to_string(),
1011                parent_id: Some(zed_id),
1012            },
1013            Channel {
1014                id: replace_id,
1015                name: "replace".to_string(),
1016                parent_id: Some(zed_id),
1017            },
1018        ]
1019    );
1020
1021    // Update member permissions
1022    let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
1023    assert!(set_subchannel_admin.is_err());
1024    let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
1025    assert!(set_channel_admin.is_ok());
1026
1027    let result = db.get_channels_for_user(b_id).await.unwrap();
1028    assert_eq!(
1029        result.channels,
1030        vec![
1031            Channel {
1032                id: zed_id,
1033                name: "zed".to_string(),
1034                parent_id: None,
1035            },
1036            Channel {
1037                id: crdb_id,
1038                name: "crdb".to_string(),
1039                parent_id: Some(zed_id),
1040            },
1041            Channel {
1042                id: livestreaming_id,
1043                name: "livestreaming".to_string(),
1044                parent_id: Some(zed_id),
1045            },
1046            Channel {
1047                id: replace_id,
1048                name: "replace".to_string(),
1049                parent_id: Some(zed_id),
1050            },
1051        ]
1052    );
1053
1054    // Remove a single channel
1055    db.remove_channel(crdb_id, a_id).await.unwrap();
1056    assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
1057
1058    // Remove a channel tree
1059    let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
1060    channel_ids.sort();
1061    assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
1062    assert_eq!(user_ids, &[a_id]);
1063
1064    assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
1065    assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
1066    assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
1067}
1068
1069test_both_dbs!(
1070    test_joining_channels,
1071    test_joining_channels_postgres,
1072    test_joining_channels_sqlite
1073);
1074
1075async fn test_joining_channels(db: &Arc<Database>) {
1076    let owner_id = db.create_server("test").await.unwrap().0 as u32;
1077
1078    let user_1 = db
1079        .create_user(
1080            "user1@example.com",
1081            false,
1082            NewUserParams {
1083                github_login: "user1".into(),
1084                github_user_id: 5,
1085                invite_count: 0,
1086            },
1087        )
1088        .await
1089        .unwrap()
1090        .user_id;
1091    let user_2 = db
1092        .create_user(
1093            "user2@example.com",
1094            false,
1095            NewUserParams {
1096                github_login: "user2".into(),
1097                github_user_id: 6,
1098                invite_count: 0,
1099            },
1100        )
1101        .await
1102        .unwrap()
1103        .user_id;
1104
1105    let channel_1 = db
1106        .create_root_channel("channel_1", "1", user_1)
1107        .await
1108        .unwrap();
1109    let room_1 = db.room_id_for_channel(channel_1).await.unwrap();
1110
1111    // can join a room with membership to its channel
1112    let joined_room = db
1113        .join_room(room_1, user_1, ConnectionId { owner_id, id: 1 })
1114        .await
1115        .unwrap();
1116    assert_eq!(joined_room.room.participants.len(), 1);
1117
1118    drop(joined_room);
1119    // cannot join a room without membership to its channel
1120    assert!(db
1121        .join_room(room_1, user_2, ConnectionId { owner_id, id: 1 })
1122        .await
1123        .is_err());
1124}
1125
1126test_both_dbs!(
1127    test_channel_invites,
1128    test_channel_invites_postgres,
1129    test_channel_invites_sqlite
1130);
1131
1132async fn test_channel_invites(db: &Arc<Database>) {
1133    db.create_server("test").await.unwrap();
1134
1135    let user_1 = db
1136        .create_user(
1137            "user1@example.com",
1138            false,
1139            NewUserParams {
1140                github_login: "user1".into(),
1141                github_user_id: 5,
1142                invite_count: 0,
1143            },
1144        )
1145        .await
1146        .unwrap()
1147        .user_id;
1148    let user_2 = db
1149        .create_user(
1150            "user2@example.com",
1151            false,
1152            NewUserParams {
1153                github_login: "user2".into(),
1154                github_user_id: 6,
1155                invite_count: 0,
1156            },
1157        )
1158        .await
1159        .unwrap()
1160        .user_id;
1161
1162    let user_3 = db
1163        .create_user(
1164            "user3@example.com",
1165            false,
1166            NewUserParams {
1167                github_login: "user3".into(),
1168                github_user_id: 7,
1169                invite_count: 0,
1170            },
1171        )
1172        .await
1173        .unwrap()
1174        .user_id;
1175
1176    let channel_1_1 = db
1177        .create_root_channel("channel_1", "1", user_1)
1178        .await
1179        .unwrap();
1180
1181    let channel_1_2 = db
1182        .create_root_channel("channel_2", "2", user_1)
1183        .await
1184        .unwrap();
1185
1186    db.invite_channel_member(channel_1_1, user_2, user_1, false)
1187        .await
1188        .unwrap();
1189    db.invite_channel_member(channel_1_2, user_2, user_1, false)
1190        .await
1191        .unwrap();
1192    db.invite_channel_member(channel_1_1, user_3, user_1, true)
1193        .await
1194        .unwrap();
1195
1196    let user_2_invites = db
1197        .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
1198        .await
1199        .unwrap()
1200        .into_iter()
1201        .map(|channel| channel.id)
1202        .collect::<Vec<_>>();
1203
1204    assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
1205
1206    let user_3_invites = db
1207        .get_channel_invites_for_user(user_3) // -> [channel_1_1]
1208        .await
1209        .unwrap()
1210        .into_iter()
1211        .map(|channel| channel.id)
1212        .collect::<Vec<_>>();
1213
1214    assert_eq!(user_3_invites, &[channel_1_1]);
1215
1216    let members = db
1217        .get_channel_member_details(channel_1_1, user_1)
1218        .await
1219        .unwrap();
1220    assert_eq!(
1221        members,
1222        &[
1223            proto::ChannelMember {
1224                user_id: user_1.to_proto(),
1225                kind: proto::channel_member::Kind::Member.into(),
1226                admin: true,
1227            },
1228            proto::ChannelMember {
1229                user_id: user_2.to_proto(),
1230                kind: proto::channel_member::Kind::Invitee.into(),
1231                admin: false,
1232            },
1233            proto::ChannelMember {
1234                user_id: user_3.to_proto(),
1235                kind: proto::channel_member::Kind::Invitee.into(),
1236                admin: true,
1237            },
1238        ]
1239    );
1240
1241    db.respond_to_channel_invite(channel_1_1, user_2, true)
1242        .await
1243        .unwrap();
1244
1245    let channel_1_3 = db
1246        .create_channel("channel_3", Some(channel_1_1), "1", user_1)
1247        .await
1248        .unwrap();
1249
1250    let members = db
1251        .get_channel_member_details(channel_1_3, user_1)
1252        .await
1253        .unwrap();
1254    assert_eq!(
1255        members,
1256        &[
1257            proto::ChannelMember {
1258                user_id: user_1.to_proto(),
1259                kind: proto::channel_member::Kind::Member.into(),
1260                admin: true,
1261            },
1262            proto::ChannelMember {
1263                user_id: user_2.to_proto(),
1264                kind: proto::channel_member::Kind::AncestorMember.into(),
1265                admin: false,
1266            },
1267        ]
1268    );
1269}
1270
1271test_both_dbs!(
1272    test_channel_renames,
1273    test_channel_renames_postgres,
1274    test_channel_renames_sqlite
1275);
1276
1277async fn test_channel_renames(db: &Arc<Database>) {
1278    db.create_server("test").await.unwrap();
1279
1280    let user_1 = db
1281        .create_user(
1282            "user1@example.com",
1283            false,
1284            NewUserParams {
1285                github_login: "user1".into(),
1286                github_user_id: 5,
1287                invite_count: 0,
1288            },
1289        )
1290        .await
1291        .unwrap()
1292        .user_id;
1293
1294    let user_2 = db
1295        .create_user(
1296            "user2@example.com",
1297            false,
1298            NewUserParams {
1299                github_login: "user2".into(),
1300                github_user_id: 6,
1301                invite_count: 0,
1302            },
1303        )
1304        .await
1305        .unwrap()
1306        .user_id;
1307
1308    let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap();
1309
1310    db.rename_channel(zed_id, user_1, "#zed-archive")
1311        .await
1312        .unwrap();
1313
1314    let zed_archive_id = zed_id;
1315
1316    let (channel, _) = db
1317        .get_channel(zed_archive_id, user_1)
1318        .await
1319        .unwrap()
1320        .unwrap();
1321    assert_eq!(channel.name, "zed-archive");
1322
1323    let non_permissioned_rename = db
1324        .rename_channel(zed_archive_id, user_2, "hacked-lol")
1325        .await;
1326    assert!(non_permissioned_rename.is_err());
1327
1328    let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
1329    assert!(bad_name_rename.is_err())
1330}
1331
1332#[gpui::test]
1333async fn test_multiple_signup_overwrite() {
1334    let test_db = TestDb::postgres(build_background_executor());
1335    let db = test_db.db();
1336
1337    let email_address = "user_1@example.com".to_string();
1338
1339    let initial_signup_created_at_milliseconds = 0;
1340
1341    let initial_signup = NewSignup {
1342        email_address: email_address.clone(),
1343        platform_mac: false,
1344        platform_linux: true,
1345        platform_windows: false,
1346        editor_features: vec!["speed".into()],
1347        programming_languages: vec!["rust".into(), "c".into()],
1348        device_id: Some(format!("device_id")),
1349        added_to_mailing_list: false,
1350        created_at: Some(
1351            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
1352        ),
1353    };
1354
1355    db.create_signup(&initial_signup).await.unwrap();
1356
1357    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
1358
1359    assert_eq!(
1360        initial_signup_from_db.clone(),
1361        signup::Model {
1362            email_address: initial_signup.email_address,
1363            platform_mac: initial_signup.platform_mac,
1364            platform_linux: initial_signup.platform_linux,
1365            platform_windows: initial_signup.platform_windows,
1366            editor_features: Some(initial_signup.editor_features),
1367            programming_languages: Some(initial_signup.programming_languages),
1368            added_to_mailing_list: initial_signup.added_to_mailing_list,
1369            ..initial_signup_from_db
1370        }
1371    );
1372
1373    let subsequent_signup = NewSignup {
1374        email_address: email_address.clone(),
1375        platform_mac: true,
1376        platform_linux: false,
1377        platform_windows: true,
1378        editor_features: vec!["git integration".into(), "clean design".into()],
1379        programming_languages: vec!["d".into(), "elm".into()],
1380        device_id: Some(format!("different_device_id")),
1381        added_to_mailing_list: true,
1382        // subsequent signup happens next day
1383        created_at: Some(
1384            DateTime::from_timestamp_millis(
1385                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
1386            )
1387            .unwrap(),
1388        ),
1389    };
1390
1391    db.create_signup(&subsequent_signup).await.unwrap();
1392
1393    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
1394
1395    assert_eq!(
1396        subsequent_signup_from_db.clone(),
1397        signup::Model {
1398            platform_mac: subsequent_signup.platform_mac,
1399            platform_linux: subsequent_signup.platform_linux,
1400            platform_windows: subsequent_signup.platform_windows,
1401            editor_features: Some(subsequent_signup.editor_features),
1402            programming_languages: Some(subsequent_signup.programming_languages),
1403            device_id: subsequent_signup.device_id,
1404            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
1405            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
1406            created_at: initial_signup_from_db.created_at,
1407            ..subsequent_signup_from_db
1408        }
1409    );
1410}
1411
1412#[gpui::test]
1413async fn test_signups() {
1414    let test_db = TestDb::postgres(build_background_executor());
1415    let db = test_db.db();
1416
1417    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
1418
1419    let all_signups = usernames
1420        .iter()
1421        .enumerate()
1422        .map(|(i, username)| NewSignup {
1423            email_address: format!("{username}@example.com"),
1424            platform_mac: true,
1425            platform_linux: i % 2 == 0,
1426            platform_windows: i % 4 == 0,
1427            editor_features: vec!["speed".into()],
1428            programming_languages: vec!["rust".into(), "c".into()],
1429            device_id: Some(format!("device_id_{i}")),
1430            added_to_mailing_list: i != 0, // One user failed to subscribe
1431            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
1432        })
1433        .collect::<Vec<NewSignup>>();
1434
1435    // people sign up on the waitlist
1436    for signup in &all_signups {
1437        // users can sign up multiple times without issues
1438        for _ in 0..2 {
1439            db.create_signup(&signup).await.unwrap();
1440        }
1441    }
1442
1443    assert_eq!(
1444        db.get_waitlist_summary().await.unwrap(),
1445        WaitlistSummary {
1446            count: 8,
1447            mac_count: 8,
1448            linux_count: 4,
1449            windows_count: 2,
1450            unknown_count: 0,
1451        }
1452    );
1453
1454    // retrieve the next batch of signup emails to send
1455    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1456    let addresses = signups_batch1
1457        .iter()
1458        .map(|s| &s.email_address)
1459        .collect::<Vec<_>>();
1460    assert_eq!(
1461        addresses,
1462        &[
1463            all_signups[0].email_address.as_str(),
1464            all_signups[1].email_address.as_str(),
1465            all_signups[2].email_address.as_str()
1466        ]
1467    );
1468    assert_ne!(
1469        signups_batch1[0].email_confirmation_code,
1470        signups_batch1[1].email_confirmation_code
1471    );
1472
1473    // the waitlist isn't updated until we record that the emails
1474    // were successfully sent.
1475    let signups_batch = db.get_unsent_invites(3).await.unwrap();
1476    assert_eq!(signups_batch, signups_batch1);
1477
1478    // once the emails go out, we can retrieve the next batch
1479    // of signups.
1480    db.record_sent_invites(&signups_batch1).await.unwrap();
1481    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1482    let addresses = signups_batch2
1483        .iter()
1484        .map(|s| &s.email_address)
1485        .collect::<Vec<_>>();
1486    assert_eq!(
1487        addresses,
1488        &[
1489            all_signups[3].email_address.as_str(),
1490            all_signups[4].email_address.as_str(),
1491            all_signups[5].email_address.as_str()
1492        ]
1493    );
1494
1495    // the sent invites are excluded from the summary.
1496    assert_eq!(
1497        db.get_waitlist_summary().await.unwrap(),
1498        WaitlistSummary {
1499            count: 5,
1500            mac_count: 5,
1501            linux_count: 2,
1502            windows_count: 1,
1503            unknown_count: 0,
1504        }
1505    );
1506
1507    // user completes the signup process by providing their
1508    // github account.
1509    let NewUserResult {
1510        user_id,
1511        inviting_user_id,
1512        signup_device_id,
1513        ..
1514    } = db
1515        .create_user_from_invite(
1516            &Invite {
1517                ..signups_batch1[0].clone()
1518            },
1519            NewUserParams {
1520                github_login: usernames[0].clone(),
1521                github_user_id: 0,
1522                invite_count: 5,
1523            },
1524        )
1525        .await
1526        .unwrap()
1527        .unwrap();
1528    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1529    assert!(inviting_user_id.is_none());
1530    assert_eq!(user.github_login, usernames[0]);
1531    assert_eq!(
1532        user.email_address,
1533        Some(all_signups[0].email_address.clone())
1534    );
1535    assert_eq!(user.invite_count, 5);
1536    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1537
1538    // cannot redeem the same signup again.
1539    assert!(db
1540        .create_user_from_invite(
1541            &Invite {
1542                email_address: signups_batch1[0].email_address.clone(),
1543                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1544            },
1545            NewUserParams {
1546                github_login: "some-other-github_account".into(),
1547                github_user_id: 1,
1548                invite_count: 5,
1549            },
1550        )
1551        .await
1552        .unwrap()
1553        .is_none());
1554
1555    // cannot redeem a signup with the wrong confirmation code.
1556    db.create_user_from_invite(
1557        &Invite {
1558            email_address: signups_batch1[1].email_address.clone(),
1559            email_confirmation_code: "the-wrong-code".to_string(),
1560        },
1561        NewUserParams {
1562            github_login: usernames[1].clone(),
1563            github_user_id: 2,
1564            invite_count: 5,
1565        },
1566    )
1567    .await
1568    .unwrap_err();
1569}
1570
1571fn build_background_executor() -> Arc<Background> {
1572    Deterministic::new(0).build_background()
1573}