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, true)
 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(a_id).await.unwrap();
 955
 956    assert_eq!(
 957        channels,
 958        vec![
 959            Channel {
 960                id: zed_id,
 961                name: "zed".to_string(),
 962                parent_id: None,
 963            },
 964            Channel {
 965                id: crdb_id,
 966                name: "crdb".to_string(),
 967                parent_id: Some(zed_id),
 968            },
 969            Channel {
 970                id: livestreaming_id,
 971                name: "livestreaming".to_string(),
 972                parent_id: Some(zed_id),
 973            },
 974            Channel {
 975                id: replace_id,
 976                name: "replace".to_string(),
 977                parent_id: Some(zed_id),
 978            },
 979            Channel {
 980                id: rust_id,
 981                name: "rust".to_string(),
 982                parent_id: None,
 983            },
 984            Channel {
 985                id: cargo_id,
 986                name: "cargo".to_string(),
 987                parent_id: Some(rust_id),
 988            },
 989            Channel {
 990                id: cargo_ra_id,
 991                name: "cargo-ra".to_string(),
 992                parent_id: Some(cargo_id),
 993            }
 994        ]
 995    );
 996
 997    // Remove a single channel
 998    db.remove_channel(crdb_id, a_id).await.unwrap();
 999    assert!(db.get_channel(crdb_id).await.unwrap().is_none());
