db_tests.rs

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