tests.rs

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