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_or_create_user_by_github_account_postgres,
  96    test_get_or_create_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_or_create_user_by_github_account("login1", None, 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_or_create_user_by_github_account("non-existent-login", None, None)
 137            .await
 138            .unwrap()
 139            .is_none());
 140
 141        let user = db
 142            .get_or_create_user_by_github_account("the-new-login2", Some(102), None)
 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        let user = db
 151            .get_or_create_user_by_github_account("login3", Some(103), Some("user3@example.com"))
 152            .await
 153            .unwrap()
 154            .unwrap();
 155        assert_eq!(&user.github_login, "login3");
 156        assert_eq!(user.github_user_id, Some(103));
 157        assert_eq!(user.email_address, Some("user3@example.com".into()));
 158    }
 159);
 160
 161test_both_dbs!(
 162    test_create_access_tokens_postgres,
 163    test_create_access_tokens_sqlite,
 164    db,
 165    {
 166        let user = db
 167            .create_user(
 168                "u1@example.com",
 169                false,
 170                NewUserParams {
 171                    github_login: "u1".into(),
 172                    github_user_id: 1,
 173                    invite_count: 0,
 174                },
 175            )
 176            .await
 177            .unwrap()
 178            .user_id;
 179
 180        let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
 181        let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
 182        assert_eq!(
 183            db.get_access_token(token_1).await.unwrap(),
 184            access_token::Model {
 185                id: token_1,
 186                user_id: user,
 187                hash: "h1".into(),
 188            }
 189        );
 190        assert_eq!(
 191            db.get_access_token(token_2).await.unwrap(),
 192            access_token::Model {
 193                id: token_2,
 194                user_id: user,
 195                hash: "h2".into()
 196            }
 197        );
 198
 199        let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
 200        assert_eq!(
 201            db.get_access_token(token_3).await.unwrap(),
 202            access_token::Model {
 203                id: token_3,
 204                user_id: user,
 205                hash: "h3".into()
 206            }
 207        );
 208        assert_eq!(
 209            db.get_access_token(token_2).await.unwrap(),
 210            access_token::Model {
 211                id: token_2,
 212                user_id: user,
 213                hash: "h2".into()
 214            }
 215        );
 216        assert!(db.get_access_token(token_1).await.is_err());
 217
 218        let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
 219        assert_eq!(
 220            db.get_access_token(token_4).await.unwrap(),
 221            access_token::Model {
 222                id: token_4,
 223                user_id: user,
 224                hash: "h4".into()
 225            }
 226        );
 227        assert_eq!(
 228            db.get_access_token(token_3).await.unwrap(),
 229            access_token::Model {
 230                id: token_3,
 231                user_id: user,
 232                hash: "h3".into()
 233            }
 234        );
 235        assert!(db.get_access_token(token_2).await.is_err());
 236        assert!(db.get_access_token(token_1).await.is_err());
 237    }
 238);
 239
 240test_both_dbs!(test_add_contacts_postgres, test_add_contacts_sqlite, db, {
 241    let mut user_ids = Vec::new();
 242    for i in 0..3 {
 243        user_ids.push(
 244            db.create_user(
 245                &format!("user{i}@example.com"),
 246                false,
 247                NewUserParams {
 248                    github_login: format!("user{i}"),
 249                    github_user_id: i,
 250                    invite_count: 0,
 251                },
 252            )
 253            .await
 254            .unwrap()
 255            .user_id,
 256        );
 257    }
 258
 259    let user_1 = user_ids[0];
 260    let user_2 = user_ids[1];
 261    let user_3 = user_ids[2];
 262
 263    // User starts with no contacts
 264    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
 265
 266    // User requests a contact. Both users see the pending request.
 267    db.send_contact_request(user_1, user_2).await.unwrap();
 268    assert!(!db.has_contact(user_1, user_2).await.unwrap());
 269    assert!(!db.has_contact(user_2, user_1).await.unwrap());
 270    assert_eq!(
 271        db.get_contacts(user_1).await.unwrap(),
 272        &[Contact::Outgoing { user_id: user_2 }],
 273    );
 274    assert_eq!(
 275        db.get_contacts(user_2).await.unwrap(),
 276        &[Contact::Incoming {
 277            user_id: user_1,
 278            should_notify: true
 279        }]
 280    );
 281
 282    // User 2 dismisses the contact request notification without accepting or rejecting.
 283    // We shouldn't notify them again.
 284    db.dismiss_contact_notification(user_1, user_2)
 285        .await
 286        .unwrap_err();
 287    db.dismiss_contact_notification(user_2, user_1)
 288        .await
 289        .unwrap();
 290    assert_eq!(
 291        db.get_contacts(user_2).await.unwrap(),
 292        &[Contact::Incoming {
 293            user_id: user_1,
 294            should_notify: false
 295        }]
 296    );
 297
 298    // User can't accept their own contact request
 299    db.respond_to_contact_request(user_1, user_2, true)
 300        .await
 301        .unwrap_err();
 302
 303    // User accepts a contact request. Both users see the contact.
 304    db.respond_to_contact_request(user_2, user_1, true)
 305        .await
 306        .unwrap();
 307    assert_eq!(
 308        db.get_contacts(user_1).await.unwrap(),
 309        &[Contact::Accepted {
 310            user_id: user_2,
 311            should_notify: true,
 312            busy: false,
 313        }],
 314    );
 315    assert!(db.has_contact(user_1, user_2).await.unwrap());
 316    assert!(db.has_contact(user_2, user_1).await.unwrap());
 317    assert_eq!(
 318        db.get_contacts(user_2).await.unwrap(),
 319        &[Contact::Accepted {
 320            user_id: user_1,
 321            should_notify: false,
 322            busy: false,
 323        }]
 324    );
 325
 326    // Users cannot re-request existing contacts.
 327    db.send_contact_request(user_1, user_2).await.unwrap_err();
 328    db.send_contact_request(user_2, user_1).await.unwrap_err();
 329
 330    // Users can't dismiss notifications of them accepting other users' requests.
 331    db.dismiss_contact_notification(user_2, user_1)
 332        .await
 333        .unwrap_err();
 334    assert_eq!(
 335        db.get_contacts(user_1).await.unwrap(),
 336        &[Contact::Accepted {
 337            user_id: user_2,
 338            should_notify: true,
 339            busy: false,
 340        }]
 341    );
 342
 343    // Users can dismiss notifications of other users accepting their requests.
 344    db.dismiss_contact_notification(user_1, user_2)
 345        .await
 346        .unwrap();
 347    assert_eq!(
 348        db.get_contacts(user_1).await.unwrap(),
 349        &[Contact::Accepted {
 350            user_id: user_2,
 351            should_notify: false,
 352            busy: false,
 353        }]
 354    );
 355
 356    // Users send each other concurrent contact requests and
 357    // see that they are immediately accepted.
 358    db.send_contact_request(user_1, user_3).await.unwrap();
 359    db.send_contact_request(user_3, user_1).await.unwrap();
 360    assert_eq!(
 361        db.get_contacts(user_1).await.unwrap(),
 362        &[
 363            Contact::Accepted {
 364                user_id: user_2,
 365                should_notify: false,
 366                busy: false,
 367            },
 368            Contact::Accepted {
 369                user_id: user_3,
 370                should_notify: false,
 371                busy: false,
 372            }
 373        ]
 374    );
 375    assert_eq!(
 376        db.get_contacts(user_3).await.unwrap(),
 377        &[Contact::Accepted {
 378            user_id: user_1,
 379            should_notify: false,
 380            busy: false,
 381        }],
 382    );
 383
 384    // User declines a contact request. Both users see that it is gone.
 385    db.send_contact_request(user_2, user_3).await.unwrap();
 386    db.respond_to_contact_request(user_3, user_2, false)
 387        .await
 388        .unwrap();
 389    assert!(!db.has_contact(user_2, user_3).await.unwrap());
 390    assert!(!db.has_contact(user_3, user_2).await.unwrap());
 391    assert_eq!(
 392        db.get_contacts(user_2).await.unwrap(),
 393        &[Contact::Accepted {
 394            user_id: user_1,
 395            should_notify: false,
 396            busy: false,
 397        }]
 398    );
 399    assert_eq!(
 400        db.get_contacts(user_3).await.unwrap(),
 401        &[Contact::Accepted {
 402            user_id: user_1,
 403            should_notify: false,
 404            busy: false,
 405        }],
 406    );
 407});
 408
 409test_both_dbs!(test_metrics_id_postgres, test_metrics_id_sqlite, db, {
 410    let NewUserResult {
 411        user_id: user1,
 412        metrics_id: metrics_id1,
 413        ..
 414    } = db
 415        .create_user(
 416            "person1@example.com",
 417            false,
 418            NewUserParams {
 419                github_login: "person1".into(),
 420                github_user_id: 101,
 421                invite_count: 5,
 422            },
 423        )
 424        .await
 425        .unwrap();
 426    let NewUserResult {
 427        user_id: user2,
 428        metrics_id: metrics_id2,
 429        ..
 430    } = db
 431        .create_user(
 432            "person2@example.com",
 433            false,
 434            NewUserParams {
 435                github_login: "person2".into(),
 436                github_user_id: 102,
 437                invite_count: 5,
 438            },
 439        )
 440        .await
 441        .unwrap();
 442
 443    assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
 444    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
 445    assert_eq!(metrics_id1.len(), 36);
 446    assert_eq!(metrics_id2.len(), 36);
 447    assert_ne!(metrics_id1, metrics_id2);
 448});
 449
 450test_both_dbs!(
 451    test_project_count_postgres,
 452    test_project_count_sqlite,
 453    db,
 454    {
 455        let owner_id = db.create_server("test").await.unwrap().0 as u32;
 456
 457        let user1 = db
 458            .create_user(
 459                &format!("admin@example.com"),
 460                true,
 461                NewUserParams {
 462                    github_login: "admin".into(),
 463                    github_user_id: 0,
 464                    invite_count: 0,
 465                },
 466            )
 467            .await
 468            .unwrap();
 469        let user2 = db
 470            .create_user(
 471                &format!("user@example.com"),
 472                false,
 473                NewUserParams {
 474                    github_login: "user".into(),
 475                    github_user_id: 1,
 476                    invite_count: 0,
 477                },
 478            )
 479            .await
 480            .unwrap();
 481
 482        let room_id = RoomId::from_proto(
 483            db.create_room(user1.user_id, ConnectionId { owner_id, id: 0 }, "")
 484                .await
 485                .unwrap()
 486                .id,
 487        );
 488        db.call(
 489            room_id,
 490            user1.user_id,
 491            ConnectionId { owner_id, id: 0 },
 492            user2.user_id,
 493            None,
 494        )
 495        .await
 496        .unwrap();
 497        db.join_room(
 498            room_id,
 499            user2.user_id,
 500            None,
 501            ConnectionId { owner_id, id: 1 },
 502        )
 503        .await
 504        .unwrap();
 505        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 506
 507        db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 508            .await
 509            .unwrap();
 510        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
 511
 512        db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[])
 513            .await
 514            .unwrap();
 515        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 516
 517        // Projects shared by admins aren't counted.
 518        db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[])
 519            .await
 520            .unwrap();
 521        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
 522
 523        db.leave_room(ConnectionId { owner_id, id: 1 })
 524            .await
 525            .unwrap();
 526        assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
 527    }
 528);
 529
 530#[test]
 531fn test_fuzzy_like_string() {
 532    assert_eq!(Database::fuzzy_like_string("abcd"), "%a%b%c%d%");
 533    assert_eq!(Database::fuzzy_like_string("x y"), "%x%y%");
 534    assert_eq!(Database::fuzzy_like_string(" z  "), "%z%");
 535}
 536
 537#[gpui::test]
 538async fn test_fuzzy_search_users() {
 539    let test_db = TestDb::postgres(build_background_executor());
 540    let db = test_db.db();
 541    for (i, github_login) in [
 542        "California",
 543        "colorado",
 544        "oregon",
 545        "washington",
 546        "florida",
 547        "delaware",
 548        "rhode-island",
 549    ]
 550    .into_iter()
 551    .enumerate()
 552    {
 553        db.create_user(
 554            &format!("{github_login}@example.com"),
 555            false,
 556            NewUserParams {
 557                github_login: github_login.into(),
 558                github_user_id: i as i32,
 559                invite_count: 0,
 560            },
 561        )
 562        .await
 563        .unwrap();
 564    }
 565
 566    assert_eq!(
 567        fuzzy_search_user_names(db, "clr").await,
 568        &["colorado", "California"]
 569    );
 570    assert_eq!(
 571        fuzzy_search_user_names(db, "ro").await,
 572        &["rhode-island", "colorado", "oregon"],
 573    );
 574
 575    async fn fuzzy_search_user_names(db: &Database, query: &str) -> Vec<String> {
 576        db.fuzzy_search_users(query, 10)
 577            .await
 578            .unwrap()
 579            .into_iter()
 580            .map(|user| user.github_login)
 581            .collect::<Vec<_>>()
 582    }
 583}
 584
 585#[gpui::test]
 586async fn test_invite_codes() {
 587    let test_db = TestDb::postgres(build_background_executor());
 588    let db = test_db.db();
 589
 590    let NewUserResult { user_id: user1, .. } = db
 591        .create_user(
 592            "user1@example.com",
 593            false,
 594            NewUserParams {
 595                github_login: "user1".into(),
 596                github_user_id: 0,
 597                invite_count: 0,
 598            },
 599        )
 600        .await
 601        .unwrap();
 602
 603    // Initially, user 1 has no invite code
 604    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
 605
 606    // Setting invite count to 0 when no code is assigned does not assign a new code
 607    db.set_invite_count_for_user(user1, 0).await.unwrap();
 608    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
 609
 610    // User 1 creates an invite code that can be used twice.
 611    db.set_invite_count_for_user(user1, 2).await.unwrap();
 612    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 613    assert_eq!(invite_count, 2);
 614
 615    // User 2 redeems the invite code and becomes a contact of user 1.
 616    let user2_invite = db
 617        .create_invite_from_code(
 618            &invite_code,
 619            "user2@example.com",
 620            Some("user-2-device-id"),
 621            true,
 622        )
 623        .await
 624        .unwrap();
 625    let NewUserResult {
 626        user_id: user2,
 627        inviting_user_id,
 628        signup_device_id,
 629        metrics_id,
 630    } = db
 631        .create_user_from_invite(
 632            &user2_invite,
 633            NewUserParams {
 634                github_login: "user2".into(),
 635                github_user_id: 2,
 636                invite_count: 7,
 637            },
 638        )
 639        .await
 640        .unwrap()
 641        .unwrap();
 642    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 643    assert_eq!(invite_count, 1);
 644    assert_eq!(inviting_user_id, Some(user1));
 645    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
 646    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
 647    assert_eq!(
 648        db.get_contacts(user1).await.unwrap(),
 649        [Contact::Accepted {
 650            user_id: user2,
 651            should_notify: true,
 652            busy: false,
 653        }]
 654    );
 655    assert_eq!(
 656        db.get_contacts(user2).await.unwrap(),
 657        [Contact::Accepted {
 658            user_id: user1,
 659            should_notify: false,
 660            busy: false,
 661        }]
 662    );
 663    assert!(db.has_contact(user1, user2).await.unwrap());
 664    assert!(db.has_contact(user2, user1).await.unwrap());
 665    assert_eq!(
 666        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
 667        7
 668    );
 669
 670    // User 3 redeems the invite code and becomes a contact of user 1.
 671    let user3_invite = db
 672        .create_invite_from_code(&invite_code, "user3@example.com", None, true)
 673        .await
 674        .unwrap();
 675    let NewUserResult {
 676        user_id: user3,
 677        inviting_user_id,
 678        signup_device_id,
 679        ..
 680    } = db
 681        .create_user_from_invite(
 682            &user3_invite,
 683            NewUserParams {
 684                github_login: "user-3".into(),
 685                github_user_id: 3,
 686                invite_count: 3,
 687            },
 688        )
 689        .await
 690        .unwrap()
 691        .unwrap();
 692    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 693    assert_eq!(invite_count, 0);
 694    assert_eq!(inviting_user_id, Some(user1));
 695    assert!(signup_device_id.is_none());
 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        ]
 710    );
 711    assert_eq!(
 712        db.get_contacts(user3).await.unwrap(),
 713        [Contact::Accepted {
 714            user_id: user1,
 715            should_notify: false,
 716            busy: false,
 717        }]
 718    );
 719    assert!(db.has_contact(user1, user3).await.unwrap());
 720    assert!(db.has_contact(user3, user1).await.unwrap());
 721    assert_eq!(
 722        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
 723        3
 724    );
 725
 726    // Trying to reedem the code for the third time results in an error.
 727    db.create_invite_from_code(
 728        &invite_code,
 729        "user4@example.com",
 730        Some("user-4-device-id"),
 731        true,
 732    )
 733    .await
 734    .unwrap_err();
 735
 736    // Invite count can be updated after the code has been created.
 737    db.set_invite_count_for_user(user1, 2).await.unwrap();
 738    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 739    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
 740    assert_eq!(invite_count, 2);
 741
 742    // User 4 can now redeem the invite code and becomes a contact of user 1.
 743    let user4_invite = db
 744        .create_invite_from_code(
 745            &invite_code,
 746            "user4@example.com",
 747            Some("user-4-device-id"),
 748            true,
 749        )
 750        .await
 751        .unwrap();
 752    let user4 = db
 753        .create_user_from_invite(
 754            &user4_invite,
 755            NewUserParams {
 756                github_login: "user-4".into(),
 757                github_user_id: 4,
 758                invite_count: 5,
 759            },
 760        )
 761        .await
 762        .unwrap()
 763        .unwrap()
 764        .user_id;
 765
 766    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 767    assert_eq!(invite_count, 1);
 768    assert_eq!(
 769        db.get_contacts(user1).await.unwrap(),
 770        [
 771            Contact::Accepted {
 772                user_id: user2,
 773                should_notify: true,
 774                busy: false,
 775            },
 776            Contact::Accepted {
 777                user_id: user3,
 778                should_notify: true,
 779                busy: false,
 780            },
 781            Contact::Accepted {
 782                user_id: user4,
 783                should_notify: true,
 784                busy: false,
 785            }
 786        ]
 787    );
 788    assert_eq!(
 789        db.get_contacts(user4).await.unwrap(),
 790        [Contact::Accepted {
 791            user_id: user1,
 792            should_notify: false,
 793            busy: false,
 794        }]
 795    );
 796    assert!(db.has_contact(user1, user4).await.unwrap());
 797    assert!(db.has_contact(user4, user1).await.unwrap());
 798    assert_eq!(
 799        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
 800        5
 801    );
 802
 803    // An existing user cannot redeem invite codes.
 804    db.create_invite_from_code(
 805        &invite_code,
 806        "user2@example.com",
 807        Some("user-2-device-id"),
 808        true,
 809    )
 810    .await
 811    .unwrap_err();
 812    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
 813    assert_eq!(invite_count, 1);
 814
 815    // A newer user can invite an existing one via a different email address
 816    // than the one they used to sign up.
 817    let user5 = db
 818        .create_user(
 819            "user5@example.com",
 820            false,
 821            NewUserParams {
 822                github_login: "user5".into(),
 823                github_user_id: 5,
 824                invite_count: 0,
 825            },
 826        )
 827        .await
 828        .unwrap()
 829        .user_id;
 830    db.set_invite_count_for_user(user5, 5).await.unwrap();
 831    let (user5_invite_code, _) = db.get_invite_code_for_user(user5).await.unwrap().unwrap();
 832    let user5_invite_to_user1 = db
 833        .create_invite_from_code(&user5_invite_code, "user1@different.com", None, true)
 834        .await
 835        .unwrap();
 836    let user1_2 = db
 837        .create_user_from_invite(
 838            &user5_invite_to_user1,
 839            NewUserParams {
 840                github_login: "user1".into(),
 841                github_user_id: 1,
 842                invite_count: 5,
 843            },
 844        )
 845        .await
 846        .unwrap()
 847        .unwrap()
 848        .user_id;
 849    assert_eq!(user1_2, user1);
 850    assert_eq!(
 851        db.get_contacts(user1).await.unwrap(),
 852        [
 853            Contact::Accepted {
 854                user_id: user2,
 855                should_notify: true,
 856                busy: false,
 857            },
 858            Contact::Accepted {
 859                user_id: user3,
 860                should_notify: true,
 861                busy: false,
 862            },
 863            Contact::Accepted {
 864                user_id: user4,
 865                should_notify: true,
 866                busy: false,
 867            },
 868            Contact::Accepted {
 869                user_id: user5,
 870                should_notify: false,
 871                busy: false,
 872            }
 873        ]
 874    );
 875    assert_eq!(
 876        db.get_contacts(user5).await.unwrap(),
 877        [Contact::Accepted {
 878            user_id: user1,
 879            should_notify: true,
 880            busy: false,
 881        }]
 882    );
 883    assert!(db.has_contact(user1, user5).await.unwrap());
 884    assert!(db.has_contact(user5, user1).await.unwrap());
 885}
 886
 887test_both_dbs!(test_channels_postgres, test_channels_sqlite, db, {
 888    let a_id = db
 889        .create_user(
 890            "user1@example.com",
 891            false,
 892            NewUserParams {
 893                github_login: "user1".into(),
 894                github_user_id: 5,
 895                invite_count: 0,
 896            },
 897        )
 898        .await
 899        .unwrap()
 900        .user_id;
 901
 902    let b_id = db
 903        .create_user(
 904            "user2@example.com",
 905            false,
 906            NewUserParams {
 907                github_login: "user2".into(),
 908                github_user_id: 6,
 909                invite_count: 0,
 910            },
 911        )
 912        .await
 913        .unwrap()
 914        .user_id;
 915
 916    let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
 917
 918    // Make sure that people cannot read channels they haven't been invited to
 919    assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
 920
 921    db.invite_channel_member(zed_id, b_id, a_id, false)
 922        .await
 923        .unwrap();
 924
 925    db.respond_to_channel_invite(zed_id, b_id, true)
 926        .await
 927        .unwrap();
 928
 929    let crdb_id = db
 930        .create_channel("crdb", Some(zed_id), "2", a_id)
 931        .await
 932        .unwrap();
 933    let livestreaming_id = db
 934        .create_channel("livestreaming", Some(zed_id), "3", a_id)
 935        .await
 936        .unwrap();
 937    let replace_id = db
 938        .create_channel("replace", Some(zed_id), "4", a_id)
 939        .await
 940        .unwrap();
 941
 942    let mut members = db.get_channel_members(replace_id).await.unwrap();
 943    members.sort();
 944    assert_eq!(members, &[a_id, b_id]);
 945
 946    let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap();
 947    let cargo_id = db
 948        .create_channel("cargo", Some(rust_id), "6", a_id)
 949        .await
 950        .unwrap();
 951
 952    let cargo_ra_id = db
 953        .create_channel("cargo-ra", Some(cargo_id), "7", a_id)
 954        .await
 955        .unwrap();
 956
 957    let result = db.get_channels_for_user(a_id).await.unwrap();
 958    assert_eq!(
 959        result.channels,
 960        vec![
 961            Channel {
 962                id: zed_id,
 963                name: "zed".to_string(),
 964                parent_id: None,
 965            },
 966            Channel {
 967                id: crdb_id,
 968                name: "crdb".to_string(),
 969                parent_id: Some(zed_id),
 970            },
 971            Channel {
 972                id: livestreaming_id,
 973                name: "livestreaming".to_string(),
 974                parent_id: Some(zed_id),
 975            },
 976            Channel {
 977                id: replace_id,
 978                name: "replace".to_string(),
 979                parent_id: Some(zed_id),
 980            },
 981            Channel {
 982                id: rust_id,
 983                name: "rust".to_string(),
 984                parent_id: None,
 985            },
 986            Channel {
 987                id: cargo_id,
 988                name: "cargo".to_string(),
 989                parent_id: Some(rust_id),
 990            },
 991            Channel {
 992                id: cargo_ra_id,
 993                name: "cargo-ra".to_string(),
 994                parent_id: Some(cargo_id),
 995            }
 996        ]
 997    );
 998
 999    let result = db.get_channels_for_user(b_id).await.unwrap();
1000    assert_eq!(
1001        result.channels,
1002        vec![
1003            Channel {
1004                id: zed_id,
1005                name: "zed".to_string(),
1006                parent_id: None,
1007            },
1008            Channel {
1009                id: crdb_id,
1010                name: "crdb".to_string(),
1011                parent_id: Some(zed_id),
1012            },
1013            Channel {
1014                id: livestreaming_id,
1015                name: "livestreaming".to_string(),
1016                parent_id: Some(zed_id),
1017            },
1018            Channel {
1019                id: replace_id,
1020                name: "replace".to_string(),
1021                parent_id: Some(zed_id),
1022            },
1023        ]
1024    );
1025
1026    // Update member permissions
1027    let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
1028    assert!(set_subchannel_admin.is_err());
1029    let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
1030    assert!(set_channel_admin.is_ok());
1031
1032    let result = db.get_channels_for_user(b_id).await.unwrap();
1033    assert_eq!(
1034        result.channels,
1035        vec![
1036            Channel {
1037                id: zed_id,
1038                name: "zed".to_string(),
1039                parent_id: None,
1040            },
1041            Channel {
1042                id: crdb_id,
1043                name: "crdb".to_string(),
1044                parent_id: Some(zed_id),
1045            },
1046            Channel {
1047                id: livestreaming_id,
1048                name: "livestreaming".to_string(),
1049                parent_id: Some(zed_id),
1050            },
1051            Channel {
1052                id: replace_id,
1053                name: "replace".to_string(),
1054                parent_id: Some(zed_id),
1055            },
1056        ]
1057    );
1058
1059    // Remove a single channel
1060    db.remove_channel(crdb_id, a_id).await.unwrap();
1061    assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
1062
1063    // Remove a channel tree
1064    let (mut channel_ids, user_ids) = db.remove_channel(rust_id, a_id).await.unwrap();
1065    channel_ids.sort();
1066    assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
1067    assert_eq!(user_ids, &[a_id]);
1068
1069    assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
1070    assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
1071    assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
1072});
1073
1074test_both_dbs!(
1075    test_joining_channels_postgres,
1076    test_joining_channels_sqlite,
1077    db,
1078    {
1079        let owner_id = db.create_server("test").await.unwrap().0 as u32;
1080
1081        let user_1 = db
1082            .create_user(
1083                "user1@example.com",
1084                false,
1085                NewUserParams {
1086                    github_login: "user1".into(),
1087                    github_user_id: 5,
1088                    invite_count: 0,
1089                },
1090            )
1091            .await
1092            .unwrap()
1093            .user_id;
1094        let user_2 = db
1095            .create_user(
1096                "user2@example.com",
1097                false,
1098                NewUserParams {
1099                    github_login: "user2".into(),
1100                    github_user_id: 6,
1101                    invite_count: 0,
1102                },
1103            )
1104            .await
1105            .unwrap()
1106            .user_id;
1107
1108        let channel_1 = db
1109            .create_root_channel("channel_1", "1", user_1)
1110            .await
1111            .unwrap();
1112        let room_1 = db.room_id_for_channel(channel_1).await.unwrap();
1113
1114        // can join a room with membership to its channel
1115        let joined_room = db
1116            .join_room(
1117                room_1,
1118                user_1,
1119                Some(channel_1),
1120                ConnectionId { owner_id, id: 1 },
1121            )
1122            .await
1123            .unwrap();
1124        assert_eq!(joined_room.room.participants.len(), 1);
1125
1126        drop(joined_room);
1127        // cannot join a room without membership to its channel
1128        assert!(db
1129            .join_room(
1130                room_1,
1131                user_2,
1132                Some(channel_1),
1133                ConnectionId { owner_id, id: 1 }
1134            )
1135            .await
1136            .is_err());
1137    }
1138);
1139
1140test_both_dbs!(
1141    test_channel_invites_postgres,
1142    test_channel_invites_sqlite,
1143    db,
1144    {
1145        db.create_server("test").await.unwrap();
1146
1147        let user_1 = db
1148            .create_user(
1149                "user1@example.com",
1150                false,
1151                NewUserParams {
1152                    github_login: "user1".into(),
1153                    github_user_id: 5,
1154                    invite_count: 0,
1155                },
1156            )
1157            .await
1158            .unwrap()
1159            .user_id;
1160        let user_2 = db
1161            .create_user(
1162                "user2@example.com",
1163                false,
1164                NewUserParams {
1165                    github_login: "user2".into(),
1166                    github_user_id: 6,
1167                    invite_count: 0,
1168                },
1169            )
1170            .await
1171            .unwrap()
1172            .user_id;
1173
1174        let user_3 = db
1175            .create_user(
1176                "user3@example.com",
1177                false,
1178                NewUserParams {
1179                    github_login: "user3".into(),
1180                    github_user_id: 7,
1181                    invite_count: 0,
1182                },
1183            )
1184            .await
1185            .unwrap()
1186            .user_id;
1187
1188        let channel_1_1 = db
1189            .create_root_channel("channel_1", "1", user_1)
1190            .await
1191            .unwrap();
1192
1193        let channel_1_2 = db
1194            .create_root_channel("channel_2", "2", user_1)
1195            .await
1196            .unwrap();
1197
1198        db.invite_channel_member(channel_1_1, user_2, user_1, false)
1199            .await
1200            .unwrap();
1201        db.invite_channel_member(channel_1_2, user_2, user_1, false)
1202            .await
1203            .unwrap();
1204        db.invite_channel_member(channel_1_1, user_3, user_1, true)
1205            .await
1206            .unwrap();
1207
1208        let user_2_invites = db
1209            .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
1210            .await
1211            .unwrap()
1212            .into_iter()
1213            .map(|channel| channel.id)
1214            .collect::<Vec<_>>();
1215
1216        assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
1217
1218        let user_3_invites = db
1219            .get_channel_invites_for_user(user_3) // -> [channel_1_1]
1220            .await
1221            .unwrap()
1222            .into_iter()
1223            .map(|channel| channel.id)
1224            .collect::<Vec<_>>();
1225
1226        assert_eq!(user_3_invites, &[channel_1_1]);
1227
1228        let members = db
1229            .get_channel_member_details(channel_1_1, user_1)
1230            .await
1231            .unwrap();
1232        assert_eq!(
1233            members,
1234            &[
1235                proto::ChannelMember {
1236                    user_id: user_1.to_proto(),
1237                    kind: proto::channel_member::Kind::Member.into(),
1238                    admin: true,
1239                },
1240                proto::ChannelMember {
1241                    user_id: user_2.to_proto(),
1242                    kind: proto::channel_member::Kind::Invitee.into(),
1243                    admin: false,
1244                },
1245                proto::ChannelMember {
1246                    user_id: user_3.to_proto(),
1247                    kind: proto::channel_member::Kind::Invitee.into(),
1248                    admin: true,
1249                },
1250            ]
1251        );
1252
1253        db.respond_to_channel_invite(channel_1_1, user_2, true)
1254            .await
1255            .unwrap();
1256
1257        let channel_1_3 = db
1258            .create_channel("channel_3", Some(channel_1_1), "1", user_1)
1259            .await
1260            .unwrap();
1261
1262        let members = db
1263            .get_channel_member_details(channel_1_3, user_1)
1264            .await
1265            .unwrap();
1266        assert_eq!(
1267            members,
1268            &[
1269                proto::ChannelMember {
1270                    user_id: user_1.to_proto(),
1271                    kind: proto::channel_member::Kind::Member.into(),
1272                    admin: true,
1273                },
1274                proto::ChannelMember {
1275                    user_id: user_2.to_proto(),
1276                    kind: proto::channel_member::Kind::AncestorMember.into(),
1277                    admin: false,
1278                },
1279            ]
1280        );
1281    }
1282);
1283
1284test_both_dbs!(
1285    test_channel_renames_postgres,
1286    test_channel_renames_sqlite,
1287    db,
1288    {
1289        db.create_server("test").await.unwrap();
1290
1291        let user_1 = db
1292            .create_user(
1293                "user1@example.com",
1294                false,
1295                NewUserParams {
1296                    github_login: "user1".into(),
1297                    github_user_id: 5,
1298                    invite_count: 0,
1299                },
1300            )
1301            .await
1302            .unwrap()
1303            .user_id;
1304
1305        let user_2 = db
1306            .create_user(
1307                "user2@example.com",
1308                false,
1309                NewUserParams {
1310                    github_login: "user2".into(),
1311                    github_user_id: 6,
1312                    invite_count: 0,
1313                },
1314            )
1315            .await
1316            .unwrap()
1317            .user_id;
1318
1319        let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap();
1320
1321        db.rename_channel(zed_id, user_1, "#zed-archive")
1322            .await
1323            .unwrap();
1324
1325        let zed_archive_id = zed_id;
1326
1327        let (channel, _) = db
1328            .get_channel(zed_archive_id, user_1)
1329            .await
1330            .unwrap()
1331            .unwrap();
1332        assert_eq!(channel.name, "zed-archive");
1333
1334        let non_permissioned_rename = db
1335            .rename_channel(zed_archive_id, user_2, "hacked-lol")
1336            .await;
1337        assert!(non_permissioned_rename.is_err());
1338
1339        let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
1340        assert!(bad_name_rename.is_err())
1341    }
1342);
1343
1344#[gpui::test]
1345async fn test_multiple_signup_overwrite() {
1346    let test_db = TestDb::postgres(build_background_executor());
1347    let db = test_db.db();
1348
1349    let email_address = "user_1@example.com".to_string();
1350
1351    let initial_signup_created_at_milliseconds = 0;
1352
1353    let initial_signup = NewSignup {
1354        email_address: email_address.clone(),
1355        platform_mac: false,
1356        platform_linux: true,
1357        platform_windows: false,
1358        editor_features: vec!["speed".into()],
1359        programming_languages: vec!["rust".into(), "c".into()],
1360        device_id: Some(format!("device_id")),
1361        added_to_mailing_list: false,
1362        created_at: Some(
1363            DateTime::from_timestamp_millis(initial_signup_created_at_milliseconds).unwrap(),
1364        ),
1365    };
1366
1367    db.create_signup(&initial_signup).await.unwrap();
1368
1369    let initial_signup_from_db = db.get_signup(&email_address).await.unwrap();
1370
1371    assert_eq!(
1372        initial_signup_from_db.clone(),
1373        signup::Model {
1374            email_address: initial_signup.email_address,
1375            platform_mac: initial_signup.platform_mac,
1376            platform_linux: initial_signup.platform_linux,
1377            platform_windows: initial_signup.platform_windows,
1378            editor_features: Some(initial_signup.editor_features),
1379            programming_languages: Some(initial_signup.programming_languages),
1380            added_to_mailing_list: initial_signup.added_to_mailing_list,
1381            ..initial_signup_from_db
1382        }
1383    );
1384
1385    let subsequent_signup = NewSignup {
1386        email_address: email_address.clone(),
1387        platform_mac: true,
1388        platform_linux: false,
1389        platform_windows: true,
1390        editor_features: vec!["git integration".into(), "clean design".into()],
1391        programming_languages: vec!["d".into(), "elm".into()],
1392        device_id: Some(format!("different_device_id")),
1393        added_to_mailing_list: true,
1394        // subsequent signup happens next day
1395        created_at: Some(
1396            DateTime::from_timestamp_millis(
1397                initial_signup_created_at_milliseconds + (1000 * 60 * 60 * 24),
1398            )
1399            .unwrap(),
1400        ),
1401    };
1402
1403    db.create_signup(&subsequent_signup).await.unwrap();
1404
1405    let subsequent_signup_from_db = db.get_signup(&email_address).await.unwrap();
1406
1407    assert_eq!(
1408        subsequent_signup_from_db.clone(),
1409        signup::Model {
1410            platform_mac: subsequent_signup.platform_mac,
1411            platform_linux: subsequent_signup.platform_linux,
1412            platform_windows: subsequent_signup.platform_windows,
1413            editor_features: Some(subsequent_signup.editor_features),
1414            programming_languages: Some(subsequent_signup.programming_languages),
1415            device_id: subsequent_signup.device_id,
1416            added_to_mailing_list: subsequent_signup.added_to_mailing_list,
1417            // shouldn't overwrite their creation Datetime - user shouldn't lose their spot in line
1418            created_at: initial_signup_from_db.created_at,
1419            ..subsequent_signup_from_db
1420        }
1421    );
1422}
1423
1424#[gpui::test]
1425async fn test_signups() {
1426    let test_db = TestDb::postgres(build_background_executor());
1427    let db = test_db.db();
1428
1429    let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
1430
1431    let all_signups = usernames
1432        .iter()
1433        .enumerate()
1434        .map(|(i, username)| NewSignup {
1435            email_address: format!("{username}@example.com"),
1436            platform_mac: true,
1437            platform_linux: i % 2 == 0,
1438            platform_windows: i % 4 == 0,
1439            editor_features: vec!["speed".into()],
1440            programming_languages: vec!["rust".into(), "c".into()],
1441            device_id: Some(format!("device_id_{i}")),
1442            added_to_mailing_list: i != 0, // One user failed to subscribe
1443            created_at: Some(DateTime::from_timestamp_millis(i as i64).unwrap()), // Signups are consecutive
1444        })
1445        .collect::<Vec<NewSignup>>();
1446
1447    // people sign up on the waitlist
1448    for signup in &all_signups {
1449        // users can sign up multiple times without issues
1450        for _ in 0..2 {
1451            db.create_signup(&signup).await.unwrap();
1452        }
1453    }
1454
1455    assert_eq!(
1456        db.get_waitlist_summary().await.unwrap(),
1457        WaitlistSummary {
1458            count: 8,
1459            mac_count: 8,
1460            linux_count: 4,
1461            windows_count: 2,
1462            unknown_count: 0,
1463        }
1464    );
1465
1466    // retrieve the next batch of signup emails to send
1467    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
1468    let addresses = signups_batch1
1469        .iter()
1470        .map(|s| &s.email_address)
1471        .collect::<Vec<_>>();
1472    assert_eq!(
1473        addresses,
1474        &[
1475            all_signups[0].email_address.as_str(),
1476            all_signups[1].email_address.as_str(),
1477            all_signups[2].email_address.as_str()
1478        ]
1479    );
1480    assert_ne!(
1481        signups_batch1[0].email_confirmation_code,
1482        signups_batch1[1].email_confirmation_code
1483    );
1484
1485    // the waitlist isn't updated until we record that the emails
1486    // were successfully sent.
1487    let signups_batch = db.get_unsent_invites(3).await.unwrap();
1488    assert_eq!(signups_batch, signups_batch1);
1489
1490    // once the emails go out, we can retrieve the next batch
1491    // of signups.
1492    db.record_sent_invites(&signups_batch1).await.unwrap();
1493    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
1494    let addresses = signups_batch2
1495        .iter()
1496        .map(|s| &s.email_address)
1497        .collect::<Vec<_>>();
1498    assert_eq!(
1499        addresses,
1500        &[
1501            all_signups[3].email_address.as_str(),
1502            all_signups[4].email_address.as_str(),
1503            all_signups[5].email_address.as_str()
1504        ]
1505    );
1506
1507    // the sent invites are excluded from the summary.
1508    assert_eq!(
1509        db.get_waitlist_summary().await.unwrap(),
1510        WaitlistSummary {
1511            count: 5,
1512            mac_count: 5,
1513            linux_count: 2,
1514            windows_count: 1,
1515            unknown_count: 0,
1516        }
1517    );
1518
1519    // user completes the signup process by providing their
1520    // github account.
1521    let NewUserResult {
1522        user_id,
1523        inviting_user_id,
1524        signup_device_id,
1525        ..
1526    } = db
1527        .create_user_from_invite(
1528            &Invite {
1529                ..signups_batch1[0].clone()
1530            },
1531            NewUserParams {
1532                github_login: usernames[0].clone(),
1533                github_user_id: 0,
1534                invite_count: 5,
1535            },
1536        )
1537        .await
1538        .unwrap()
1539        .unwrap();
1540    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
1541    assert!(inviting_user_id.is_none());
1542    assert_eq!(user.github_login, usernames[0]);
1543    assert_eq!(
1544        user.email_address,
1545        Some(all_signups[0].email_address.clone())
1546    );
1547    assert_eq!(user.invite_count, 5);
1548    assert_eq!(signup_device_id.unwrap(), "device_id_0");
1549
1550    // cannot redeem the same signup again.
1551    assert!(db
1552        .create_user_from_invite(
1553            &Invite {
1554                email_address: signups_batch1[0].email_address.clone(),
1555                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
1556            },
1557            NewUserParams {
1558                github_login: "some-other-github_account".into(),
1559                github_user_id: 1,
1560                invite_count: 5,
1561            },
1562        )
1563        .await
1564        .unwrap()
1565        .is_none());
1566
1567    // cannot redeem a signup with the wrong confirmation code.
1568    db.create_user_from_invite(
1569        &Invite {
1570            email_address: signups_batch1[1].email_address.clone(),
1571            email_confirmation_code: "the-wrong-code".to_string(),
1572        },
1573        NewUserParams {
1574            github_login: usernames[1].clone(),
1575            github_user_id: 2,
1576            invite_count: 5,
1577        },
1578    )
1579    .await
1580    .unwrap_err();
1581}
1582
1583fn build_background_executor() -> Arc<Background> {
1584    Deterministic::new(0).build_background()
1585}