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_user_by_github_account_postgres,
  96    test_get_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_user_by_github_account("login1", 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_user_by_github_account("non-existent-login", None)
 137            .await
 138            .unwrap()
 139            .is_none());
 140
 141        let user = db
 142            .get_user_by_github_account("the-new-login2", Some(102))
 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);
 151
 152test_both_dbs!(
 153    test_create_access_tokens_postgres,
 154    test_create_access_tokens_sqlite,
 155    db,
 156    {
 157        let user = db
 158            .create_user(
 159                "u1@example.com",
 160                false,
 161                NewUserParams {
 162                    github_login: "u1".into(),
 163                    github_user_id: 1,
 164                    invite_count: 0,
 165                },
 166            )
 167            .await
 168            .unwrap()
 169            .user_id;
 170
 171        db.create_access_token_hash(user, "h1", 3).await.unwrap();
 172        db.create_access_token_hash(user, "h2", 3).await.unwrap();
 173        assert_eq!(
 174            db.get_access_token_hashes(user).await.unwrap(),
 175            &["h2".to_string(), "h1".to_string()]
 176        );
 177
 178        db.create_access_token_hash(user, "h3", 3).await.unwrap();
 179        assert_eq!(
 180            db.get_access_token_hashes(user).await.unwrap(),
 181            &["h3".to_string(), "h2".to_string(), "h1".to_string(),]
 182        );
 183
 184        db.create_access_token_hash(user, "h4", 3).await.unwrap();
 185        assert_eq!(
 186            db.get_access_token_hashes(user).await.unwrap(),
 187            &["h4".to_string(), "h3".to_string(), "h2".to_string(),]
 188        );
 189
 190        db.create_access_token_hash(user, "h5", 3).await.unwrap();
 191        assert_eq!(
 192            db.get_access_token_hashes(user).await.unwrap(),
 193            &["h5".to_string(), "h4".to_string(), "h3".to_string()]
 194        );
 195    }
 196);
 197
 198test_both_dbs!(test_add_contacts_postgres, test_add_contacts_sqlite, db, {
 199    let mut user_ids = Vec::new();
 200    for i in 0..3 {
 201        user_ids.push(
 202            db.create_user(
 203                &format!("user{i}@example.com"),
 204                false,
 205                NewUserParams {
 206                    github_login: format!("user{i}"),
 207                    github_user_id: i,
 208                    invite_count: 0,
 209                },
 210            )
 211            .await
 212            .unwrap()
 213            .user_id,
 214        );
 215    }
 216
 217    let user_1 = user_ids[0];
 218    let user_2 = user_ids[1];
 219    let user_3 = user_ids[2];
 220
 221    // User starts with no contacts
 222    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
 223
 224    // User requests a contact. Both users see the pending request.
 225    db.send_contact_request(user_1, user_2).await.unwrap();
 226    assert!(!db.has_contact(user_1, user_2).await.unwrap());
 227    assert!(!db.has_contact(user_2, user_1).await.unwrap());
 228    assert_eq!(
 229        db.get_contacts(user_1).await.unwrap(),
 230        &[Contact::Outgoing { user_id: user_2 }],
 231    );
 232    assert_eq!(
 233        db.get_contacts(user_2).await.unwrap(),
 234        &[Contact::Incoming {
 235            user_id: user_1,
 236            should_notify: true
 237        }]
 238    );
 239
 240    // User 2 dismisses the contact request notification without accepting or rejecting.
 241    // We shouldn't notify them again.
 242    db.dismiss_contact_notification(user_1, user_2)
 243        .await
 244        .unwrap_err();
 245    db.dismiss_contact_notification(user_2, user_1)
 246        .await
 247        .unwrap();
 248    assert_eq!(
 249        db.get_contacts(user_2).await.unwrap(),
 250        &[Contact::Incoming {
 251            user_id: user_1,
 252            should_notify: false
 253        }]
 254    );
 255
 256    // User can't accept their own contact request
 257    db.respond_to_contact_request(user_1, user_2, true)
 258        .await
 259        .unwrap_err();
 260
 261    // User accepts a contact request. Both users see the contact.
 262    db.respond_to_contact_request(user_2, user_1, true)
 263        .await
 264        .unwrap();
 265    assert_eq!(
 266        db.get_contacts(user_1).await.unwrap(),
 267        &[Contact::Accepted {
 268            user_id: user_2,
 269            should_notify: true,
 270            busy: false,
 271        }],
 272    );
 273    assert!(db.has_contact(user_1, user_2).await.unwrap());
 274    assert!(db.has_contact(user_2, user_1).await.unwrap());
 275    assert_eq!(
 276        db.get_contacts(user_2).await.unwrap(),
 277        &[Contact::Accepted {
 278            user_id: user_1,
 279            should_notify: false,
 280            busy: false,
 281        }]
 282    );
 283
 284    // Users cannot re-request existing contacts.
 285    db.send_contact_request(user_1, user_2).await.unwrap_err();
 286    db.send_contact_request(user_2, user_1).await.unwrap_err();
 287
 288    // Users can't dismiss notifications of them accepting other users' requests.
 289    db.dismiss_contact_notification(user_2, user_1)
 290        .await
 291        .unwrap_err();
 292    assert_eq!(
 293        db.get_contacts(user_1).await.unwrap(),
 294        &[Contact::Accepted {
 295            user_id: user_2,
 296            should_notify: true,
 297            busy: false,
 298        }]
 299    );
 300
 301    // Users can dismiss notifications of other users accepting their requests.
 302    db.dismiss_contact_notification(user_1, user_2)
 303        .await
 304        .unwrap();
 305    assert_eq!(
 306        db.get_contacts(user_1).await.unwrap(),
 307        &[Contact::Accepted {
 308            user_id: user_2,
 309            should_notify: false,
 310            busy: false,
 311        }]
 312    );
 313
 314    // Users send each other concurrent contact requests and
 315    // see that they are immediately accepted.
 316    db.send_contact_request(user_1, user_3).await.unwrap();
 317    db.send_contact_request(user_3, user_1).await.unwrap();
 318    assert_eq!(
 319        db.get_contacts(user_1).await.unwrap(),
 320        &[
 321            Contact::Accepted {
 322                user_id: user_2,
 323                should_notify: false,
 324                busy: false,
 325            },
 326            Contact::Accepted {
 327                user_id: user_3,
 328                should_notify: false,
 329                busy: false,
 330            }
 331        ]
 332    );
 333    assert_eq!(
 334        db.get_contacts(user_3).await.unwrap(),
 335        &[Contact::Accepted {
 336            user_id: user_1,
 337            should_notify: false,
 338            busy: false,
 339        }],
 340    );
 341
 342    // User declines a contact request. Both users see that it is gone.
 343    db.send_contact_request(user_2, user_3).await.unwrap();
 344    db.respond_to_contact_request(user_3, user_2, false)
 345        .await
 346        .unwrap();
 347    assert!(!db.has_contact(user_2, user_3).await.unwrap());
 348    assert!(!db.has_contact(user_3, user_2).await.unwrap());
 349    assert_eq!(
 350        db.get_contacts(user_2).await.unwrap(),
 351        &[Contact::Accepted {
 352            user_id: user_1,
 353            should_notify: false,
 354            busy: false,
 355        }]
 356    );
 357    assert_eq!(
 358        db.get_contacts(user_3).await.unwrap(),
 359        &[Contact::Accepted {
 360            user_id: user_1,
 361            should_notify: false,
 362            busy: false,
 363        }],
 364    );
 365});
 366
 367test_both_dbs!(test_metrics_id_postgres, test_metrics_id_sqlite, db, {
 368    let NewUserResult {
 369        user_id: user1,
 370        metrics_id: metrics_id1,
 371        ..
 372    } = db
 373        .create_user(
 374            "person1@example.com",
 375            false,
 376            NewUserParams {
 377                github_login: "person1".into(),
 378                github_user_id: 101,
 379                invite_count: 5,
 380            },
 381        )
 382        .await
 383        .unwrap();
 384    let NewUserResult {
 385        user_id: user2,
 386        metrics_id: metrics_id2,
 387        ..
 388    } = db
 389        .create_user(
 390            "person2@example.com",
 391            false,
 392            NewUserParams {
 393                github_login: "person2".into(),
 394                github_user_id: 102,
 395                invite_count: 5,
 396            },
 397        )
 398        .await
 399        .unwrap();
 400
 401    assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
 402    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
 403    assert_eq!(metrics_id1.len(), 36);
 404    assert_eq!(metrics_id2.len(), 36);
 405    assert_ne!(metrics_id1, metrics_id2);
 406});
 407
 408test_both_dbs!(
 409    test_project_count_postgres,
 410    test_project_count_sqlite,
 411    db,
 412    {
 413        let owner_id = db.create_server("test").await.unwrap().0 as u32;
 414
 415        let user1 = db
 416            .create_user(
 417                &format!("admin@example.com"),
 418                true,
 419                NewUserParams {
 420                    github_login: "admin".into(),
 421                    github_user_id: 0,
 422                    invite_count: 0,
 423                },
 424            )
 425            .await
 426            .unwrap();
 427        let user2 = db
 428            .create_user(
 429                &format!("user@example.com"),
 430                false,
 431                NewUserParams {
 432                    github_login: "user".into(),
 433                    github_user_id: 1,
 434                    invite_count: 0,
 435                },
 436            )
 437            .await
 438            .unwrap();
 439
 440        let room_id = RoomId::from_proto(
 441            db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
 442                .await
 443                .unwrap()
 444                .id,
 445        );
 446        db.call(
 447            room_id,
 448            user1.user_id,
 449            ConnectionId { owner_id, id: 0 },
 450            user2.user_id,
 451            None,
 452        )
 453        .await
 454        .unwrap();
 455        db.join_room(room_id, user2.user_id, ConnectionId { owner_id, id: 1 })
 456            .await
 457            .unwrap();
 458        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 459
 460        db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 461            .await
 462            .unwrap();
 463        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
 464
 465        db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 466            .await
 467            .unwrap();
 468        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 469
 470        // Projects shared by admins aren't counted.
 471        db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
 472            .await
 473            .unwrap();
 474        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 475
 476        db.leave_room(ConnectionId { owner_id, id: 1 })
 477            .await
 478            .unwrap();
 479        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 480    }
 481);
 482
 483#[test]
 484fn test_fuzzy_like_string() {
 485    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
 486    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
 487    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
 488}
 489
 490#[gpui::test]
 491async fn test_fuzzy_search_users() {
 492    let test_db = TestDb::postgres(build_background_executor());
 493    let db = test_db.db();
 494    for (i, github_login) in [
 495        "California",
 496        "colorado",
 497        "oregon",
 498        "washington",
 499        "florida",
 500        "delaware",
 501        "rhode-island",
 502    ]
 503    .into_iter()
 504    .enumerate()
 505    {
 506        db.create_user(
 507            &format!("{github_login}@example.com"),
 508            false,
 509            NewUserParams {
 510                github_login: github_login.into(),
 511                github_user_id: i as i32,
 512                invite_count: 0,
 513            },
 514        )
 515        .await
 516        .unwrap();
 517    }
 518
 519    assert_eq!(
 520        fuzzy_search_user_names(db, "clr").await,
 521        &["colorado", "California"]
 522    );
 523    assert_eq!(
 524        fuzzy_search_user_names(db, "ro").await,
 525        &["rhode-island", "colorado", "oregon"],
 526    );
 527
 528    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
 529        db.fuzzy_search_users(query, 10)
 530            .await
 531            .unwrap()
 532            .into_iter()
 533            .map(|user| user.github_login)
 534            .collect::<Vec<_>>()
 535    }
 536}
 537
 538#[gpui::test]
 539async fn test_invite_codes() {
 540    let test_db = TestDb::postgres(build_background_executor());
 541    let db = test_db.db();
 542
 543    let NewUserResult { user_id: user1, .. } = db
 544        .create_user(
 545            "user1@example.com",
 546            false,
 547            NewUserParams {
 548                github_login: "user1".into(),
 549                github_user_id: 0,
 550                invite_count: 0,
 551            },
 552        )
 553        .await
 554        .unwrap();
 555
 556    // Initially, user 1 has no invite code
 557    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 558
 559    // Setting invite count to 0 when no code is assigned does not assign a new code
 560    db.set_invite_count_for_user(user1, 0).await.unwrap();
 561    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 562
 563    // User 1 creates an invite code that can be used twice.
 564    db.set_invite_count_for_user(user1, 2).await.unwrap();
 565    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 566    assert_eq!(invite_count, 2);
 567
 568    // User 2 redeems the invite code and becomes a contact of user 1.
 569    let user2_invite = db
 570        .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
 571        .await
 572        .unwrap();
 573    let NewUserResult {
 574        user_id: user2,
 575        inviting_user_id,
 576        signup_device_id,
 577        metrics_id,
 578    } = db
 579        .create_user_from_invite(
 580            &user2_invite,
 581            NewUserParams {
 582                github_login: "user2".into(),
 583                github_user_id: 2,
 584                invite_count: 7,
 585            },
 586        )
 587        .await
 588        .unwrap()
 589        .unwrap();
 590    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 591    assert_eq!(invite_count, 1);
 592    assert_eq!(inviting_user_id, Some(user1));
 593    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 594    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 595    assert_eq!(
 596        db.get_contacts(user1).await.unwrap(),
 597        [Contact::Accepted {
 598            user_id: user2,
 599            should_notify: true,
 600            busy: false,
 601        }]
 602    );
 603    assert_eq!(
 604        db.get_contacts(user2).await.unwrap(),
 605        [Contact::Accepted {
 606            user_id: user1,
 607            should_notify: false,
 608            busy: false,
 609        }]
 610    );
 611    assert!(db.has_contact(user1, user2).await.unwrap());
 612    assert!(db.has_contact(user2, user1).await.unwrap());
 613    assert_eq!(
 614        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 615        7
 616    );
 617
 618    // User 3 redeems the invite code and becomes a contact of user 1.
 619    let user3_invite = db
 620        .create_invite_from_code(&invite_code, "user3@example.com", None)
 621        .await
 622        .unwrap();
 623    let NewUserResult {
 624        user_id: user3,
 625        inviting_user_id,
 626        signup_device_id,
 627        ..
 628    } = db
 629        .create_user_from_invite(
 630            &user3_invite,
 631            NewUserParams {
 632                github_login: "user-3".into(),
 633                github_user_id: 3,
 634                invite_count: 3,
 635            },
 636        )
 637        .await
 638        .unwrap()
 639        .unwrap();
 640    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 641    assert_eq!(invite_count, 0);
 642    assert_eq!(inviting_user_id, Some(user1));
 643    assert!(signup_device_id.is_none());
 644    assert_eq!(
 645        db.get_contacts(user1).await.unwrap(),
 646        [
 647            Contact::Accepted {
 648                user_id: user2,
 649                should_notify: true,
 650                busy: false,
 651            },
 652            Contact::Accepted {
 653                user_id: user3,
 654                should_notify: true,
 655                busy: false,
 656            }
 657        ]
 658    );
 659    assert_eq!(
 660        db.get_contacts(user3).await.unwrap(),
 661        [Contact::Accepted {
 662            user_id: user1,
 663            should_notify: false,
 664            busy: false,
 665        }]
 666    );
 667    assert!(db.has_contact(user1, user3).await.unwrap());
 668    assert!(db.has_contact(user3, user1).await.unwrap());
 669    assert_eq!(
 670        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 671        3
 672    );
 673
 674    // Trying to reedem the code for the third time results in an error.
 675    db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
 676        .await
 677        .unwrap_err();
 678
 679    // Invite count can be updated after the code has been created.
 680    db.set_invite_count_for_user(user1, 2).await.unwrap();
 681    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 682    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 683    assert_eq!(invite_count, 2);
 684
 685    // User 4 can now redeem the invite code and becomes a contact of user 1.
 686    let user4_invite = db
 687        .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
 688        .await
 689        .unwrap();
 690    let user4 = db
 691        .create_user_from_invite(
 692            &user4_invite,
 693            NewUserParams {
 694                github_login: "user-4".into(),
 695                github_user_id: 4,
 696                invite_count: 5,
 697            },
 698        )
 699        .await
 700        .unwrap()
 701        .unwrap()
 702        .user_id;
 703
 704    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 705    assert_eq!(invite_count, 1);
 706    assert_eq!(
 707        db.get_contacts(user1).await.unwrap(),
 708        [
 709            Contact::Accepted {
 710                user_id: user2,
 711                should_notify: true,
 712                busy: false,
 713            },
 714            Contact::Accepted {
 715                user_id: user3,
 716                should_notify: true,
 717                busy: false,
 718            },
 719            Contact::Accepted {
 720                user_id: user4,
 721                should_notify: true,
 722                busy: false,
 723            }
 724        ]
 725    );
 726    assert_eq!(
 727        db.get_contacts(user4).await.unwrap(),
 728        [Contact::Accepted {
 729            user_id: user1,
 730            should_notify: false,
 731            busy: false,
 732        }]
 733    );
 734    assert!(db.has_contact(user1, user4).await.unwrap());
 735    assert!(db.has_contact(user4, user1).await.unwrap());
 736    assert_eq!(
 737        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 738        5
 739    );
 740
 741    // An existing user cannot redeem invite codes.
 742    db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
 743        .await
 744        .unwrap_err();
 745    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 746    assert_eq!(invite_count, 1);
 747
 748    // A newer user can invite an existing one via a different email address
 749    // than the one they used to sign up.
 750    let user5 = db
 751        .create_user(
 752            "user5@example.com",
 753            false,
 754            NewUserParams {
 755                github_login: "user5".into(),
 756                github_user_id: 5,
 757                invite_count: 0,
 758            },
 759        )
 760        .await
 761        .unwrap()
 762        .user_id;
 763    db.set_invite_count_for_user(user5, 5).await.unwrap();
 764    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 765    let user5_invite_to_user1 = db
 766        .create_invite_from_code(&user5_invite_code, "user1@different.com", None)
 767        .await
 768        .unwrap();
 769    let user1_2 = db
 770        .create_user_from_invite(
 771            &user5_invite_to_user1,
 772            NewUserParams {
 773                github_login: "user1".into(),
 774                github_user_id: 1,
 775                invite_count: 5,
 776            },
 777        )
 778        .await
 779        .unwrap()
 780        .unwrap()
 781        .user_id;
 782    assert_eq!(user1_2, user1);
 783    assert_eq!(
 784        db.get_contacts(user1).await.unwrap(),
 785        [
 786            Contact::Accepted {
 787                user_id: user2,
 788                should_notify: true,
 789                busy: false,
 790            },
 791            Contact::Accepted {
 792                user_id: user3,
 793                should_notify: true,
 794                busy: false,
 795            },
 796            Contact::Accepted {
 797                user_id: user4,
 798                should_notify: true,
 799                busy: false,
 800            },
 801            Contact::Accepted {
 802                user_id: user5,
 803                should_notify: false,
 804                busy: false,
 805            }
 806        ]
 807    );
 808    assert_eq!(
 809        db.get_contacts(user5).await.unwrap(),
 810        [Contact::Accepted {
 811            user_id: user1,
 812            should_notify: true,
 813            busy: false,
 814        }]
 815    );
 816    assert!(db.has_contact(user1, user5).await.unwrap());
 817    assert!(db.has_contact(user5, user1).await.unwrap());
 818}
 819
 820#[gpui::test]
 821async fn test_multiple_signup_overwrite() {
 822    let test_db = TestDb::postgres(build_background_executor());
 823    let db = test_db.db();
 824
 825    let email_address = "user_1@example.com".to_string();
 826
 827    let initial_signup_created_at_milliseconds = 0;
 828
 829    let initial_signup = NewSignup {
 830        email_address: email_address.clone(),
 831        platform_mac: false,
 832        platform_linux: true,
 833        platform_windows: false,
 834        editor_features: vec!["speed".into()],
 835        programming_languages: vec!["rust".into(), "c".into()],
 836        device_id: Some(format!("device_id")),
 837        added_to_mailing_list: false,
 838        created_at: Some(
 839            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
 840        ),
 841    };
 842
 843    db.create_signup(&initial_signup).await.unwrap();
 844
 845    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
 846
 847    assert_eq!(
 848        initial_signup_from_db.clone(),
 849        signup::Model {
 850            email_address: initial_signup.email_address,
 851            platform_mac: initial_signup.platform_mac,
 852            platform_linux: initial_signup.platform_linux,
 853            platform_windows: initial_signup.platform_windows,
 854            editor_features: Some(initial_signup.editor_features),
 855            programming_languages: Some(initial_signup.programming_languages),
 856            added_to_mailing_list: initial_signup.added_to_mailing_list,
 857            ..initial_signup_from_db
 858        }
 859    );
 860
 861    let subsequent_signup = NewSignup {
 862        email_address: email_address.clone(),
 863        platform_mac: true,
 864        platform_linux: false,
 865        platform_windows: true,
 866        editor_features: vec!["git integration".into(), "clean design".into()],
 867        programming_languages: vec!["d".into(), "elm".into()],
 868        device_id: Some(format!("different_device_id")),
 869        added_to_mailing_list: true,
 870        // subsequent signup happens next day
 871        created_at: Some(
 872            DateTime::from_timestamp_millis(
 873                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
 874            )
 875            .unwrap(),
 876        ),
 877    };
 878
 879    db.create_signup(&subsequent_signup).await.unwrap();
 880
 881    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
 882
 883    assert_eq!(
 884        subsequent_signup_from_db.clone(),
 885        signup::Model {
 886            platform_mac: subsequent_signup.platform_mac,
 887            platform_linux: subsequent_signup.platform_linux,
 888            platform_windows: subsequent_signup.platform_windows,
 889            editor_features: Some(subsequent_signup.editor_features),
 890            programming_languages: Some(subsequent_signup.programming_languages),
 891            device_id: subsequent_signup.device_id,
 892            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
 893            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
 894            created_at: initial_signup_from_db.created_at,
 895            ..subsequent_signup_from_db
 896        }
 897    );
 898}
 899
 900#[gpui::test]
 901async fn test_signups() {
 902    let test_db = TestDb::postgres(build_background_executor());
 903    let db = test_db.db();
 904
 905    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
 906
 907    let all_signups = usernames
 908        .iter()
 909        .enumerate()
 910        .map(|(i, username)| NewSignup {
 911            email_address: format!("{username}@example.com"),
 912            platform_mac: true,
 913            platform_linux: i % 2 == 0,
 914            platform_windows: i % 4 == 0,
 915            editor_features: vec!["speed".into()],
 916            programming_languages: vec!["rust".into(), "c".into()],
 917            device_id: Some(format!("device_id_{i}")),
 918            added_to_mailing_list: i != 0, // One user failed to subscribe
 919            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
 920        })
 921        .collect::<Vec<NewSignup>>();
 922
 923    // people sign up on the waitlist
 924    for signup in &all_signups {
 925        // users can sign up multiple times without issues
 926        for _ in 0..2 {
 927            db.create_signup(&signup).await.unwrap();
 928        }
 929    }
 930
 931    assert_eq!(
 932        db.get_waitlist_summary().await.unwrap(),
 933        WaitlistSummary {
 934            count: 8,
 935            mac_count: 8,
 936            linux_count: 4,
 937            windows_count: 2,
 938            unknown_count: 0,
 939        }
 940    );
 941
 942    // retrieve the next batch of signup emails to send
 943    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
 944    let addresses = signups_batch1
 945        .iter()
 946        .map(|s| &s.email_address)
 947        .collect::<Vec<_>>();
 948    assert_eq!(
 949        addresses,
 950        &[
 951            all_signups[0].email_address.as_str(),
 952            all_signups[1].email_address.as_str(),
 953            all_signups[2].email_address.as_str()
 954        ]
 955    );
 956    assert_ne!(
 957        signups_batch1[0].email_confirmation_code,
 958        signups_batch1[1].email_confirmation_code
 959    );
 960
 961    // the waitlist isn't updated until we record that the emails
 962    // were successfully sent.
 963    let signups_batch = db.get_unsent_invites(3).await.unwrap();
 964    assert_eq!(signups_batch, signups_batch1);
 965
 966    // once the emails go out, we can retrieve the next batch
 967    // of signups.
 968    db.record_sent_invites(&signups_batch1).await.unwrap();
 969    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
 970    let addresses = signups_batch2
 971        .iter()
 972        .map(|s| &s.email_address)
 973        .collect::<Vec<_>>();
 974    assert_eq!(
 975        addresses,
 976        &[
 977            all_signups[3].email_address.as_str(),
 978            all_signups[4].email_address.as_str(),
 979            all_signups[5].email_address.as_str()
 980        ]
 981    );
 982
 983    // the sent invites are excluded from the summary.
 984    assert_eq!(
 985        db.get_waitlist_summary().await.unwrap(),
 986        WaitlistSummary {
 987            count: 5,
 988            mac_count: 5,
 989            linux_count: 2,
 990            windows_count: 1,
 991            unknown_count: 0,
 992        }
 993    );
 994
 995    // user completes the signup process by providing their
 996    // github account.
 997    let NewUserResult {
 998        user_id,
 999        inviting_user_id,
1000        signup_device_id,
1001        ..
1002    } = db
1003        .create_user_from_invite(
1004            &Invite {
1005                ..signups_batch1[0].clone()
1006            },
1007            NewUserParams {
1008                github_login: usernames[0].clone(),
1009                github_user_id: 0,
1010                invite_count: 5,
1011            },
1012        )
1013        .await
1014        .unwrap()
1015        .unwrap();
1016    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1017    assert!(inviting_user_id.is_none());
1018    assert_eq!(user.github_login, usernames[0]);
1019    assert_eq!(
1020        user.email_address,
1021        Some(all_signups[0].email_address.clone())
1022    );
1023    assert_eq!(user.invite_count, 5);
1024    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1025
1026    // cannot redeem the same signup again.
1027    assert!(db
1028        .create_user_from_invite(
1029            &Invite {
1030                email_address: signups_batch1[0].email_address.clone(),
1031                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1032            },
1033            NewUserParams {
1034                github_login: "some-other-github_account".into(),
1035                github_user_id: 1,
1036                invite_count: 5,
1037            },
1038        )
1039        .await
1040        .unwrap()
1041        .is_none());
1042
1043    // cannot redeem a signup with the wrong confirmation code.
1044    db.create_user_from_invite(
1045        &Invite {
1046            email_address: signups_batch1[1].email_address.clone(),
1047            email_confirmation_code: "the-wrong-code".to_string(),
1048        },
1049        NewUserParams {
1050            github_login: usernames[1].clone(),
1051            github_user_id: 2,
1052            invite_count: 5,
1053        },
1054    )
1055    .await
1056    .unwrap_err();
1057}
1058
1059fn build_background_executor() -> Arc<Background> {
1060    Deterministic::new(0).build_background()
1061}