1000
1001    // Remove a channel tree
1002    let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
1003    channel_ids.sort();
1004    assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
1005    assert_eq!(user_ids, &[a_id]);
1006
1007    assert!(db.get_channel(rust_id).await.unwrap().is_none());
1008    assert!(db.get_channel(cargo_id).await.unwrap().is_none());
1009    assert!(db.get_channel(cargo_ra_id).await.unwrap().is_none());
1010});
1011
1012test_both_dbs!(
1013    test_joining_channels_postgres,
1014    test_joining_channels_sqlite,
1015    db,
1016    {
1017        let owner_id = db.create_server("test").await.unwrap().0 as u32;
1018
1019        let user_1 = db
1020            .create_user(
1021                "user1@example.com",
1022                false,
1023                NewUserParams {
1024                    github_login: "user1".into(),
1025                    github_user_id: 5,
1026                    invite_count: 0,
1027                },
1028            )
1029            .await
1030            .unwrap()
1031            .user_id;
1032        let user_2 = db
1033            .create_user(
1034                "user2@example.com",
1035                false,
1036                NewUserParams {
1037                    github_login: "user2".into(),
1038                    github_user_id: 6,
1039                    invite_count: 0,
1040                },
1041            )
1042            .await
1043            .unwrap()
1044            .user_id;
1045
1046        let channel_1 = db
1047            .create_root_channel("channel_1", "1", user_1)
1048            .await
1049            .unwrap();
1050        let room_1 = db.get_channel_room(channel_1).await.unwrap();
1051
1052        // can join a room with membership to its channel
1053        let room = db
1054            .join_room(
1055                room_1,
1056                user_1,
1057                Some(channel_1),
1058                ConnectionId { owner_id, id: 1 },
1059            )
1060            .await
1061            .unwrap();
1062        assert_eq!(room.participants.len(), 1);
1063
1064        drop(room);
1065        // cannot join a room without membership to its channel
1066        assert!(db
1067            .join_room(
1068                room_1,
1069                user_2,
1070                Some(channel_1),
1071                ConnectionId { owner_id, id: 1 }
1072            )
1073            .await
1074            .is_err());
1075    }
1076);
1077
1078test_both_dbs!(
1079    test_channel_invites_postgres,
1080    test_channel_invites_sqlite,
1081    db,
1082    {
1083        let owner_id = db.create_server("test").await.unwrap().0 as u32;
1084
1085        let user_1 = db
1086            .create_user(
1087                "user1@example.com",
1088                false,
1089                NewUserParams {
1090                    github_login: "user1".into(),
1091                    github_user_id: 5,
1092                    invite_count: 0,
1093                },
1094            )
1095            .await
1096            .unwrap()
1097            .user_id;
1098        let user_2 = db
1099            .create_user(
1100                "user2@example.com",
1101                false,
1102                NewUserParams {
1103                    github_login: "user2".into(),
1104                    github_user_id: 6,
1105                    invite_count: 0,
1106                },
1107            )
1108            .await
1109            .unwrap()
1110            .user_id;
1111
1112        let user_3 = db
1113            .create_user(
1114                "user3@example.com",
1115                false,
1116                NewUserParams {
1117                    github_login: "user3".into(),
1118                    github_user_id: 7,
1119                    invite_count: 0,
1120                },
1121            )
1122            .await
1123            .unwrap()
1124            .user_id;
1125
1126        let channel_1_1 = db
1127            .create_root_channel("channel_1", "1", user_1)
1128            .await
1129            .unwrap();
1130
1131        let channel_1_2 = db
1132            .create_root_channel("channel_2", "2", user_1)
1133            .await
1134            .unwrap();
1135
1136        db.invite_channel_member(channel_1_1, user_2, user_1, false)
1137            .await
1138            .unwrap();
1139        db.invite_channel_member(channel_1_2, user_2, user_1, false)
1140            .await
1141            .unwrap();
1142        db.invite_channel_member(channel_1_1, user_3, user_1, false)
1143            .await
1144            .unwrap();
1145
1146        let user_2_invites = db
1147            .get_channel_invites(user_2) // -> [channel_1_1, channel_1_2]
1148            .await
1149            .unwrap()
1150            .into_iter()
1151            .map(|channel| channel.id)
1152            .collect::<Vec<_>>();
1153
1154        assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
1155
1156        let user_3_invites = db
1157            .get_channel_invites(user_3) // -> [channel_1_1]
1158            .await
1159            .unwrap()
1160            .into_iter()
1161            .map(|channel| channel.id)
1162            .collect::<Vec<_>>();
1163
1164        assert_eq!(user_3_invites, &[channel_1_1])
1165    }
1166);
1167
1168#[gpui::test]
1169async fn test_multiple_signup_overwrite() {
1170    let test_db = TestDb::postgres(build_background_executor());
1171    let db = test_db.db();
1172
1173    let email_address = "user_1@example.com".to_string();
1174
1175    let initial_signup_created_at_milliseconds = 0;
1176
1177    let initial_signup = NewSignup {
1178        email_address: email_address.clone(),
1179        platform_mac: false,
1180        platform_linux: true,
1181        platform_windows: false,
1182        editor_features: vec!["speed".into()],
1183        programming_languages: vec!["rust".into(), "c".into()],
1184        device_id: Some(format!("device_id")),
1185        added_to_mailing_list: false,
1186        created_at: Some(
1187            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
1188        ),
1189    };
1190
1191    db.create_signup(&initial_signup).await.unwrap();
1192
1193    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
1194
1195    assert_eq!(
1196        initial_signup_from_db.clone(),
1197        signup::Model {
1198            email_address: initial_signup.email_address,
1199            platform_mac: initial_signup.platform_mac,
1200            platform_linux: initial_signup.platform_linux,
1201            platform_windows: initial_signup.platform_windows,
1202            editor_features: Some(initial_signup.editor_features),
1203            programming_languages: Some(initial_signup.programming_languages),
1204            added_to_mailing_list: initial_signup.added_to_mailing_list,
1205            ..initial_signup_from_db
1206        }
1207    );
1208
1209    let subsequent_signup = NewSignup {
1210        email_address: email_address.clone(),
1211        platform_mac: true,
1212        platform_linux: false,
1213        platform_windows: true,
1214        editor_features: vec!["git integration".into(), "clean design".into()],
1215        programming_languages: vec!["d".into(), "elm".into()],
1216        device_id: Some(format!("different_device_id")),
1217        added_to_mailing_list: true,
1218        // subsequent signup happens next day
1219        created_at: Some(
1220            DateTime::from_timestamp_millis(
1221                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
1222            )
1223            .unwrap(),
1224        ),
1225    };
1226
1227    db.create_signup(&subsequent_signup).await.unwrap();
1228
1229    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
1230
1231    assert_eq!(
1232        subsequent_signup_from_db.clone(),
1233        signup::Model {
1234            platform_mac: subsequent_signup.platform_mac,
1235            platform_linux: subsequent_signup.platform_linux,
1236            platform_windows: subsequent_signup.platform_windows,
1237            editor_features: Some(subsequent_signup.editor_features),
1238            programming_languages: Some(subsequent_signup.programming_languages),
1239            device_id: subsequent_signup.device_id,
1240            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
1241            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
1242            created_at: initial_signup_from_db.created_at,
1243            ..subsequent_signup_from_db
1244        }
1245    );
1246}
1247
1248#[gpui::test]
1249async fn test_signups() {
1250    let test_db = TestDb::postgres(build_background_executor());
1251    let db = test_db.db();
1252
1253    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
1254
1255    let all_signups = usernames
1256        .iter()
1257        .enumerate()
1258        .map(|(i, username)| NewSignup {
1259            email_address: format!("{username}@example.com"),
1260            platform_mac: true,
1261            platform_linux: i % 2 == 0,
1262            platform_windows: i % 4 == 0,
1263            editor_features: vec!["speed".into()],
1264            programming_languages: vec!["rust".into(), "c".into()],
1265            device_id: Some(format!("device_id_{i}")),
1266            added_to_mailing_list: i != 0, // One user failed to subscribe
1267            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
1268        })
1269        .collect::<Vec<NewSignup>>();
1270
1271    // people sign up on the waitlist
1272    for signup in &all_signups {
1273        // users can sign up multiple times without issues
1274        for _ in 0..2 {
1275            db.create_signup(&signup).await.unwrap();
1276        }
1277    }
1278
1279    assert_eq!(
1280        db.get_waitlist_summary().await.unwrap(),
1281        WaitlistSummary {
1282            count: 8,
1283            mac_count: 8,
1284            linux_count: 4,
1285            windows_count: 2,
1286            unknown_count: 0,
1287        }
1288    );
1289
1290    // retrieve the next batch of signup emails to send
1291    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1292    let addresses = signups_batch1
1293        .iter()
1294        .map(|s| &s.email_address)
1295        .collect::<Vec<_>>();
1296    assert_eq!(
1297        addresses,
1298        &[
1299            all_signups[0].email_address.as_str(),
1300            all_signups[1].email_address.as_str(),
1301            all_signups[2].email_address.as_str()
1302        ]
1303    );
1304    assert_ne!(
1305        signups_batch1[0].email_confirmation_code,
1306        signups_batch1[1].email_confirmation_code
1307    );
1308
1309    // the waitlist isn't updated until we record that the emails
1310    // were successfully sent.
1311    let signups_batch = db.get_unsent_invites(3).await.unwrap();
1312    assert_eq!(signups_batch, signups_batch1);
1313
1314    // once the emails go out, we can retrieve the next batch
1315    // of signups.
1316    db.record_sent_invites(&signups_batch1).await.unwrap();
1317    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1318    let addresses = signups_batch2
1319        .iter()
1320        .map(|s| &s.email_address)
1321        .collect::<Vec<_>>();
1322    assert_eq!(
1323        addresses,
1324        &[
1325            all_signups[3].email_address.as_str(),
1326            all_signups[4].email_address.as_str(),
1327            all_signups[5].email_address.as_str()
1328        ]
1329    );
1330
1331    // the sent invites are excluded from the summary.
1332    assert_eq!(
1333        db.get_waitlist_summary().await.unwrap(),
1334        WaitlistSummary {
1335            count: 5,
1336            mac_count: 5,
1337            linux_count: 2,
1338            windows_count: 1,
1339            unknown_count: 0,
1340        }
1341    );
1342
1343    // user completes the signup process by providing their
1344    // github account.
1345    let NewUserResult {
1346        user_id,
1347        inviting_user_id,
1348        signup_device_id,
1349        ..
1350    } = db
1351        .create_user_from_invite(
1352            &Invite {
1353                ..signups_batch1[0].clone()
1354            },
1355            NewUserParams {
1356                github_login: usernames[0].clone(),
1357                github_user_id: 0,
1358                invite_count: 5,
1359            },
1360        )
1361        .await
1362        .unwrap()
1363        .unwrap();
1364    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1365    assert!(inviting_user_id.is_none());
1366    assert_eq!(user.github_login, usernames[0]);
1367    assert_eq!(
1368        user.email_address,
1369        Some(all_signups[0].email_address.clone())
1370    );
1371    assert_eq!(user.invite_count, 5);
1372    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1373
1374    // cannot redeem the same signup again.
1375    assert!(db
1376        .create_user_from_invite(
1377            &Invite {
1378                email_address: signups_batch1[0].email_address.clone(),
1379                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1380            },
1381            NewUserParams {
1382                github_login: "some-other-github_account".into(),
1383                github_user_id: 1,
1384                invite_count: 5,
1385            },
1386        )
1387        .await
1388        .unwrap()
1389        .is_none());
1390
1391    // cannot redeem a signup with the wrong confirmation code.
1392    db.create_user_from_invite(
1393        &Invite {
1394            email_address: signups_batch1[1].email_address.clone(),
1395            email_confirmation_code: "the-wrong-code".to_string(),
1396        },
1397        NewUserParams {
1398            github_login: usernames[1].clone(),
1399            github_user_id: 2,
1400            invite_count: 5,
1401        },
1402    )
1403    .await
1404    .unwrap_err();
1405}
1406
1407fn build_background_executor() -> Arc<Background> {
1408    Deterministic::new(0).build_background()
1409}