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 user1 = db
 414            .create_user(
 415                &format!("admin@example.com"),
 416                true,
 417                NewUserParams {
 418                    github_login: "admin".into(),
 419                    github_user_id: 0,
 420                    invite_count: 0,
 421                },
 422            )
 423            .await
 424            .unwrap();
 425        let user2 = db
 426            .create_user(
 427                &format!("user@example.com"),
 428                false,
 429                NewUserParams {
 430                    github_login: "user".into(),
 431                    github_user_id: 1,
 432                    invite_count: 0,
 433                },
 434            )
 435            .await
 436            .unwrap();
 437
 438        let room_id = RoomId::from_proto(
 439            db.create_room(user1.user_id, ConnectionId(0), "")
 440                .await
 441                .unwrap()
 442                .id,
 443        );
 444        db.call(room_id, user1.user_id, ConnectionId(0), user2.user_id, None)
 445            .await
 446            .unwrap();
 447        db.join_room(room_id, user2.user_id, ConnectionId(1))
 448            .await
 449            .unwrap();
 450        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 451
 452        db.share_project(room_id, ConnectionId(1), &[])
 453            .await
 454            .unwrap();
 455        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
 456
 457        db.share_project(room_id, ConnectionId(1), &[])
 458            .await
 459            .unwrap();
 460        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 461
 462        // Projects shared by admins aren't counted.
 463        db.share_project(room_id, ConnectionId(0), &[])
 464            .await
 465            .unwrap();
 466        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 467
 468        db.leave_room(ConnectionId(1)).await.unwrap();
 469        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 470    }
 471);
 472
 473#[test]
 474fn test_fuzzy_like_string() {
 475    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
 476    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
 477    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
 478}
 479
 480#[gpui::test]
 481async fn test_fuzzy_search_users() {
 482    let test_db = TestDb::postgres(build_background_executor());
 483    let db = test_db.db();
 484    for (i, github_login) in [
 485        "California",
 486        "colorado",
 487        "oregon",
 488        "washington",
 489        "florida",
 490        "delaware",
 491        "rhode-island",
 492    ]
 493    .into_iter()
 494    .enumerate()
 495    {
 496        db.create_user(
 497            &format!("{github_login}@example.com"),
 498            false,
 499            NewUserParams {
 500                github_login: github_login.into(),
 501                github_user_id: i as i32,
 502                invite_count: 0,
 503            },
 504        )
 505        .await
 506        .unwrap();
 507    }
 508
 509    assert_eq!(
 510        fuzzy_search_user_names(db, "clr").await,
 511        &["colorado", "California"]
 512    );
 513    assert_eq!(
 514        fuzzy_search_user_names(db, "ro").await,
 515        &["rhode-island", "colorado", "oregon"],
 516    );
 517
 518    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
 519        db.fuzzy_search_users(query, 10)
 520            .await
 521            .unwrap()
 522            .into_iter()
 523            .map(|user| user.github_login)
 524            .collect::<Vec<_>>()
 525    }
 526}
 527
 528#[gpui::test]
 529async fn test_invite_codes() {
 530    let test_db = TestDb::postgres(build_background_executor());
 531    let db = test_db.db();
 532
 533    let NewUserResult { user_id: user1, .. } = db
 534        .create_user(
 535            "user1@example.com",
 536            false,
 537            NewUserParams {
 538                github_login: "user1".into(),
 539                github_user_id: 0,
 540                invite_count: 0,
 541            },
 542        )
 543        .await
 544        .unwrap();
 545
 546    // Initially, user 1 has no invite code
 547    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 548
 549    // Setting invite count to 0 when no code is assigned does not assign a new code
 550    db.set_invite_count_for_user(user1, 0).await.unwrap();
 551    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 552
 553    // User 1 creates an invite code that can be used twice.
 554    db.set_invite_count_for_user(user1, 2).await.unwrap();
 555    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 556    assert_eq!(invite_count, 2);
 557
 558    // User 2 redeems the invite code and becomes a contact of user 1.
 559    let user2_invite = db
 560        .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
 561        .await
 562        .unwrap();
 563    let NewUserResult {
 564        user_id: user2,
 565        inviting_user_id,
 566        signup_device_id,
 567        metrics_id,
 568    } = db
 569        .create_user_from_invite(
 570            &user2_invite,
 571            NewUserParams {
 572                github_login: "user2".into(),
 573                github_user_id: 2,
 574                invite_count: 7,
 575            },
 576        )
 577        .await
 578        .unwrap()
 579        .unwrap();
 580    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 581    assert_eq!(invite_count, 1);
 582    assert_eq!(inviting_user_id, Some(user1));
 583    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 584    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 585    assert_eq!(
 586        db.get_contacts(user1).await.unwrap(),
 587        [Contact::Accepted {
 588            user_id: user2,
 589            should_notify: true,
 590            busy: false,
 591        }]
 592    );
 593    assert_eq!(
 594        db.get_contacts(user2).await.unwrap(),
 595        [Contact::Accepted {
 596            user_id: user1,
 597            should_notify: false,
 598            busy: false,
 599        }]
 600    );
 601    assert!(db.has_contact(user1, user2).await.unwrap());
 602    assert!(db.has_contact(user2, user1).await.unwrap());
 603    assert_eq!(
 604        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 605        7
 606    );
 607
 608    // User 3 redeems the invite code and becomes a contact of user 1.
 609    let user3_invite = db
 610        .create_invite_from_code(&invite_code, "user3@example.com", None)
 611        .await
 612        .unwrap();
 613    let NewUserResult {
 614        user_id: user3,
 615        inviting_user_id,
 616        signup_device_id,
 617        ..
 618    } = db
 619        .create_user_from_invite(
 620            &user3_invite,
 621            NewUserParams {
 622                github_login: "user-3".into(),
 623                github_user_id: 3,
 624                invite_count: 3,
 625            },
 626        )
 627        .await
 628        .unwrap()
 629        .unwrap();
 630    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 631    assert_eq!(invite_count, 0);
 632    assert_eq!(inviting_user_id, Some(user1));
 633    assert!(signup_device_id.is_none());
 634    assert_eq!(
 635        db.get_contacts(user1).await.unwrap(),
 636        [
 637            Contact::Accepted {
 638                user_id: user2,
 639                should_notify: true,
 640                busy: false,
 641            },
 642            Contact::Accepted {
 643                user_id: user3,
 644                should_notify: true,
 645                busy: false,
 646            }
 647        ]
 648    );
 649    assert_eq!(
 650        db.get_contacts(user3).await.unwrap(),
 651        [Contact::Accepted {
 652            user_id: user1,
 653            should_notify: false,
 654            busy: false,
 655        }]
 656    );
 657    assert!(db.has_contact(user1, user3).await.unwrap());
 658    assert!(db.has_contact(user3, user1).await.unwrap());
 659    assert_eq!(
 660        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 661        3
 662    );
 663
 664    // Trying to reedem the code for the third time results in an error.
 665    db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
 666        .await
 667        .unwrap_err();
 668
 669    // Invite count can be updated after the code has been created.
 670    db.set_invite_count_for_user(user1, 2).await.unwrap();
 671    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 672    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 673    assert_eq!(invite_count, 2);
 674
 675    // User 4 can now redeem the invite code and becomes a contact of user 1.
 676    let user4_invite = db
 677        .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
 678        .await
 679        .unwrap();
 680    let user4 = db
 681        .create_user_from_invite(
 682            &user4_invite,
 683            NewUserParams {
 684                github_login: "user-4".into(),
 685                github_user_id: 4,
 686                invite_count: 5,
 687            },
 688        )
 689        .await
 690        .unwrap()
 691        .unwrap()
 692        .user_id;
 693
 694    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 695    assert_eq!(invite_count, 1);
 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            Contact::Accepted {
 710                user_id: user4,
 711                should_notify: true,
 712                busy: false,
 713            }
 714        ]
 715    );
 716    assert_eq!(
 717        db.get_contacts(user4).await.unwrap(),
 718        [Contact::Accepted {
 719            user_id: user1,
 720            should_notify: false,
 721            busy: false,
 722        }]
 723    );
 724    assert!(db.has_contact(user1, user4).await.unwrap());
 725    assert!(db.has_contact(user4, user1).await.unwrap());
 726    assert_eq!(
 727        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 728        5
 729    );
 730
 731    // An existing user cannot redeem invite codes.
 732    db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
 733        .await
 734        .unwrap_err();
 735    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 736    assert_eq!(invite_count, 1);
 737
 738    // A newer user can invite an existing one via a different email address
 739    // than the one they used to sign up.
 740    let user5 = db
 741        .create_user(
 742            "user5@example.com",
 743            false,
 744            NewUserParams {
 745                github_login: "user5".into(),
 746                github_user_id: 5,
 747                invite_count: 0,
 748            },
 749        )
 750        .await
 751        .unwrap()
 752        .user_id;
 753    db.set_invite_count_for_user(user5, 5).await.unwrap();
 754    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 755    let user5_invite_to_user1 = db
 756        .create_invite_from_code(&user5_invite_code, "user1@different.com", None)
 757        .await
 758        .unwrap();
 759    let user1_2 = db
 760        .create_user_from_invite(
 761            &user5_invite_to_user1,
 762            NewUserParams {
 763                github_login: "user1".into(),
 764                github_user_id: 1,
 765                invite_count: 5,
 766            },
 767        )
 768        .await
 769        .unwrap()
 770        .unwrap()
 771        .user_id;
 772    assert_eq!(user1_2, user1);
 773    assert_eq!(
 774        db.get_contacts(user1).await.unwrap(),
 775        [
 776            Contact::Accepted {
 777                user_id: user2,
 778                should_notify: true,
 779                busy: false,
 780            },
 781            Contact::Accepted {
 782                user_id: user3,
 783                should_notify: true,
 784                busy: false,
 785            },
 786            Contact::Accepted {
 787                user_id: user4,
 788                should_notify: true,
 789                busy: false,
 790            },
 791            Contact::Accepted {
 792                user_id: user5,
 793                should_notify: false,
 794                busy: false,
 795            }
 796        ]
 797    );
 798    assert_eq!(
 799        db.get_contacts(user5).await.unwrap(),
 800        [Contact::Accepted {
 801            user_id: user1,
 802            should_notify: true,
 803            busy: false,
 804        }]
 805    );
 806    assert!(db.has_contact(user1, user5).await.unwrap());
 807    assert!(db.has_contact(user5, user1).await.unwrap());
 808}
 809
 810#[gpui::test]
 811async fn test_multiple_signup_overwrite() {
 812    let test_db = TestDb::postgres(build_background_executor());
 813    let db = test_db.db();
 814
 815    let email_address = "user_1@example.com".to_string();
 816
 817    let initial_signup_created_at_milliseconds = 0;
 818
 819    let initial_signup = NewSignup {
 820        email_address: email_address.clone(),
 821        platform_mac: false,
 822        platform_linux: true,
 823        platform_windows: false,
 824        editor_features: vec!["speed".into()],
 825        programming_languages: vec!["rust".into(), "c".into()],
 826        device_id: Some(format!("device_id")),
 827        added_to_mailing_list: false,
 828        created_at: Some(
 829            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
 830        ),
 831    };
 832
 833    db.create_signup(&initial_signup).await.unwrap();
 834
 835    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
 836
 837    assert_eq!(
 838        initial_signup_from_db.clone(),
 839        signup::Model {
 840            email_address: initial_signup.email_address,
 841            platform_mac: initial_signup.platform_mac,
 842            platform_linux: initial_signup.platform_linux,
 843            platform_windows: initial_signup.platform_windows,
 844            editor_features: Some(initial_signup.editor_features),
 845            programming_languages: Some(initial_signup.programming_languages),
 846            added_to_mailing_list: initial_signup.added_to_mailing_list,
 847            ..initial_signup_from_db
 848        }
 849    );
 850
 851    let subsequent_signup = NewSignup {
 852        email_address: email_address.clone(),
 853        platform_mac: true,
 854        platform_linux: false,
 855        platform_windows: true,
 856        editor_features: vec!["git integration".into(), "clean design".into()],
 857        programming_languages: vec!["d".into(), "elm".into()],
 858        device_id: Some(format!("different_device_id")),
 859        added_to_mailing_list: true,
 860        // subsequent signup happens next day
 861        created_at: Some(
 862            DateTime::from_timestamp_millis(
 863                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
 864            )
 865            .unwrap(),
 866        ),
 867    };
 868
 869    db.create_signup(&subsequent_signup).await.unwrap();
 870
 871    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
 872
 873    assert_eq!(
 874        subsequent_signup_from_db.clone(),
 875        signup::Model {
 876            platform_mac: subsequent_signup.platform_mac,
 877            platform_linux: subsequent_signup.platform_linux,
 878            platform_windows: subsequent_signup.platform_windows,
 879            editor_features: Some(subsequent_signup.editor_features),
 880            programming_languages: Some(subsequent_signup.programming_languages),
 881            device_id: subsequent_signup.device_id,
 882            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
 883            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
 884            created_at: initial_signup_from_db.created_at,
 885            ..subsequent_signup_from_db
 886        }
 887    );
 888}
 889
 890#[gpui::test]
 891async fn test_signups() {
 892    let test_db = TestDb::postgres(build_background_executor());
 893    let db = test_db.db();
 894
 895    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
 896
 897    let all_signups = usernames
 898        .iter()
 899        .enumerate()
 900        .map(|(i, username)| NewSignup {
 901            email_address: format!("{username}@example.com"),
 902            platform_mac: true,
 903            platform_linux: i % 2 == 0,
 904            platform_windows: i % 4 == 0,
 905            editor_features: vec!["speed".into()],
 906            programming_languages: vec!["rust".into(), "c".into()],
 907            device_id: Some(format!("device_id_{i}")),
 908            added_to_mailing_list: i != 0, // One user failed to subscribe
 909            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
 910        })
 911        .collect::<Vec<NewSignup>>();
 912
 913    // people sign up on the waitlist
 914    for signup in &all_signups {
 915        // users can sign up multiple times without issues
 916        for _ in 0..2 {
 917            db.create_signup(&signup).await.unwrap();
 918        }
 919    }
 920
 921    assert_eq!(
 922        db.get_waitlist_summary().await.unwrap(),
 923        WaitlistSummary {
 924            count: 8,
 925            mac_count: 8,
 926            linux_count: 4,
 927            windows_count: 2,
 928            unknown_count: 0,
 929        }
 930    );
 931
 932    // retrieve the next batch of signup emails to send
 933    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
 934    let addresses = signups_batch1
 935        .iter()
 936        .map(|s| &s.email_address)
 937        .collect::<Vec<_>>();
 938    assert_eq!(
 939        addresses,
 940        &[
 941            all_signups[0].email_address.as_str(),
 942            all_signups[1].email_address.as_str(),
 943            all_signups[2].email_address.as_str()
 944        ]
 945    );
 946    assert_ne!(
 947        signups_batch1[0].email_confirmation_code,
 948        signups_batch1[1].email_confirmation_code
 949    );
 950
 951    // the waitlist isn't updated until we record that the emails
 952    // were successfully sent.
 953    let signups_batch = db.get_unsent_invites(3).await.unwrap();
 954    assert_eq!(signups_batch, signups_batch1);
 955
 956    // once the emails go out, we can retrieve the next batch
 957    // of signups.
 958    db.record_sent_invites(&signups_batch1).await.unwrap();
 959    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
 960    let addresses = signups_batch2
 961        .iter()
 962        .map(|s| &s.email_address)
 963        .collect::<Vec<_>>();
 964    assert_eq!(
 965        addresses,
 966        &[
 967            all_signups[3].email_address.as_str(),
 968            all_signups[4].email_address.as_str(),
 969            all_signups[5].email_address.as_str()
 970        ]
 971    );
 972
 973    // the sent invites are excluded from the summary.
 974    assert_eq!(
 975        db.get_waitlist_summary().await.unwrap(),
 976        WaitlistSummary {
 977            count: 5,
 978            mac_count: 5,
 979            linux_count: 2,
 980            windows_count: 1,
 981            unknown_count: 0,
 982        }
 983    );
 984
 985    // user completes the signup process by providing their
 986    // github account.
 987    let NewUserResult {
 988        user_id,
 989        inviting_user_id,
 990        signup_device_id,
 991        ..
 992    } = db
 993        .create_user_from_invite(
 994            &Invite {
 995                ..signups_batch1[0].clone()
 996            },
 997            NewUserParams {
 998                github_login: usernames[0].clone(),
 999                github_user_id: 0,
1000                invite_count: 5,
1001            },
1002        )
1003        .await
1004        .unwrap()
1005        .unwrap();
1006    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1007    assert!(inviting_user_id.is_none());
1008    assert_eq!(user.github_login, usernames[0]);
1009    assert_eq!(
1010        user.email_address,
1011        Some(all_signups[0].email_address.clone())
1012    );
1013    assert_eq!(user.invite_count, 5);
1014    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1015
1016    // cannot redeem the same signup again.
1017    assert!(db
1018        .create_user_from_invite(
1019            &Invite {
1020                email_address: signups_batch1[0].email_address.clone(),
1021                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1022            },
1023            NewUserParams {
1024                github_login: "some-other-github_account".into(),
1025                github_user_id: 1,
1026                invite_count: 5,
1027            },
1028        )
1029        .await
1030        .unwrap()
1031        .is_none());
1032
1033    // cannot redeem a signup with the wrong confirmation code.
1034    db.create_user_from_invite(
1035        &Invite {
1036            email_address: signups_batch1[1].email_address.clone(),
1037            email_confirmation_code: "the-wrong-code".to_string(),
1038        },
1039        NewUserParams {
1040            github_login: usernames[1].clone(),
1041            github_user_id: 2,
1042            invite_count: 5,
1043        },
1044    )
1045    .await
1046    .unwrap_err();
1047}
1048
1049fn build_background_executor() -> Arc<Background> {
1050    Deterministic::new(0).build_background()
1051}