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