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(
 498            room_id,
 499            user2.user_id,
 500            None,
 501            ConnectionId { owner_id, id: 1 },
 502        )
 503        .await
 504        .unwrap();
 505        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 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(), 1);
 511
 512        db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 513            .await
 514            .unwrap();
 515        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 516
 517        // Projects shared by admins aren't counted.
 518        db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
 519            .await
 520            .unwrap();
 521        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 522
 523        db.leave_room(ConnectionId { owner_id, id: 1 })
 524            .await
 525            .unwrap();
 526        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 527    }
 528);
 529
 530#[test]
 531fn test_fuzzy_like_string() {
 532    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
 533    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
 534    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
 535}
 536
 537#[gpui::test]
 538async fn test_fuzzy_search_users() {
 539    let test_db = TestDb::postgres(build_background_executor());
 540    let db = test_db.db();
 541    for (i, github_login) in [
 542        "California",
 543        "colorado",
 544        "oregon",
 545        "washington",
 546        "florida",
 547        "delaware",
 548        "rhode-island",
 549    ]
 550    .into_iter()
 551    .enumerate()
 552    {
 553        db.create_user(
 554            &format!("{github_login}@example.com"),
 555            false,
 556            NewUserParams {
 557                github_login: github_login.into(),
 558                github_user_id: i as i32,
 559                invite_count: 0,
 560            },
 561        )
 562        .await
 563        .unwrap();
 564    }
 565
 566    assert_eq!(
 567        fuzzy_search_user_names(db, "clr").await,
 568        &["colorado", "California"]
 569    );
 570    assert_eq!(
 571        fuzzy_search_user_names(db, "ro").await,
 572        &["rhode-island", "colorado", "oregon"],
 573    );
 574
 575    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
 576        db.fuzzy_search_users(query, 10)
 577            .await
 578            .unwrap()
 579            .into_iter()
 580            .map(|user| user.github_login)
 581            .collect::<Vec<_>>()
 582    }
 583}
 584
 585#[gpui::test]
 586async fn test_invite_codes() {
 587    let test_db = TestDb::postgres(build_background_executor());
 588    let db = test_db.db();
 589
 590    let NewUserResult { user_id: user1, .. } = db
 591        .create_user(
 592            "user1@example.com",
 593            false,
 594            NewUserParams {
 595                github_login: "user1".into(),
 596                github_user_id: 0,
 597                invite_count: 0,
 598            },
 599        )
 600        .await
 601        .unwrap();
 602
 603    // Initially, user 1 has no invite code
 604    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 605
 606    // Setting invite count to 0 when no code is assigned does not assign a new code
 607    db.set_invite_count_for_user(user1, 0).await.unwrap();
 608    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 609
 610    // User 1 creates an invite code that can be used twice.
 611    db.set_invite_count_for_user(user1, 2).await.unwrap();
 612    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 613    assert_eq!(invite_count, 2);
 614
 615    // User 2 redeems the invite code and becomes a contact of user 1.
 616    let user2_invite = db
 617        .create_invite_from_code(
 618            &invite_code,
 619            "user2@example.com",
 620            Some("user-2-device-id"),
 621            true,
 622        )
 623        .await
 624        .unwrap();
 625    let NewUserResult {
 626        user_id: user2,
 627        inviting_user_id,
 628        signup_device_id,
 629        metrics_id,
 630    } = db
 631        .create_user_from_invite(
 632            &user2_invite,
 633            NewUserParams {
 634                github_login: "user2".into(),
 635                github_user_id: 2,
 636                invite_count: 7,
 637            },
 638        )
 639        .await
 640        .unwrap()
 641        .unwrap();
 642    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 643    assert_eq!(invite_count, 1);
 644    assert_eq!(inviting_user_id, Some(user1));
 645    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 646    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 647    assert_eq!(
 648        db.get_contacts(user1).await.unwrap(),
 649        [Contact::Accepted {
 650            user_id: user2,
 651            should_notify: true,
 652            busy: false,
 653        }]
 654    );
 655    assert_eq!(
 656        db.get_contacts(user2).await.unwrap(),
 657        [Contact::Accepted {
 658            user_id: user1,
 659            should_notify: false,
 660            busy: false,
 661        }]
 662    );
 663    assert!(db.has_contact(user1, user2).await.unwrap());
 664    assert!(db.has_contact(user2, user1).await.unwrap());
 665    assert_eq!(
 666        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 667        7
 668    );
 669
 670    // User 3 redeems the invite code and becomes a contact of user 1.
 671    let user3_invite = db
 672        .create_invite_from_code(&invite_code, "user3@example.com", None, true)
 673        .await
 674        .unwrap();
 675    let NewUserResult {
 676        user_id: user3,
 677        inviting_user_id,
 678        signup_device_id,
 679        ..
 680    } = db
 681        .create_user_from_invite(
 682            &user3_invite,
 683            NewUserParams {
 684                github_login: "user-3".into(),
 685                github_user_id: 3,
 686                invite_count: 3,
 687            },
 688        )
 689        .await
 690        .unwrap()
 691        .unwrap();
 692    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 693    assert_eq!(invite_count, 0);
 694    assert_eq!(inviting_user_id, Some(user1));
 695    assert!(signup_device_id.is_none());
 696    assert_eq!(
 697        db.get_contacts(user1).await.unwrap(),
 698        [
 699            Contact::Accepted {
 700                user_id: user2,
 701                should_notify: true,
 702                busy: false,
 703            },
 704            Contact::Accepted {
 705                user_id: user3,
 706                should_notify: true,
 707                busy: false,
 708            }
 709        ]
 710    );
 711    assert_eq!(
 712        db.get_contacts(user3).await.unwrap(),
 713        [Contact::Accepted {
 714            user_id: user1,
 715            should_notify: false,
 716            busy: false,
 717        }]
 718    );
 719    assert!(db.has_contact(user1, user3).await.unwrap());
 720    assert!(db.has_contact(user3, user1).await.unwrap());
 721    assert_eq!(
 722        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 723        3
 724    );
 725
 726    // Trying to reedem the code for the third time results in an error.
 727    db.create_invite_from_code(
 728        &invite_code,
 729        "user4@example.com",
 730        Some("user-4-device-id"),
 731        true,
 732    )
 733    .await
 734    .unwrap_err();
 735
 736    // Invite count can be updated after the code has been created.
 737    db.set_invite_count_for_user(user1, 2).await.unwrap();
 738    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 739    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 740    assert_eq!(invite_count, 2);
 741
 742    // User 4 can now redeem the invite code and becomes a contact of user 1.
 743    let user4_invite = db
 744        .create_invite_from_code(
 745            &invite_code,
 746            "user4@example.com",
 747            Some("user-4-device-id"),
 748            true,
 749        )
 750        .await
 751        .unwrap();
 752    let user4 = db
 753        .create_user_from_invite(
 754            &user4_invite,
 755            NewUserParams {
 756                github_login: "user-4".into(),
 757                github_user_id: 4,
 758                invite_count: 5,
 759            },
 760        )
 761        .await
 762        .unwrap()
 763        .unwrap()
 764        .user_id;
 765
 766    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 767    assert_eq!(invite_count, 1);
 768    assert_eq!(
 769        db.get_contacts(user1).await.unwrap(),
 770        [
 771            Contact::Accepted {
 772                user_id: user2,
 773                should_notify: true,
 774                busy: false,
 775            },
 776            Contact::Accepted {
 777                user_id: user3,
 778                should_notify: true,
 779                busy: false,
 780            },
 781            Contact::Accepted {
 782                user_id: user4,
 783                should_notify: true,
 784                busy: false,
 785            }
 786        ]
 787    );
 788    assert_eq!(
 789        db.get_contacts(user4).await.unwrap(),
 790        [Contact::Accepted {
 791            user_id: user1,
 792            should_notify: false,
 793            busy: false,
 794        }]
 795    );
 796    assert!(db.has_contact(user1, user4).await.unwrap());
 797    assert!(db.has_contact(user4, user1).await.unwrap());
 798    assert_eq!(
 799        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 800        5
 801    );
 802
 803    // An existing user cannot redeem invite codes.
 804    db.create_invite_from_code(
 805        &invite_code,
 806        "user2@example.com",
 807        Some("user-2-device-id"),
 808        true,
 809    )
 810    .await
 811    .unwrap_err();
 812    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 813    assert_eq!(invite_count, 1);
 814
 815    // A newer user can invite an existing one via a different email address
 816    // than the one they used to sign up.
 817    let user5 = db
 818        .create_user(
 819            "user5@example.com",
 820            false,
 821            NewUserParams {
 822                github_login: "user5".into(),
 823                github_user_id: 5,
 824                invite_count: 0,
 825            },
 826        )
 827        .await
 828        .unwrap()
 829        .user_id;
 830    db.set_invite_count_for_user(user5, 5).await.unwrap();
 831    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 832    let user5_invite_to_user1 = db
 833        .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
 834        .await
 835        .unwrap();
 836    let user1_2 = db
 837        .create_user_from_invite(
 838            &user5_invite_to_user1,
 839            NewUserParams {
 840                github_login: "user1".into(),
 841                github_user_id: 1,
 842                invite_count: 5,
 843            },
 844        )
 845        .await
 846        .unwrap()
 847        .unwrap()
 848        .user_id;
 849    assert_eq!(user1_2, user1);
 850    assert_eq!(
 851        db.get_contacts(user1).await.unwrap(),
 852        [
 853            Contact::Accepted {
 854                user_id: user2,
 855                should_notify: true,
 856                busy: false,
 857            },
 858            Contact::Accepted {
 859                user_id: user3,
 860                should_notify: true,
 861                busy: false,
 862            },
 863            Contact::Accepted {
 864                user_id: user4,
 865                should_notify: true,
 866                busy: false,
 867            },
 868            Contact::Accepted {
 869                user_id: user5,
 870                should_notify: false,
 871                busy: false,
 872            }
 873        ]
 874    );
 875    assert_eq!(
 876        db.get_contacts(user5).await.unwrap(),
 877        [Contact::Accepted {
 878            user_id: user1,
 879            should_notify: true,
 880            busy: false,
 881        }]
 882    );
 883    assert!(db.has_contact(user1, user5).await.unwrap());
 884    assert!(db.has_contact(user5, user1).await.unwrap());
 885}
 886
 887test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, {
 888    let a_id = db
 889        .create_user(
 890            "user1@example.com",
 891            false,
 892            NewUserParams {
 893                github_login: "user1".into(),
 894                github_user_id: 5,
 895                invite_count: 0,
 896            },
 897        )
 898        .await
 899        .unwrap()
 900        .user_id;
 901
 902    let b_id = db
 903        .create_user(
 904            "user2@example.com",
 905            false,
 906            NewUserParams {
 907                github_login: "user2".into(),
 908                github_user_id: 6,
 909                invite_count: 0,
 910            },
 911        )
 912        .await
 913        .unwrap()
 914        .user_id;
 915
 916    let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
 917
 918    db.invite_channel_member(zed_id, b_id, a_id, false)
 919        .await
 920        .unwrap();
 921
 922    db.respond_to_channel_invite(zed_id, b_id, true)
 923        .await
 924        .unwrap();
 925
 926    let crdb_id = db
 927        .create_channel("crdb", Some(zed_id), "2", a_id)
 928        .await
 929        .unwrap();
 930    let livestreaming_id = db
 931        .create_channel("livestreaming", Some(zed_id), "3", a_id)
 932        .await
 933        .unwrap();
 934    let replace_id = db
 935        .create_channel("replace", Some(zed_id), "4", a_id)
 936        .await
 937        .unwrap();
 938
 939    let mut members = db.get_channel_members(replace_id).await.unwrap();
 940    members.sort();
 941    assert_eq!(members, &[a_id, b_id]);
 942
 943    let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap();
 944    let cargo_id = db
 945        .create_channel("cargo", Some(rust_id), "6", a_id)
 946        .await
 947        .unwrap();
 948
 949    let cargo_ra_id = db
 950        .create_channel("cargo-ra", Some(cargo_id), "7", a_id)
 951        .await
 952        .unwrap();
 953
 954    let (channels, _) = db.get_channels_for_user(a_id).await.unwrap();
 955    assert_eq!(
 956        channels,
 957        vec![
 958            Channel {
 959                id: zed_id,
 960                name: "zed".to_string(),
 961                parent_id: None,
 962                user_is_admin: true,
 963            },
 964            Channel {
 965                id: crdb_id,
 966                name: "crdb".to_string(),
 967                parent_id: Some(zed_id),
 968                user_is_admin: true,
 969            },
 970            Channel {
 971                id: livestreaming_id,
 972                name: "livestreaming".to_string(),
 973                parent_id: Some(zed_id),
 974                user_is_admin: true,
 975            },
 976            Channel {
 977                id: replace_id,
 978                name: "replace".to_string(),
 979                parent_id: Some(zed_id),
 980                user_is_admin: true,
 981            },
 982            Channel {
 983                id: rust_id,
 984                name: "rust".to_string(),
 985                parent_id: None,
 986                user_is_admin: true,
 987            },
 988            Channel {
 989                id: cargo_id,
 990                name: "cargo".to_string(),
 991                parent_id: Some(rust_id),
 992                user_is_admin: true,
 993            },
 994            Channel {
 995                id: cargo_ra_id,
 996                name: "cargo-ra".to_string(),
 997                parent_id: Some(cargo_id),
 998                user_is_admin: true,
 999            }
1000        ]
1001    );
1002
1003    let (channels, _) = db.get_channels_for_user(b_id).await.unwrap();
1004    assert_eq!(
1005        channels,
1006        vec![
1007            Channel {
1008                id: zed_id,
1009                name: "zed".to_string(),
1010                parent_id: None,
1011                user_is_admin: false,
1012            },
1013            Channel {
1014                id: crdb_id,
1015                name: "crdb".to_string(),
1016                parent_id: Some(zed_id),
1017                user_is_admin: false,
1018            },
1019            Channel {
1020                id: livestreaming_id,
1021                name: "livestreaming".to_string(),
1022                parent_id: Some(zed_id),
1023                user_is_admin: false,
1024            },
1025            Channel {
1026                id: replace_id,
1027                name: "replace".to_string(),
1028                parent_id: Some(zed_id),
1029                user_is_admin: false,
1030            },
1031        ]
1032    );
1033
1034    // Update member permissions
1035    let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
1036    assert!(set_subchannel_admin.is_err());
1037    let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
1038    assert!(set_channel_admin.is_ok());
1039
1040    let (channels, _) = db.get_channels_for_user(b_id).await.unwrap();
1041    assert_eq!(
1042        channels,
1043        vec![
1044            Channel {
1045                id: zed_id,
1046                name: "zed".to_string(),
1047                parent_id: None,
1048                user_is_admin: true,
1049            },
1050            Channel {
1051                id: crdb_id,
1052                name: "crdb".to_string(),
1053                parent_id: Some(zed_id),
1054                user_is_admin: false,
1055            },
1056            Channel {
1057                id: livestreaming_id,
1058                name: "livestreaming".to_string(),
1059                parent_id: Some(zed_id),
1060                user_is_admin: false,
1061            },
1062            Channel {
1063                id: replace_id,
1064                name: "replace".to_string(),
1065                parent_id: Some(zed_id),
1066                user_is_admin: false,
1067            },
1068        ]
1069    );
1070
1071    // Remove a single channel
1072    db.remove_channel(crdb_id, a_id).await.unwrap();
1073    assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
1074
1075    // Remove a channel tree
1076    let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
1077    channel_ids.sort();
1078    assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
1079    assert_eq!(user_ids, &[a_id]);
1080
1081    assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
1082    assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
1083    assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
1084});
1085
1086test_both_dbs!(
1087    test_joining_channels_postgres,
1088    test_joining_channels_sqlite,
1089    db,
1090    {
1091        let owner_id = db.create_server("test").await.unwrap().0 as u32;
1092
1093        let user_1 = db
1094            .create_user(
1095                "user1@example.com",
1096                false,
1097                NewUserParams {
1098                    github_login: "user1".into(),
1099                    github_user_id: 5,
1100                    invite_count: 0,
1101                },
1102            )
1103            .await
1104            .unwrap()
1105            .user_id;
1106        let user_2 = db
1107            .create_user(
1108                "user2@example.com",
1109                false,
1110                NewUserParams {
1111                    github_login: "user2".into(),
1112                    github_user_id: 6,
1113                    invite_count: 0,
1114                },
1115            )
1116            .await
1117            .unwrap()
1118            .user_id;
1119
1120        let channel_1 = db
1121            .create_root_channel("channel_1", "1", user_1)
1122            .await
1123            .unwrap();
1124        let room_1 = db.room_id_for_channel(channel_1).await.unwrap();
1125
1126        // can join a room with membership to its channel
1127        let joined_room = db
1128            .join_room(
1129                room_1,
1130                user_1,
1131                Some(channel_1),
1132                ConnectionId { owner_id, id: 1 },
1133            )
1134            .await
1135            .unwrap();
1136        assert_eq!(joined_room.room.participants.len(), 1);
1137
1138        drop(joined_room);
1139        // cannot join a room without membership to its channel
1140        assert!(db
1141            .join_room(
1142                room_1,
1143                user_2,
1144                Some(channel_1),
1145                ConnectionId { owner_id, id: 1 }
1146            )
1147            .await
1148            .is_err());
1149    }
1150);
1151
1152test_both_dbs!(
1153    test_channel_invites_postgres,
1154    test_channel_invites_sqlite,
1155    db,
1156    {
1157        db.create_server("test").await.unwrap();
1158
1159        let user_1 = db
1160            .create_user(
1161                "user1@example.com",
1162                false,
1163                NewUserParams {
1164                    github_login: "user1".into(),
1165                    github_user_id: 5,
1166                    invite_count: 0,
1167                },
1168            )
1169            .await
1170            .unwrap()
1171            .user_id;
1172        let user_2 = db
1173            .create_user(
1174                "user2@example.com",
1175                false,
1176                NewUserParams {
1177                    github_login: "user2".into(),
1178                    github_user_id: 6,
1179                    invite_count: 0,
1180                },
1181            )
1182            .await
1183            .unwrap()
1184            .user_id;
1185
1186        let user_3 = db
1187            .create_user(
1188                "user3@example.com",
1189                false,
1190                NewUserParams {
1191                    github_login: "user3".into(),
1192                    github_user_id: 7,
1193                    invite_count: 0,
1194                },
1195            )
1196            .await
1197            .unwrap()
1198            .user_id;
1199
1200        let channel_1_1 = db
1201            .create_root_channel("channel_1", "1", user_1)
1202            .await
1203            .unwrap();
1204
1205        let channel_1_2 = db
1206            .create_root_channel("channel_2", "2", user_1)
1207            .await
1208            .unwrap();
1209
1210        db.invite_channel_member(channel_1_1, user_2, user_1, false)
1211            .await
1212            .unwrap();
1213        db.invite_channel_member(channel_1_2, user_2, user_1, false)
1214            .await
1215            .unwrap();
1216        db.invite_channel_member(channel_1_1, user_3, user_1, true)
1217            .await
1218            .unwrap();
1219
1220        let user_2_invites = db
1221            .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
1222            .await
1223            .unwrap()
1224            .into_iter()
1225            .map(|channel| channel.id)
1226            .collect::<Vec<_>>();
1227
1228        assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
1229
1230        let user_3_invites = db
1231            .get_channel_invites_for_user(user_3) // -> [channel_1_1]
1232            .await
1233            .unwrap()
1234            .into_iter()
1235            .map(|channel| channel.id)
1236            .collect::<Vec<_>>();
1237
1238        assert_eq!(user_3_invites, &[channel_1_1]);
1239
1240        let members = db
1241            .get_channel_member_details(channel_1_1, user_1)
1242            .await
1243            .unwrap();
1244        assert_eq!(
1245            members,
1246            &[
1247                proto::ChannelMember {
1248                    user_id: user_1.to_proto(),
1249                    kind: proto::channel_member::Kind::Member.into(),
1250                    admin: true,
1251                },
1252                proto::ChannelMember {
1253                    user_id: user_2.to_proto(),
1254                    kind: proto::channel_member::Kind::Invitee.into(),
1255                    admin: false,
1256                },
1257                proto::ChannelMember {
1258                    user_id: user_3.to_proto(),
1259                    kind: proto::channel_member::Kind::Invitee.into(),
1260                    admin: true,
1261                },
1262            ]
1263        );
1264
1265        db.respond_to_channel_invite(channel_1_1, user_2, true)
1266            .await
1267            .unwrap();
1268
1269        let channel_1_3 = db
1270            .create_channel("channel_3", Some(channel_1_1), "1", user_1)
1271            .await
1272            .unwrap();
1273
1274        let members = db
1275            .get_channel_member_details(channel_1_3, user_1)
1276            .await
1277            .unwrap();
1278        assert_eq!(
1279            members,
1280            &[
1281                proto::ChannelMember {
1282                    user_id: user_1.to_proto(),
1283                    kind: proto::channel_member::Kind::Member.into(),
1284                    admin: true,
1285                },
1286                proto::ChannelMember {
1287                    user_id: user_2.to_proto(),
1288                    kind: proto::channel_member::Kind::AncestorMember.into(),
1289                    admin: false,
1290                },
1291            ]
1292        );
1293    }
1294);
1295
1296#[gpui::test]
1297async fn test_multiple_signup_overwrite() {
1298    let test_db = TestDb::postgres(build_background_executor());
1299    let db = test_db.db();
1300
1301    let email_address = "user_1@example.com".to_string();
1302
1303    let initial_signup_created_at_milliseconds = 0;
1304
1305    let initial_signup = NewSignup {
1306        email_address: email_address.clone(),
1307        platform_mac: false,
1308        platform_linux: true,
1309        platform_windows: false,
1310        editor_features: vec!["speed".into()],
1311        programming_languages: vec!["rust".into(), "c".into()],
1312        device_id: Some(format!("device_id")),
1313        added_to_mailing_list: false,
1314        created_at: Some(
1315            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
1316        ),
1317    };
1318
1319    db.create_signup(&initial_signup).await.unwrap();
1320
1321    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
1322
1323    assert_eq!(
1324        initial_signup_from_db.clone(),
1325        signup::Model {
1326            email_address: initial_signup.email_address,
1327            platform_mac: initial_signup.platform_mac,
1328            platform_linux: initial_signup.platform_linux,
1329            platform_windows: initial_signup.platform_windows,
1330            editor_features: Some(initial_signup.editor_features),
1331            programming_languages: Some(initial_signup.programming_languages),
1332            added_to_mailing_list: initial_signup.added_to_mailing_list,
1333            ..initial_signup_from_db
1334        }
1335    );
1336
1337    let subsequent_signup = NewSignup {
1338        email_address: email_address.clone(),
1339        platform_mac: true,
1340        platform_linux: false,
1341        platform_windows: true,
1342        editor_features: vec!["git integration".into(), "clean design".into()],
1343        programming_languages: vec!["d".into(), "elm".into()],
1344        device_id: Some(format!("different_device_id")),
1345        added_to_mailing_list: true,
1346        // subsequent signup happens next day
1347        created_at: Some(
1348            DateTime::from_timestamp_millis(
1349                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
1350            )
1351            .unwrap(),
1352        ),
1353    };
1354
1355    db.create_signup(&subsequent_signup).await.unwrap();
1356
1357    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
1358
1359    assert_eq!(
1360        subsequent_signup_from_db.clone(),
1361        signup::Model {
1362            platform_mac: subsequent_signup.platform_mac,
1363            platform_linux: subsequent_signup.platform_linux,
1364            platform_windows: subsequent_signup.platform_windows,
1365            editor_features: Some(subsequent_signup.editor_features),
1366            programming_languages: Some(subsequent_signup.programming_languages),
1367            device_id: subsequent_signup.device_id,
1368            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
1369            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
1370            created_at: initial_signup_from_db.created_at,
1371            ..subsequent_signup_from_db
1372        }
1373    );
1374}
1375
1376#[gpui::test]
1377async fn test_signups() {
1378    let test_db = TestDb::postgres(build_background_executor());
1379    let db = test_db.db();
1380
1381    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
1382
1383    let all_signups = usernames
1384        .iter()
1385        .enumerate()
1386        .map(|(i, username)| NewSignup {
1387            email_address: format!("{username}@example.com"),
1388            platform_mac: true,
1389            platform_linux: i % 2 == 0,
1390            platform_windows: i % 4 == 0,
1391            editor_features: vec!["speed".into()],
1392            programming_languages: vec!["rust".into(), "c".into()],
1393            device_id: Some(format!("device_id_{i}")),
1394            added_to_mailing_list: i != 0, // One user failed to subscribe
1395            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
1396        })
1397        .collect::<Vec<NewSignup>>();
1398
1399    // people sign up on the waitlist
1400    for signup in &all_signups {
1401        // users can sign up multiple times without issues
1402        for _ in 0..2 {
1403            db.create_signup(&signup).await.unwrap();
1404        }
1405    }
1406
1407    assert_eq!(
1408        db.get_waitlist_summary().await.unwrap(),
1409        WaitlistSummary {
1410            count: 8,
1411            mac_count: 8,
1412            linux_count: 4,
1413            windows_count: 2,
1414            unknown_count: 0,
1415        }
1416    );
1417
1418    // retrieve the next batch of signup emails to send
1419    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1420    let addresses = signups_batch1
1421        .iter()
1422        .map(|s| &s.email_address)
1423        .collect::<Vec<_>>();
1424    assert_eq!(
1425        addresses,
1426        &[
1427            all_signups[0].email_address.as_str(),
1428            all_signups[1].email_address.as_str(),
1429            all_signups[2].email_address.as_str()
1430        ]
1431    );
1432    assert_ne!(
1433        signups_batch1[0].email_confirmation_code,
1434        signups_batch1[1].email_confirmation_code
1435    );
1436
1437    // the waitlist isn't updated until we record that the emails
1438    // were successfully sent.
1439    let signups_batch = db.get_unsent_invites(3).await.unwrap();
1440    assert_eq!(signups_batch, signups_batch1);
1441
1442    // once the emails go out, we can retrieve the next batch
1443    // of signups.
1444    db.record_sent_invites(&signups_batch1).await.unwrap();
1445    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1446    let addresses = signups_batch2
1447        .iter()
1448        .map(|s| &s.email_address)
1449        .collect::<Vec<_>>();
1450    assert_eq!(
1451        addresses,
1452        &[
1453            all_signups[3].email_address.as_str(),
1454            all_signups[4].email_address.as_str(),
1455            all_signups[5].email_address.as_str()
1456        ]
1457    );
1458
1459    // the sent invites are excluded from the summary.
1460    assert_eq!(
1461        db.get_waitlist_summary().await.unwrap(),
1462        WaitlistSummary {
1463            count: 5,
1464            mac_count: 5,
1465            linux_count: 2,
1466            windows_count: 1,
1467            unknown_count: 0,
1468        }
1469    );
1470
1471    // user completes the signup process by providing their
1472    // github account.
1473    let NewUserResult {
1474        user_id,
1475        inviting_user_id,
1476        signup_device_id,
1477        ..
1478    } = db
1479        .create_user_from_invite(
1480            &Invite {
1481                ..signups_batch1[0].clone()
1482            },
1483            NewUserParams {
1484                github_login: usernames[0].clone(),
1485                github_user_id: 0,
1486                invite_count: 5,
1487            },
1488        )
1489        .await
1490        .unwrap()
1491        .unwrap();
1492    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1493    assert!(inviting_user_id.is_none());
1494    assert_eq!(user.github_login, usernames[0]);
1495    assert_eq!(
1496        user.email_address,
1497        Some(all_signups[0].email_address.clone())
1498    );
1499    assert_eq!(user.invite_count, 5);
1500    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1501
1502    // cannot redeem the same signup again.
1503    assert!(db
1504        .create_user_from_invite(
1505            &Invite {
1506                email_address: signups_batch1[0].email_address.clone(),
1507                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1508            },
1509            NewUserParams {
1510                github_login: "some-other-github_account".into(),
1511                github_user_id: 1,
1512                invite_count: 5,
1513            },
1514        )
1515        .await
1516        .unwrap()
1517        .is_none());
1518
1519    // cannot redeem a signup with the wrong confirmation code.
1520    db.create_user_from_invite(
1521        &Invite {
1522            email_address: signups_batch1[1].email_address.clone(),
1523            email_confirmation_code: "the-wrong-code".to_string(),
1524        },
1525        NewUserParams {
1526            github_login: usernames[1].clone(),
1527            github_user_id: 2,
1528            invite_count: 5,
1529        },
1530    )
1531    .await
1532    .unwrap_err();
1533}
1534
1535fn build_background_executor() -> Arc<Background> {
1536    Deterministic::new(0).build_background()
1537}