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(
 571            &invite_code,
 572            "user2@example.com",
 573            Some("user-2-device-id"),
 574            true,
 575        )
 576        .await
 577        .unwrap();
 578    let NewUserResult {
 579        user_id: user2,
 580        inviting_user_id,
 581        signup_device_id,
 582        metrics_id,
 583    } = db
 584        .create_user_from_invite(
 585            &user2_invite,
 586            NewUserParams {
 587                github_login: "user2".into(),
 588                github_user_id: 2,
 589                invite_count: 7,
 590            },
 591        )
 592        .await
 593        .unwrap()
 594        .unwrap();
 595    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 596    assert_eq!(invite_count, 1);
 597    assert_eq!(inviting_user_id, Some(user1));
 598    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 599    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 600    assert_eq!(
 601        db.get_contacts(user1).await.unwrap(),
 602        [Contact::Accepted {
 603            user_id: user2,
 604            should_notify: true,
 605            busy: false,
 606        }]
 607    );
 608    assert_eq!(
 609        db.get_contacts(user2).await.unwrap(),
 610        [Contact::Accepted {
 611            user_id: user1,
 612            should_notify: false,
 613            busy: false,
 614        }]
 615    );
 616    assert!(db.has_contact(user1, user2).await.unwrap());
 617    assert!(db.has_contact(user2, user1).await.unwrap());
 618    assert_eq!(
 619        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 620        7
 621    );
 622
 623    // User 3 redeems the invite code and becomes a contact of user 1.
 624    let user3_invite = db
 625        .create_invite_from_code(&invite_code, "user3@example.com", None, true)
 626        .await
 627        .unwrap();
 628    let NewUserResult {
 629        user_id: user3,
 630        inviting_user_id,
 631        signup_device_id,
 632        ..
 633    } = db
 634        .create_user_from_invite(
 635            &user3_invite,
 636            NewUserParams {
 637                github_login: "user-3".into(),
 638                github_user_id: 3,
 639                invite_count: 3,
 640            },
 641        )
 642        .await
 643        .unwrap()
 644        .unwrap();
 645    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 646    assert_eq!(invite_count, 0);
 647    assert_eq!(inviting_user_id, Some(user1));
 648    assert!(signup_device_id.is_none());
 649    assert_eq!(
 650        db.get_contacts(user1).await.unwrap(),
 651        [
 652            Contact::Accepted {
 653                user_id: user2,
 654                should_notify: true,
 655                busy: false,
 656            },
 657            Contact::Accepted {
 658                user_id: user3,
 659                should_notify: true,
 660                busy: false,
 661            }
 662        ]
 663    );
 664    assert_eq!(
 665        db.get_contacts(user3).await.unwrap(),
 666        [Contact::Accepted {
 667            user_id: user1,
 668            should_notify: false,
 669            busy: false,
 670        }]
 671    );
 672    assert!(db.has_contact(user1, user3).await.unwrap());
 673    assert!(db.has_contact(user3, user1).await.unwrap());
 674    assert_eq!(
 675        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 676        3
 677    );
 678
 679    // Trying to reedem the code for the third time results in an error.
 680    db.create_invite_from_code(
 681        &invite_code,
 682        "user4@example.com",
 683        Some("user-4-device-id"),
 684        true,
 685    )
 686    .await
 687    .unwrap_err();
 688
 689    // Invite count can be updated after the code has been created.
 690    db.set_invite_count_for_user(user1, 2).await.unwrap();
 691    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 692    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 693    assert_eq!(invite_count, 2);
 694
 695    // User 4 can now redeem the invite code and becomes a contact of user 1.
 696    let user4_invite = db
 697        .create_invite_from_code(
 698            &invite_code,
 699            "user4@example.com",
 700            Some("user-4-device-id"),
 701            true,
 702        )
 703        .await
 704        .unwrap();
 705    let user4 = db
 706        .create_user_from_invite(
 707            &user4_invite,
 708            NewUserParams {
 709                github_login: "user-4".into(),
 710                github_user_id: 4,
 711                invite_count: 5,
 712            },
 713        )
 714        .await
 715        .unwrap()
 716        .unwrap()
 717        .user_id;
 718
 719    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 720    assert_eq!(invite_count, 1);
 721    assert_eq!(
 722        db.get_contacts(user1).await.unwrap(),
 723        [
 724            Contact::Accepted {
 725                user_id: user2,
 726                should_notify: true,
 727                busy: false,
 728            },
 729            Contact::Accepted {
 730                user_id: user3,
 731                should_notify: true,
 732                busy: false,
 733            },
 734            Contact::Accepted {
 735                user_id: user4,
 736                should_notify: true,
 737                busy: false,
 738            }
 739        ]
 740    );
 741    assert_eq!(
 742        db.get_contacts(user4).await.unwrap(),
 743        [Contact::Accepted {
 744            user_id: user1,
 745            should_notify: false,
 746            busy: false,
 747        }]
 748    );
 749    assert!(db.has_contact(user1, user4).await.unwrap());
 750    assert!(db.has_contact(user4, user1).await.unwrap());
 751    assert_eq!(
 752        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 753        5
 754    );
 755
 756    // An existing user cannot redeem invite codes.
 757    db.create_invite_from_code(
 758        &invite_code,
 759        "user2@example.com",
 760        Some("user-2-device-id"),
 761        true,
 762    )
 763    .await
 764    .unwrap_err();
 765    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 766    assert_eq!(invite_count, 1);
 767
 768    // A newer user can invite an existing one via a different email address
 769    // than the one they used to sign up.
 770    let user5 = db
 771        .create_user(
 772            "user5@example.com",
 773            false,
 774            NewUserParams {
 775                github_login: "user5".into(),
 776                github_user_id: 5,
 777                invite_count: 0,
 778            },
 779        )
 780        .await
 781        .unwrap()
 782        .user_id;
 783    db.set_invite_count_for_user(user5, 5).await.unwrap();
 784    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 785    let user5_invite_to_user1 = db
 786        .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
 787        .await
 788        .unwrap();
 789    let user1_2 = db
 790        .create_user_from_invite(
 791            &user5_invite_to_user1,
 792            NewUserParams {
 793                github_login: "user1".into(),
 794                github_user_id: 1,
 795                invite_count: 5,
 796            },
 797        )
 798        .await
 799        .unwrap()
 800        .unwrap()
 801        .user_id;
 802    assert_eq!(user1_2, user1);
 803    assert_eq!(
 804        db.get_contacts(user1).await.unwrap(),
 805        [
 806            Contact::Accepted {
 807                user_id: user2,
 808                should_notify: true,
 809                busy: false,
 810            },
 811            Contact::Accepted {
 812                user_id: user3,
 813                should_notify: true,
 814                busy: false,
 815            },
 816            Contact::Accepted {
 817                user_id: user4,
 818                should_notify: true,
 819                busy: false,
 820            },
 821            Contact::Accepted {
 822                user_id: user5,
 823                should_notify: false,
 824                busy: false,
 825            }
 826        ]
 827    );
 828    assert_eq!(
 829        db.get_contacts(user5).await.unwrap(),
 830        [Contact::Accepted {
 831            user_id: user1,
 832            should_notify: true,
 833            busy: false,
 834        }]
 835    );
 836    assert!(db.has_contact(user1, user5).await.unwrap());
 837    assert!(db.has_contact(user5, user1).await.unwrap());
 838}
 839
 840#[gpui::test]
 841async fn test_multiple_signup_overwrite() {
 842    let test_db = TestDb::postgres(build_background_executor());
 843    let db = test_db.db();
 844
 845    let email_address = "user_1@example.com".to_string();
 846
 847    let initial_signup_created_at_milliseconds = 0;
 848
 849    let initial_signup = NewSignup {
 850        email_address: email_address.clone(),
 851        platform_mac: false,
 852        platform_linux: true,
 853        platform_windows: false,
 854        editor_features: vec!["speed".into()],
 855        programming_languages: vec!["rust".into(), "c".into()],
 856        device_id: Some(format!("device_id")),
 857        added_to_mailing_list: false,
 858        created_at: Some(
 859            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
 860        ),
 861    };
 862
 863    db.create_signup(&initial_signup).await.unwrap();
 864
 865    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
 866
 867    assert_eq!(
 868        initial_signup_from_db.clone(),
 869        signup::Model {
 870            email_address: initial_signup.email_address,
 871            platform_mac: initial_signup.platform_mac,
 872            platform_linux: initial_signup.platform_linux,
 873            platform_windows: initial_signup.platform_windows,
 874            editor_features: Some(initial_signup.editor_features),
 875            programming_languages: Some(initial_signup.programming_languages),
 876            added_to_mailing_list: initial_signup.added_to_mailing_list,
 877            ..initial_signup_from_db
 878        }
 879    );
 880
 881    let subsequent_signup = NewSignup {
 882        email_address: email_address.clone(),
 883        platform_mac: true,
 884        platform_linux: false,
 885        platform_windows: true,
 886        editor_features: vec!["git integration".into(), "clean design".into()],
 887        programming_languages: vec!["d".into(), "elm".into()],
 888        device_id: Some(format!("different_device_id")),
 889        added_to_mailing_list: true,
 890        // subsequent signup happens next day
 891        created_at: Some(
 892            DateTime::from_timestamp_millis(
 893                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
 894            )
 895            .unwrap(),
 896        ),
 897    };
 898
 899    db.create_signup(&subsequent_signup).await.unwrap();
 900
 901    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
 902
 903    assert_eq!(
 904        subsequent_signup_from_db.clone(),
 905        signup::Model {
 906            platform_mac: subsequent_signup.platform_mac,
 907            platform_linux: subsequent_signup.platform_linux,
 908            platform_windows: subsequent_signup.platform_windows,
 909            editor_features: Some(subsequent_signup.editor_features),
 910            programming_languages: Some(subsequent_signup.programming_languages),
 911            device_id: subsequent_signup.device_id,
 912            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
 913            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
 914            created_at: initial_signup_from_db.created_at,
 915            ..subsequent_signup_from_db
 916        }
 917    );
 918}
 919
 920#[gpui::test]
 921async fn test_signups() {
 922    let test_db = TestDb::postgres(build_background_executor());
 923    let db = test_db.db();
 924
 925    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
 926
 927    let all_signups = usernames
 928        .iter()
 929        .enumerate()
 930        .map(|(i, username)| NewSignup {
 931            email_address: format!("{username}@example.com"),
 932            platform_mac: true,
 933            platform_linux: i % 2 == 0,
 934            platform_windows: i % 4 == 0,
 935            editor_features: vec!["speed".into()],
 936            programming_languages: vec!["rust".into(), "c".into()],
 937            device_id: Some(format!("device_id_{i}")),
 938            added_to_mailing_list: i != 0, // One user failed to subscribe
 939            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
 940        })
 941        .collect::<Vec<NewSignup>>();
 942
 943    // people sign up on the waitlist
 944    for signup in &all_signups {
 945        // users can sign up multiple times without issues
 946        for _ in 0..2 {
 947            db.create_signup(&signup).await.unwrap();
 948        }
 949    }
 950
 951    assert_eq!(
 952        db.get_waitlist_summary().await.unwrap(),
 953        WaitlistSummary {
 954            count: 8,
 955            mac_count: 8,
 956            linux_count: 4,
 957            windows_count: 2,
 958            unknown_count: 0,
 959        }
 960    );
 961
 962    // retrieve the next batch of signup emails to send
 963    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
 964    let addresses = signups_batch1
 965        .iter()
 966        .map(|s| &s.email_address)
 967        .collect::<Vec<_>>();
 968    assert_eq!(
 969        addresses,
 970        &[
 971            all_signups[0].email_address.as_str(),
 972            all_signups[1].email_address.as_str(),
 973            all_signups[2].email_address.as_str()
 974        ]
 975    );
 976    assert_ne!(
 977        signups_batch1[0].email_confirmation_code,
 978        signups_batch1[1].email_confirmation_code
 979    );
 980
 981    // the waitlist isn't updated until we record that the emails
 982    // were successfully sent.
 983    let signups_batch = db.get_unsent_invites(3).await.unwrap();
 984    assert_eq!(signups_batch, signups_batch1);
 985
 986    // once the emails go out, we can retrieve the next batch
 987    // of signups.
 988    db.record_sent_invites(&signups_batch1).await.unwrap();
 989    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
 990    let addresses = signups_batch2
 991        .iter()
 992        .map(|s| &s.email_address)
 993        .collect::<Vec<_>>();
 994    assert_eq!(
 995        addresses,
 996        &[
 997            all_signups[3].email_address.as_str(),
 998            all_signups[4].email_address.as_str(),
 999            all_signups[5].email_address.as_str()
1000        ]
1001    );
1002
1003    // the sent invites are excluded from the summary.
1004    assert_eq!(
1005        db.get_waitlist_summary().await.unwrap(),
1006        WaitlistSummary {
1007            count: 5,
1008            mac_count: 5,
1009            linux_count: 2,
1010            windows_count: 1,
1011            unknown_count: 0,
1012        }
1013    );
1014
1015    // user completes the signup process by providing their
1016    // github account.
1017    let NewUserResult {
1018        user_id,
1019        inviting_user_id,
1020        signup_device_id,
1021        ..
1022    } = db
1023        .create_user_from_invite(
1024            &Invite {
1025                ..signups_batch1[0].clone()
1026            },
1027            NewUserParams {
1028                github_login: usernames[0].clone(),
1029                github_user_id: 0,
1030                invite_count: 5,
1031            },
1032        )
1033        .await
1034        .unwrap()
1035        .unwrap();
1036    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1037    assert!(inviting_user_id.is_none());
1038    assert_eq!(user.github_login, usernames[0]);
1039    assert_eq!(
1040        user.email_address,
1041        Some(all_signups[0].email_address.clone())
1042    );
1043    assert_eq!(user.invite_count, 5);
1044    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1045
1046    // cannot redeem the same signup again.
1047    assert!(db
1048        .create_user_from_invite(
1049            &Invite {
1050                email_address: signups_batch1[0].email_address.clone(),
1051                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1052            },
1053            NewUserParams {
1054                github_login: "some-other-github_account".into(),
1055                github_user_id: 1,
1056                invite_count: 5,
1057            },
1058        )
1059        .await
1060        .unwrap()
1061        .is_none());
1062
1063    // cannot redeem a signup with the wrong confirmation code.
1064    db.create_user_from_invite(
1065        &Invite {
1066            email_address: signups_batch1[1].email_address.clone(),
1067            email_confirmation_code: "the-wrong-code".to_string(),
1068        },
1069        NewUserParams {
1070            github_login: usernames[1].clone(),
1071            github_user_id: 2,
1072            invite_count: 5,
1073        },
1074    )
1075    .await
1076    .unwrap_err();
1077}
1078
1079fn build_background_executor() -> Arc<Background> {
1080    Deterministic::new(0).build_background()
1081}