db_tests.rs

   1use super::db::*;
   2use collections::HashMap;
   3use gpui::executor::{Background, Deterministic};
   4use std::{sync::Arc, time::Duration};
   5use time::OffsetDateTime;
   6
   7#[tokio::test(flavor = "multi_thread")]
   8async fn test_get_users_by_ids() {
   9    for test_db in [
  10        TestDb::postgres().await,
  11        TestDb::fake(build_background_executor()),
  12    ] {
  13        let db = test_db.db();
  14
  15        let user1 = db.create_user("u1", "u1@example.com", false).await.unwrap();
  16        let user2 = db.create_user("u2", "u2@example.com", false).await.unwrap();
  17        let user3 = db.create_user("u3", "u3@example.com", false).await.unwrap();
  18        let user4 = db.create_user("u4", "u4@example.com", false).await.unwrap();
  19
  20        assert_eq!(
  21            db.get_users_by_ids(vec![user1, user2, user3, user4])
  22                .await
  23                .unwrap(),
  24            vec![
  25                User {
  26                    id: user1,
  27                    github_login: "u1".to_string(),
  28                    email_address: Some("u1@example.com".to_string()),
  29                    admin: false,
  30                    ..Default::default()
  31                },
  32                User {
  33                    id: user2,
  34                    github_login: "u2".to_string(),
  35                    email_address: Some("u2@example.com".to_string()),
  36                    admin: false,
  37                    ..Default::default()
  38                },
  39                User {
  40                    id: user3,
  41                    github_login: "u3".to_string(),
  42                    email_address: Some("u3@example.com".to_string()),
  43                    admin: false,
  44                    ..Default::default()
  45                },
  46                User {
  47                    id: user4,
  48                    github_login: "u4".to_string(),
  49                    email_address: Some("u4@example.com".to_string()),
  50                    admin: false,
  51                    ..Default::default()
  52                }
  53            ]
  54        );
  55    }
  56}
  57
  58#[tokio::test(flavor = "multi_thread")]
  59async fn test_create_users() {
  60    let db = TestDb::postgres().await;
  61    let db = db.db();
  62
  63    // Create the first batch of users, ensuring invite counts are assigned
  64    // correctly and the respective invite codes are unique.
  65    let user_ids_batch_1 = db
  66        .create_users(vec![
  67            ("user1".to_string(), "hi@user1.com".to_string(), 5),
  68            ("user2".to_string(), "hi@user2.com".to_string(), 4),
  69            ("user3".to_string(), "hi@user3.com".to_string(), 3),
  70        ])
  71        .await
  72        .unwrap();
  73    assert_eq!(user_ids_batch_1.len(), 3);
  74
  75    let users = db.get_users_by_ids(user_ids_batch_1.clone()).await.unwrap();
  76    assert_eq!(users.len(), 3);
  77    assert_eq!(users[0].github_login, "user1");
  78    assert_eq!(users[0].email_address.as_deref(), Some("hi@user1.com"));
  79    assert_eq!(users[0].invite_count, 5);
  80    assert_eq!(users[1].github_login, "user2");
  81    assert_eq!(users[1].email_address.as_deref(), Some("hi@user2.com"));
  82    assert_eq!(users[1].invite_count, 4);
  83    assert_eq!(users[2].github_login, "user3");
  84    assert_eq!(users[2].email_address.as_deref(), Some("hi@user3.com"));
  85    assert_eq!(users[2].invite_count, 3);
  86
  87    let invite_code_1 = users[0].invite_code.clone().unwrap();
  88    let invite_code_2 = users[1].invite_code.clone().unwrap();
  89    let invite_code_3 = users[2].invite_code.clone().unwrap();
  90    assert_ne!(invite_code_1, invite_code_2);
  91    assert_ne!(invite_code_1, invite_code_3);
  92    assert_ne!(invite_code_2, invite_code_3);
  93
  94    // Create the second batch of users and include a user that is already in the database, ensuring
  95    // the invite count for the existing user is updated without changing their invite code.
  96    let user_ids_batch_2 = db
  97        .create_users(vec![
  98            ("user2".to_string(), "hi@user2.com".to_string(), 10),
  99            ("user4".to_string(), "hi@user4.com".to_string(), 2),
 100        ])
 101        .await
 102        .unwrap();
 103    assert_eq!(user_ids_batch_2.len(), 2);
 104    assert_eq!(user_ids_batch_2[0], user_ids_batch_1[1]);
 105
 106    let users = db.get_users_by_ids(user_ids_batch_2).await.unwrap();
 107    assert_eq!(users.len(), 2);
 108    assert_eq!(users[0].github_login, "user2");
 109    assert_eq!(users[0].email_address.as_deref(), Some("hi@user2.com"));
 110    assert_eq!(users[0].invite_count, 10);
 111    assert_eq!(users[0].invite_code, Some(invite_code_2.clone()));
 112    assert_eq!(users[1].github_login, "user4");
 113    assert_eq!(users[1].email_address.as_deref(), Some("hi@user4.com"));
 114    assert_eq!(users[1].invite_count, 2);
 115
 116    let invite_code_4 = users[1].invite_code.clone().unwrap();
 117    assert_ne!(invite_code_4, invite_code_1);
 118    assert_ne!(invite_code_4, invite_code_2);
 119    assert_ne!(invite_code_4, invite_code_3);
 120}
 121
 122#[tokio::test(flavor = "multi_thread")]
 123async fn test_worktree_extensions() {
 124    let test_db = TestDb::postgres().await;
 125    let db = test_db.db();
 126
 127    let user = db.create_user("u1", "u1@example.com", false).await.unwrap();
 128    let project = db.register_project(user).await.unwrap();
 129
 130    db.update_worktree_extensions(project, 100, Default::default())
 131        .await
 132        .unwrap();
 133    db.update_worktree_extensions(
 134        project,
 135        100,
 136        [("rs".to_string(), 5), ("md".to_string(), 3)]
 137            .into_iter()
 138            .collect(),
 139    )
 140    .await
 141    .unwrap();
 142    db.update_worktree_extensions(
 143        project,
 144        100,
 145        [("rs".to_string(), 6), ("md".to_string(), 5)]
 146            .into_iter()
 147            .collect(),
 148    )
 149    .await
 150    .unwrap();
 151    db.update_worktree_extensions(
 152        project,
 153        101,
 154        [("ts".to_string(), 2), ("md".to_string(), 1)]
 155            .into_iter()
 156            .collect(),
 157    )
 158    .await
 159    .unwrap();
 160
 161    assert_eq!(
 162        db.get_project_extensions(project).await.unwrap(),
 163        [
 164            (
 165                100,
 166                [("rs".into(), 6), ("md".into(), 5),]
 167                    .into_iter()
 168                    .collect::<HashMap<_, _>>()
 169            ),
 170            (
 171                101,
 172                [("ts".into(), 2), ("md".into(), 1),]
 173                    .into_iter()
 174                    .collect::<HashMap<_, _>>()
 175            )
 176        ]
 177        .into_iter()
 178        .collect()
 179    );
 180}
 181
 182#[tokio::test(flavor = "multi_thread")]
 183async fn test_user_activity() {
 184    let test_db = TestDb::postgres().await;
 185    let db = test_db.db();
 186
 187    let user_1 = db.create_user("u1", "u1@example.com", false).await.unwrap();
 188    let user_2 = db.create_user("u2", "u2@example.com", false).await.unwrap();
 189    let user_3 = db.create_user("u3", "u3@example.com", false).await.unwrap();
 190    let project_1 = db.register_project(user_1).await.unwrap();
 191    db.update_worktree_extensions(
 192        project_1,
 193        1,
 194        HashMap::from_iter([("rs".into(), 5), ("md".into(), 7)]),
 195    )
 196    .await
 197    .unwrap();
 198    let project_2 = db.register_project(user_2).await.unwrap();
 199    let t0 = OffsetDateTime::now_utc() - Duration::from_secs(60 * 60);
 200
 201    // User 2 opens a project
 202    let t1 = t0 + Duration::from_secs(10);
 203    db.record_user_activity(t0..t1, &[(user_2, project_2)])
 204        .await
 205        .unwrap();
 206
 207    let t2 = t1 + Duration::from_secs(10);
 208    db.record_user_activity(t1..t2, &[(user_2, project_2)])
 209        .await
 210        .unwrap();
 211
 212    // User 1 joins the project
 213    let t3 = t2 + Duration::from_secs(10);
 214    db.record_user_activity(t2..t3, &[(user_2, project_2), (user_1, project_2)])
 215        .await
 216        .unwrap();
 217
 218    // User 1 opens another project
 219    let t4 = t3 + Duration::from_secs(10);
 220    db.record_user_activity(
 221        t3..t4,
 222        &[
 223            (user_2, project_2),
 224            (user_1, project_2),
 225            (user_1, project_1),
 226        ],
 227    )
 228    .await
 229    .unwrap();
 230
 231    // User 3 joins that project
 232    let t5 = t4 + Duration::from_secs(10);
 233    db.record_user_activity(
 234        t4..t5,
 235        &[
 236            (user_2, project_2),
 237            (user_1, project_2),
 238            (user_1, project_1),
 239            (user_3, project_1),
 240        ],
 241    )
 242    .await
 243    .unwrap();
 244
 245    // User 2 leaves
 246    let t6 = t5 + Duration::from_secs(5);
 247    db.record_user_activity(t5..t6, &[(user_1, project_1), (user_3, project_1)])
 248        .await
 249        .unwrap();
 250
 251    let t7 = t6 + Duration::from_secs(60);
 252    let t8 = t7 + Duration::from_secs(10);
 253    db.record_user_activity(t7..t8, &[(user_1, project_1)])
 254        .await
 255        .unwrap();
 256
 257    assert_eq!(
 258        db.get_top_users_activity_summary(t0..t6, 10).await.unwrap(),
 259        &[
 260            UserActivitySummary {
 261                id: user_1,
 262                github_login: "u1".to_string(),
 263                project_activity: vec![
 264                    ProjectActivitySummary {
 265                        id: project_1,
 266                        duration: Duration::from_secs(25),
 267                        max_collaborators: 2
 268                    },
 269                    ProjectActivitySummary {
 270                        id: project_2,
 271                        duration: Duration::from_secs(30),
 272                        max_collaborators: 2
 273                    }
 274                ]
 275            },
 276            UserActivitySummary {
 277                id: user_2,
 278                github_login: "u2".to_string(),
 279                project_activity: vec![ProjectActivitySummary {
 280                    id: project_2,
 281                    duration: Duration::from_secs(50),
 282                    max_collaborators: 2
 283                }]
 284            },
 285            UserActivitySummary {
 286                id: user_3,
 287                github_login: "u3".to_string(),
 288                project_activity: vec![ProjectActivitySummary {
 289                    id: project_1,
 290                    duration: Duration::from_secs(15),
 291                    max_collaborators: 2
 292                }]
 293            },
 294        ]
 295    );
 296
 297    assert_eq!(
 298        db.get_active_user_count(t0..t6, Duration::from_secs(56), false)
 299            .await
 300            .unwrap(),
 301        0
 302    );
 303    assert_eq!(
 304        db.get_active_user_count(t0..t6, Duration::from_secs(56), true)
 305            .await
 306            .unwrap(),
 307        0
 308    );
 309    assert_eq!(
 310        db.get_active_user_count(t0..t6, Duration::from_secs(54), false)
 311            .await
 312            .unwrap(),
 313        1
 314    );
 315    assert_eq!(
 316        db.get_active_user_count(t0..t6, Duration::from_secs(54), true)
 317            .await
 318            .unwrap(),
 319        1
 320    );
 321    assert_eq!(
 322        db.get_active_user_count(t0..t6, Duration::from_secs(30), false)
 323            .await
 324            .unwrap(),
 325        2
 326    );
 327    assert_eq!(
 328        db.get_active_user_count(t0..t6, Duration::from_secs(30), true)
 329            .await
 330            .unwrap(),
 331        2
 332    );
 333    assert_eq!(
 334        db.get_active_user_count(t0..t6, Duration::from_secs(10), false)
 335            .await
 336            .unwrap(),
 337        3
 338    );
 339    assert_eq!(
 340        db.get_active_user_count(t0..t6, Duration::from_secs(10), true)
 341            .await
 342            .unwrap(),
 343        3
 344    );
 345    assert_eq!(
 346        db.get_active_user_count(t0..t1, Duration::from_secs(5), false)
 347            .await
 348            .unwrap(),
 349        1
 350    );
 351    assert_eq!(
 352        db.get_active_user_count(t0..t1, Duration::from_secs(5), true)
 353            .await
 354            .unwrap(),
 355        0
 356    );
 357
 358    assert_eq!(
 359        db.get_user_activity_timeline(t3..t6, user_1).await.unwrap(),
 360        &[
 361            UserActivityPeriod {
 362                project_id: project_1,
 363                start: t3,
 364                end: t6,
 365                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
 366            },
 367            UserActivityPeriod {
 368                project_id: project_2,
 369                start: t3,
 370                end: t5,
 371                extensions: Default::default(),
 372            },
 373        ]
 374    );
 375    assert_eq!(
 376        db.get_user_activity_timeline(t0..t8, user_1).await.unwrap(),
 377        &[
 378            UserActivityPeriod {
 379                project_id: project_2,
 380                start: t2,
 381                end: t5,
 382                extensions: Default::default(),
 383            },
 384            UserActivityPeriod {
 385                project_id: project_1,
 386                start: t3,
 387                end: t6,
 388                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
 389            },
 390            UserActivityPeriod {
 391                project_id: project_1,
 392                start: t7,
 393                end: t8,
 394                extensions: HashMap::from_iter([("rs".to_string(), 5), ("md".to_string(), 7)]),
 395            },
 396        ]
 397    );
 398}
 399
 400#[tokio::test(flavor = "multi_thread")]
 401async fn test_recent_channel_messages() {
 402    for test_db in [
 403        TestDb::postgres().await,
 404        TestDb::fake(build_background_executor()),
 405    ] {
 406        let db = test_db.db();
 407        let user = db.create_user("u", "u@example.com", false).await.unwrap();
 408        let org = db.create_org("org", "org").await.unwrap();
 409        let channel = db.create_org_channel(org, "channel").await.unwrap();
 410        for i in 0..10 {
 411            db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i)
 412                .await
 413                .unwrap();
 414        }
 415
 416        let messages = db.get_channel_messages(channel, 5, None).await.unwrap();
 417        assert_eq!(
 418            messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
 419            ["5", "6", "7", "8", "9"]
 420        );
 421
 422        let prev_messages = db
 423            .get_channel_messages(channel, 4, Some(messages[0].id))
 424            .await
 425            .unwrap();
 426        assert_eq!(
 427            prev_messages.iter().map(|m| &m.body).collect::<Vec<_>>(),
 428            ["1", "2", "3", "4"]
 429        );
 430    }
 431}
 432
 433#[tokio::test(flavor = "multi_thread")]
 434async fn test_channel_message_nonces() {
 435    for test_db in [
 436        TestDb::postgres().await,
 437        TestDb::fake(build_background_executor()),
 438    ] {
 439        let db = test_db.db();
 440        let user = db.create_user("u", "u@example.com", false).await.unwrap();
 441        let org = db.create_org("org", "org").await.unwrap();
 442        let channel = db.create_org_channel(org, "channel").await.unwrap();
 443
 444        let msg1_id = db
 445            .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1)
 446            .await
 447            .unwrap();
 448        let msg2_id = db
 449            .create_channel_message(channel, user, "2", OffsetDateTime::now_utc(), 2)
 450            .await
 451            .unwrap();
 452        let msg3_id = db
 453            .create_channel_message(channel, user, "3", OffsetDateTime::now_utc(), 1)
 454            .await
 455            .unwrap();
 456        let msg4_id = db
 457            .create_channel_message(channel, user, "4", OffsetDateTime::now_utc(), 2)
 458            .await
 459            .unwrap();
 460
 461        assert_ne!(msg1_id, msg2_id);
 462        assert_eq!(msg1_id, msg3_id);
 463        assert_eq!(msg2_id, msg4_id);
 464    }
 465}
 466
 467#[tokio::test(flavor = "multi_thread")]
 468async fn test_create_access_tokens() {
 469    let test_db = TestDb::postgres().await;
 470    let db = test_db.db();
 471    let user = db.create_user("u1", "u1@example.com", false).await.unwrap();
 472
 473    db.create_access_token_hash(user, "h1", 3).await.unwrap();
 474    db.create_access_token_hash(user, "h2", 3).await.unwrap();
 475    assert_eq!(
 476        db.get_access_token_hashes(user).await.unwrap(),
 477        &["h2".to_string(), "h1".to_string()]
 478    );
 479
 480    db.create_access_token_hash(user, "h3", 3).await.unwrap();
 481    assert_eq!(
 482        db.get_access_token_hashes(user).await.unwrap(),
 483        &["h3".to_string(), "h2".to_string(), "h1".to_string(),]
 484    );
 485
 486    db.create_access_token_hash(user, "h4", 3).await.unwrap();
 487    assert_eq!(
 488        db.get_access_token_hashes(user).await.unwrap(),
 489        &["h4".to_string(), "h3".to_string(), "h2".to_string(),]
 490    );
 491
 492    db.create_access_token_hash(user, "h5", 3).await.unwrap();
 493    assert_eq!(
 494        db.get_access_token_hashes(user).await.unwrap(),
 495        &["h5".to_string(), "h4".to_string(), "h3".to_string()]
 496    );
 497}
 498
 499#[test]
 500fn test_fuzzy_like_string() {
 501    assert_eq!(PostgresDb::fuzzy_like_string("abcd"), "%a%b%c%d%");
 502    assert_eq!(PostgresDb::fuzzy_like_string("x y"), "%x%y%");
 503    assert_eq!(PostgresDb::fuzzy_like_string(" z  "), "%z%");
 504}
 505
 506#[tokio::test(flavor = "multi_thread")]
 507async fn test_fuzzy_search_users() {
 508    let test_db = TestDb::postgres().await;
 509    let db = test_db.db();
 510    for github_login in [
 511        "California",
 512        "colorado",
 513        "oregon",
 514        "washington",
 515        "florida",
 516        "delaware",
 517        "rhode-island",
 518    ] {
 519        db.create_user(github_login, &format!("{github_login}@example.com"), false)
 520            .await
 521            .unwrap();
 522    }
 523
 524    assert_eq!(
 525        fuzzy_search_user_names(db, "clr").await,
 526        &["colorado", "California"]
 527    );
 528    assert_eq!(
 529        fuzzy_search_user_names(db, "ro").await,
 530        &["rhode-island", "colorado", "oregon"],
 531    );
 532
 533    async fn fuzzy_search_user_names(db: &Arc<dyn Db>, query: &str) -> Vec<String> {
 534        db.fuzzy_search_users(query, 10)
 535            .await
 536            .unwrap()
 537            .into_iter()
 538            .map(|user| user.github_login)
 539            .collect::<Vec<_>>()
 540    }
 541}
 542
 543#[tokio::test(flavor = "multi_thread")]
 544async fn test_add_contacts() {
 545    for test_db in [
 546        TestDb::postgres().await,
 547        TestDb::fake(build_background_executor()),
 548    ] {
 549        let db = test_db.db();
 550
 551        let user_1 = db.create_user("u1", "u1@example.com", false).await.unwrap();
 552        let user_2 = db.create_user("u2", "u2@example.com", false).await.unwrap();
 553        let user_3 = db.create_user("u3", "u3@example.com", false).await.unwrap();
 554
 555        // User starts with no contacts
 556        assert_eq!(
 557            db.get_contacts(user_1).await.unwrap(),
 558            vec![Contact::Accepted {
 559                user_id: user_1,
 560                should_notify: false
 561            }],
 562        );
 563
 564        // User requests a contact. Both users see the pending request.
 565        db.send_contact_request(user_1, user_2).await.unwrap();
 566        assert!(!db.has_contact(user_1, user_2).await.unwrap());
 567        assert!(!db.has_contact(user_2, user_1).await.unwrap());
 568        assert_eq!(
 569            db.get_contacts(user_1).await.unwrap(),
 570            &[
 571                Contact::Accepted {
 572                    user_id: user_1,
 573                    should_notify: false
 574                },
 575                Contact::Outgoing { user_id: user_2 }
 576            ],
 577        );
 578        assert_eq!(
 579            db.get_contacts(user_2).await.unwrap(),
 580            &[
 581                Contact::Incoming {
 582                    user_id: user_1,
 583                    should_notify: true
 584                },
 585                Contact::Accepted {
 586                    user_id: user_2,
 587                    should_notify: false
 588                },
 589            ]
 590        );
 591
 592        // User 2 dismisses the contact request notification without accepting or rejecting.
 593        // We shouldn't notify them again.
 594        db.dismiss_contact_notification(user_1, user_2)
 595            .await
 596            .unwrap_err();
 597        db.dismiss_contact_notification(user_2, user_1)
 598            .await
 599            .unwrap();
 600        assert_eq!(
 601            db.get_contacts(user_2).await.unwrap(),
 602            &[
 603                Contact::Incoming {
 604                    user_id: user_1,
 605                    should_notify: false
 606                },
 607                Contact::Accepted {
 608                    user_id: user_2,
 609                    should_notify: false
 610                },
 611            ]
 612        );
 613
 614        // User can't accept their own contact request
 615        db.respond_to_contact_request(user_1, user_2, true)
 616            .await
 617            .unwrap_err();
 618
 619        // User accepts a contact request. Both users see the contact.
 620        db.respond_to_contact_request(user_2, user_1, true)
 621            .await
 622            .unwrap();
 623        assert_eq!(
 624            db.get_contacts(user_1).await.unwrap(),
 625            &[
 626                Contact::Accepted {
 627                    user_id: user_1,
 628                    should_notify: false
 629                },
 630                Contact::Accepted {
 631                    user_id: user_2,
 632                    should_notify: true
 633                }
 634            ],
 635        );
 636        assert!(db.has_contact(user_1, user_2).await.unwrap());
 637        assert!(db.has_contact(user_2, user_1).await.unwrap());
 638        assert_eq!(
 639            db.get_contacts(user_2).await.unwrap(),
 640            &[
 641                Contact::Accepted {
 642                    user_id: user_1,
 643                    should_notify: false,
 644                },
 645                Contact::Accepted {
 646                    user_id: user_2,
 647                    should_notify: false,
 648                },
 649            ]
 650        );
 651
 652        // Users cannot re-request existing contacts.
 653        db.send_contact_request(user_1, user_2).await.unwrap_err();
 654        db.send_contact_request(user_2, user_1).await.unwrap_err();
 655
 656        // Users can't dismiss notifications of them accepting other users' requests.
 657        db.dismiss_contact_notification(user_2, user_1)
 658            .await
 659            .unwrap_err();
 660        assert_eq!(
 661            db.get_contacts(user_1).await.unwrap(),
 662            &[
 663                Contact::Accepted {
 664                    user_id: user_1,
 665                    should_notify: false
 666                },
 667                Contact::Accepted {
 668                    user_id: user_2,
 669                    should_notify: true,
 670                },
 671            ]
 672        );
 673
 674        // Users can dismiss notifications of other users accepting their requests.
 675        db.dismiss_contact_notification(user_1, user_2)
 676            .await
 677            .unwrap();
 678        assert_eq!(
 679            db.get_contacts(user_1).await.unwrap(),
 680            &[
 681                Contact::Accepted {
 682                    user_id: user_1,
 683                    should_notify: false
 684                },
 685                Contact::Accepted {
 686                    user_id: user_2,
 687                    should_notify: false,
 688                },
 689            ]
 690        );
 691
 692        // Users send each other concurrent contact requests and
 693        // see that they are immediately accepted.
 694        db.send_contact_request(user_1, user_3).await.unwrap();
 695        db.send_contact_request(user_3, user_1).await.unwrap();
 696        assert_eq!(
 697            db.get_contacts(user_1).await.unwrap(),
 698            &[
 699                Contact::Accepted {
 700                    user_id: user_1,
 701                    should_notify: false
 702                },
 703                Contact::Accepted {
 704                    user_id: user_2,
 705                    should_notify: false,
 706                },
 707                Contact::Accepted {
 708                    user_id: user_3,
 709                    should_notify: false
 710                },
 711            ]
 712        );
 713        assert_eq!(
 714            db.get_contacts(user_3).await.unwrap(),
 715            &[
 716                Contact::Accepted {
 717                    user_id: user_1,
 718                    should_notify: false
 719                },
 720                Contact::Accepted {
 721                    user_id: user_3,
 722                    should_notify: false
 723                }
 724            ],
 725        );
 726
 727        // User declines a contact request. Both users see that it is gone.
 728        db.send_contact_request(user_2, user_3).await.unwrap();
 729        db.respond_to_contact_request(user_3, user_2, false)
 730            .await
 731            .unwrap();
 732        assert!(!db.has_contact(user_2, user_3).await.unwrap());
 733        assert!(!db.has_contact(user_3, user_2).await.unwrap());
 734        assert_eq!(
 735            db.get_contacts(user_2).await.unwrap(),
 736            &[
 737                Contact::Accepted {
 738                    user_id: user_1,
 739                    should_notify: false
 740                },
 741                Contact::Accepted {
 742                    user_id: user_2,
 743                    should_notify: false
 744                }
 745            ]
 746        );
 747        assert_eq!(
 748            db.get_contacts(user_3).await.unwrap(),
 749            &[
 750                Contact::Accepted {
 751                    user_id: user_1,
 752                    should_notify: false
 753                },
 754                Contact::Accepted {
 755                    user_id: user_3,
 756                    should_notify: false
 757                }
 758            ],
 759        );
 760    }
 761}
 762
 763#[tokio::test(flavor = "multi_thread")]
 764async fn test_invite_codes() {
 765    let postgres = TestDb::postgres().await;
 766    let db = postgres.db();
 767    let user1 = db.create_user("u1", "u1@example.com", false).await.unwrap();
 768
 769    // Initially, user 1 has no invite code
 770    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 771
 772    // Setting invite count to 0 when no code is assigned does not assign a new code
 773    db.set_invite_count_for_user(user1, 0).await.unwrap();
 774    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 775
 776    // User 1 creates an invite code that can be used twice.
 777    db.set_invite_count_for_user(user1, 2).await.unwrap();
 778    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 779    assert_eq!(invite_count, 2);
 780
 781    // User 2 redeems the invite code and becomes a contact of user 1.
 782    let user2_invite = db
 783        .create_invite_from_code(&invite_code, "u2@example.com")
 784        .await
 785        .unwrap();
 786    let (user2, inviter) = db
 787        .create_user_from_invite(
 788            &user2_invite,
 789            NewUserParams {
 790                github_login: "user2".into(),
 791                invite_count: 7,
 792            },
 793        )
 794        .await
 795        .unwrap();
 796    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 797    assert_eq!(invite_count, 1);
 798    assert_eq!(inviter, Some(user1));
 799    assert_eq!(
 800        db.get_contacts(user1).await.unwrap(),
 801        [
 802            Contact::Accepted {
 803                user_id: user1,
 804                should_notify: false
 805            },
 806            Contact::Accepted {
 807                user_id: user2,
 808                should_notify: true
 809            }
 810        ]
 811    );
 812    assert_eq!(
 813        db.get_contacts(user2).await.unwrap(),
 814        [
 815            Contact::Accepted {
 816                user_id: user1,
 817                should_notify: false
 818            },
 819            Contact::Accepted {
 820                user_id: user2,
 821                should_notify: false
 822            }
 823        ]
 824    );
 825    assert_eq!(
 826        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 827        7
 828    );
 829
 830    // User 3 redeems the invite code and becomes a contact of user 1.
 831    let user3_invite = db
 832        .create_invite_from_code(&invite_code, "u3@example.com")
 833        .await
 834        .unwrap();
 835    let (user3, inviter) = db
 836        .create_user_from_invite(
 837            &user3_invite,
 838            NewUserParams {
 839                github_login: "user-3".into(),
 840                invite_count: 3,
 841            },
 842        )
 843        .await
 844        .unwrap();
 845    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 846    assert_eq!(invite_count, 0);
 847    assert_eq!(inviter, Some(user1));
 848    assert_eq!(
 849        db.get_contacts(user1).await.unwrap(),
 850        [
 851            Contact::Accepted {
 852                user_id: user1,
 853                should_notify: false
 854            },
 855            Contact::Accepted {
 856                user_id: user2,
 857                should_notify: true
 858            },
 859            Contact::Accepted {
 860                user_id: user3,
 861                should_notify: true
 862            }
 863        ]
 864    );
 865    assert_eq!(
 866        db.get_contacts(user3).await.unwrap(),
 867        [
 868            Contact::Accepted {
 869                user_id: user1,
 870                should_notify: false
 871            },
 872            Contact::Accepted {
 873                user_id: user3,
 874                should_notify: false
 875            },
 876        ]
 877    );
 878    assert_eq!(
 879        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 880        3
 881    );
 882
 883    // Trying to reedem the code for the third time results in an error.
 884    db.create_invite_from_code(&invite_code, "u4@example.com")
 885        .await
 886        .unwrap_err();
 887
 888    // Invite count can be updated after the code has been created.
 889    db.set_invite_count_for_user(user1, 2).await.unwrap();
 890    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 891    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 892    assert_eq!(invite_count, 2);
 893
 894    // User 4 can now redeem the invite code and becomes a contact of user 1.
 895    let user4_invite = db
 896        .create_invite_from_code(&invite_code, "u4@example.com")
 897        .await
 898        .unwrap();
 899    let (user4, _) = db
 900        .create_user_from_invite(
 901            &user4_invite,
 902            NewUserParams {
 903                github_login: "user-4".into(),
 904                invite_count: 5,
 905            },
 906        )
 907        .await
 908        .unwrap();
 909
 910    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 911    assert_eq!(invite_count, 1);
 912    assert_eq!(
 913        db.get_contacts(user1).await.unwrap(),
 914        [
 915            Contact::Accepted {
 916                user_id: user1,
 917                should_notify: false
 918            },
 919            Contact::Accepted {
 920                user_id: user2,
 921                should_notify: true
 922            },
 923            Contact::Accepted {
 924                user_id: user3,
 925                should_notify: true
 926            },
 927            Contact::Accepted {
 928                user_id: user4,
 929                should_notify: true
 930            }
 931        ]
 932    );
 933    assert_eq!(
 934        db.get_contacts(user4).await.unwrap(),
 935        [
 936            Contact::Accepted {
 937                user_id: user1,
 938                should_notify: false
 939            },
 940            Contact::Accepted {
 941                user_id: user4,
 942                should_notify: false
 943            },
 944        ]
 945    );
 946    assert_eq!(
 947        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 948        5
 949    );
 950
 951    // An existing user cannot redeem invite codes.
 952    db.create_invite_from_code(&invite_code, "u2@example.com")
 953        .await
 954        .unwrap_err();
 955    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 956    assert_eq!(invite_count, 1);
 957}
 958
 959#[tokio::test(flavor = "multi_thread")]
 960async fn test_signups() {
 961    let postgres = TestDb::postgres().await;
 962    let db = postgres.db();
 963
 964    // people sign up on the waitlist
 965    for i in 0..8 {
 966        db.create_signup(Signup {
 967            email_address: format!("person-{i}@example.com"),
 968            platform_mac: true,
 969            platform_linux: i % 2 == 0,
 970            platform_windows: i % 4 == 0,
 971            editor_features: vec!["speed".into()],
 972            programming_languages: vec!["rust".into(), "c".into()],
 973        })
 974        .await
 975        .unwrap();
 976    }
 977
 978    assert_eq!(
 979        db.get_waitlist_summary().await.unwrap(),
 980        WaitlistSummary {
 981            count: 8,
 982            mac_count: 8,
 983            linux_count: 4,
 984            windows_count: 2,
 985        }
 986    );
 987
 988    // retrieve the next batch of signup emails to send
 989    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
 990    let addresses = signups_batch1
 991        .iter()
 992        .map(|s| &s.email_address)
 993        .collect::<Vec<_>>();
 994    assert_eq!(
 995        addresses,
 996        &[
 997            "person-0@example.com",
 998            "person-1@example.com",
 999            "person-2@example.com"
1000        ]
1001    );
1002    assert_ne!(
1003        signups_batch1[0].email_confirmation_code,
1004        signups_batch1[1].email_confirmation_code
1005    );
1006
1007    // the waitlist isn't updated until we record that the emails
1008    // were successfully sent.
1009    let signups_batch = db.get_unsent_invites(3).await.unwrap();
1010    assert_eq!(signups_batch, signups_batch1);
1011
1012    // once the emails go out, we can retrieve the next batch
1013    // of signups.
1014    db.record_sent_invites(&signups_batch1).await.unwrap();
1015    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1016    let addresses = signups_batch2
1017        .iter()
1018        .map(|s| &s.email_address)
1019        .collect::<Vec<_>>();
1020    assert_eq!(
1021        addresses,
1022        &[
1023            "person-3@example.com",
1024            "person-4@example.com",
1025            "person-5@example.com"
1026        ]
1027    );
1028
1029    // the sent invites are excluded from the summary.
1030    assert_eq!(
1031        db.get_waitlist_summary().await.unwrap(),
1032        WaitlistSummary {
1033            count: 5,
1034            mac_count: 5,
1035            linux_count: 2,
1036            windows_count: 1,
1037        }
1038    );
1039
1040    // user completes the signup process by providing their
1041    // github account.
1042    let (user_id, inviter_id) = db
1043        .create_user_from_invite(
1044            &Invite {
1045                email_address: signups_batch1[0].email_address.clone(),
1046                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1047            },
1048            NewUserParams {
1049                github_login: "person-0".into(),
1050                invite_count: 5,
1051            },
1052        )
1053        .await
1054        .unwrap();
1055    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1056    assert!(inviter_id.is_none());
1057    assert_eq!(user.github_login, "person-0");
1058    assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
1059    assert_eq!(user.invite_count, 5);
1060
1061    // cannot redeem the same signup again.
1062    db.create_user_from_invite(
1063        &Invite {
1064            email_address: signups_batch1[0].email_address.clone(),
1065            email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1066        },
1067        NewUserParams {
1068            github_login: "some-other-github_account".into(),
1069            invite_count: 5,
1070        },
1071    )
1072    .await
1073    .unwrap_err();
1074
1075    // cannot redeem a signup with the wrong confirmation code.
1076    db.create_user_from_invite(
1077        &Invite {
1078            email_address: signups_batch1[1].email_address.clone(),
1079            email_confirmation_code: "the-wrong-code".to_string(),
1080        },
1081        NewUserParams {
1082            github_login: "person-1".into(),
1083            invite_count: 5,
1084        },
1085    )
1086    .await
1087    .unwrap_err();
1088}
1089
1090fn build_background_executor() -> Arc<Background> {
1091    Deterministic::new(0).build_background()
1092}