Make some db tests pass against the new sea-orm implementation

Antonio Scandurra created

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql |    2 
crates/collab/src/db2.rs                                       |  164 
crates/collab/src/db2/project.rs                               |    7 
crates/collab/src/db2/project_collaborator.rs                  |    7 
crates/collab/src/db2/room.rs                                  |    3 
crates/collab/src/db2/room_participant.rs                      |   13 
crates/collab/src/db2/tests.rs                                 | 1453 ++-
crates/collab/src/db2/user.rs                                  |    3 
crates/collab/src/db2/worktree.rs                              |    4 
9 files changed, 897 insertions(+), 759 deletions(-)

Detailed changes

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -8,7 +8,7 @@ CREATE TABLE "users" (
     "inviter_id" INTEGER REFERENCES users (id),
     "connected_once" BOOLEAN NOT NULL DEFAULT false,
     "created_at" TIMESTAMP NOT NULL DEFAULT now,
-    "metrics_id" VARCHAR(255),
+    "metrics_id" TEXT,
     "github_user_id" INTEGER
 );
 CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");

crates/collab/src/db2.rs 🔗

@@ -18,6 +18,7 @@ use sea_orm::{
     entity::prelude::*, ConnectOptions, DatabaseConnection, DatabaseTransaction, DbErr,
     TransactionTrait,
 };
+use sea_query::OnConflict;
 use serde::{Deserialize, Serialize};
 use sqlx::migrate::{Migrate, Migration, MigrationSource};
 use sqlx::Connection;
@@ -42,7 +43,7 @@ pub struct Database {
 impl Database {
     pub async fn new(url: &str, max_connections: u32) -> Result<Self> {
         let mut options = ConnectOptions::new(url.into());
-        options.min_connections(1).max_connections(max_connections);
+        options.max_connections(max_connections);
         Ok(Self {
             url: url.into(),
             pool: sea_orm::Database::connect(options).await?,
@@ -58,7 +59,7 @@ impl Database {
         &self,
         migrations_path: &Path,
         ignore_checksum_mismatch: bool,
-    ) -> anyhow::Result<Vec<(Migration, Duration)>> {
+    ) -> anyhow::Result<(sqlx::AnyConnection, Vec<(Migration, Duration)>)> {
         let migrations = MigrationSource::resolve(migrations_path)
             .await
             .map_err(|err| anyhow!("failed to load migrations: {err:?}"))?;
@@ -92,11 +93,45 @@ impl Database {
             }
         }
 
-        Ok(new_migrations)
+        Ok((connection, new_migrations))
+    }
+
+    pub async fn create_user(
+        &self,
+        email_address: &str,
+        admin: bool,
+        params: NewUserParams,
+    ) -> Result<NewUserResult> {
+        self.transact(|tx| async {
+            let user = user::Entity::insert(user::ActiveModel {
+                email_address: ActiveValue::set(Some(email_address.into())),
+                github_login: ActiveValue::set(params.github_login.clone()),
+                github_user_id: ActiveValue::set(Some(params.github_user_id)),
+                admin: ActiveValue::set(admin),
+                metrics_id: ActiveValue::set(Uuid::new_v4()),
+                ..Default::default()
+            })
+            .on_conflict(
+                OnConflict::column(user::Column::GithubLogin)
+                    .update_column(user::Column::GithubLogin)
+                    .to_owned(),
+            )
+            .exec_with_returning(&tx)
+            .await?;
+
+            tx.commit().await?;
+
+            Ok(NewUserResult {
+                user_id: user.id,
+                metrics_id: user.metrics_id.to_string(),
+                signup_device_id: None,
+                inviting_user_id: None,
+            })
+        })
+        .await
     }
 
     pub async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<user::Model>> {
-        let ids = ids.iter().map(|id| id.0).collect::<Vec<_>>();
         self.transact(|tx| async {
             let tx = tx;
             Ok(user::Entity::find()
@@ -119,7 +154,7 @@ impl Database {
                 .one(&tx)
                 .await?
                 .ok_or_else(|| anyhow!("could not find participant"))?;
-            if participant.room_id != room_id.0 {
+            if participant.room_id != room_id {
                 return Err(anyhow!("shared project on unexpected room"))?;
             }
 
@@ -156,14 +191,14 @@ impl Database {
             .await?;
 
             let room = self.get_room(room_id, &tx).await?;
-            self.commit_room_transaction(room_id, tx, (ProjectId(project.id), room))
+            self.commit_room_transaction(room_id, tx, (project.id, room))
                 .await
         })
         .await
     }
 
     async fn get_room(&self, room_id: RoomId, tx: &DatabaseTransaction) -> Result<proto::Room> {
-        let db_room = room::Entity::find_by_id(room_id.0)
+        let db_room = room::Entity::find_by_id(room_id)
             .one(tx)
             .await?
             .ok_or_else(|| anyhow!("could not find room"))?;
@@ -184,7 +219,7 @@ impl Database {
                     (Some(0), Some(project_id)) => {
                         Some(proto::participant_location::Variant::SharedProject(
                             proto::participant_location::SharedProject {
-                                id: project_id as u64,
+                                id: project_id.to_proto(),
                             },
                         ))
                     }
@@ -198,7 +233,7 @@ impl Database {
                 participants.insert(
                     answering_connection_id,
                     proto::Participant {
-                        user_id: db_participant.user_id as u64,
+                        user_id: db_participant.user_id.to_proto(),
                         peer_id: answering_connection_id as u32,
                         projects: Default::default(),
                         location: Some(proto::ParticipantLocation { variant: location }),
@@ -206,9 +241,9 @@ impl Database {
                 );
             } else {
                 pending_participants.push(proto::PendingParticipant {
-                    user_id: db_participant.user_id as u64,
-                    calling_user_id: db_participant.calling_user_id as u64,
-                    initial_project_id: db_participant.initial_project_id.map(|id| id as u64),
+                    user_id: db_participant.user_id.to_proto(),
+                    calling_user_id: db_participant.calling_user_id.to_proto(),
+                    initial_project_id: db_participant.initial_project_id.map(|id| id.to_proto()),
                 });
             }
         }
@@ -225,12 +260,12 @@ impl Database {
                 let project = if let Some(project) = participant
                     .projects
                     .iter_mut()
-                    .find(|project| project.id as i32 == db_project.id)
+                    .find(|project| project.id == db_project.id.to_proto())
                 {
                     project
                 } else {
                     participant.projects.push(proto::ParticipantProject {
-                        id: db_project.id as u64,
+                        id: db_project.id.to_proto(),
                         worktree_root_names: Default::default(),
                     });
                     participant.projects.last_mut().unwrap()
@@ -243,7 +278,7 @@ impl Database {
         }
 
         Ok(proto::Room {
-            id: db_room.id as u64,
+            id: db_room.id.to_proto(),
             live_kit_room: db_room.live_kit_room,
             participants: participants.into_values().collect(),
             pending_participants,
@@ -393,6 +428,84 @@ macro_rules! id_type {
                 self.0.fmt(f)
             }
         }
+
+        impl From<$name> for sea_query::Value {
+            fn from(value: $name) -> Self {
+                sea_query::Value::Int(Some(value.0))
+            }
+        }
+
+        impl sea_orm::TryGetable for $name {
+            fn try_get(
+                res: &sea_orm::QueryResult,
+                pre: &str,
+                col: &str,
+            ) -> Result<Self, sea_orm::TryGetError> {
+                Ok(Self(i32::try_get(res, pre, col)?))
+            }
+        }
+
+        impl sea_query::ValueType for $name {
+            fn try_from(v: Value) -> Result<Self, sea_query::ValueTypeErr> {
+                match v {
+                    Value::TinyInt(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::SmallInt(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::Int(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::BigInt(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::TinyUnsigned(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::SmallUnsigned(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::Unsigned(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    Value::BigUnsigned(Some(int)) => {
+                        Ok(Self(int.try_into().map_err(|_| sea_query::ValueTypeErr)?))
+                    }
+                    _ => Err(sea_query::ValueTypeErr),
+                }
+            }
+
+            fn type_name() -> String {
+                stringify!($name).into()
+            }
+
+            fn array_type() -> sea_query::ArrayType {
+                sea_query::ArrayType::Int
+            }
+
+            fn column_type() -> sea_query::ColumnType {
+                sea_query::ColumnType::Integer(None)
+            }
+        }
+
+        impl sea_orm::TryFromU64 for $name {
+            fn try_from_u64(n: u64) -> Result<Self, DbErr> {
+                Ok(Self(n.try_into().map_err(|_| {
+                    DbErr::ConvertFromU64(concat!(
+                        "error converting ",
+                        stringify!($name),
+                        " to u64"
+                    ))
+                })?))
+            }
+        }
+
+        impl sea_query::Nullable for $name {
+            fn null() -> Value {
+                Value::Int(None)
+            }
+        }
     };
 }
 
@@ -400,6 +513,7 @@ id_type!(UserId);
 id_type!(RoomId);
 id_type!(RoomParticipantId);
 id_type!(ProjectId);
+id_type!(ProjectCollaboratorId);
 id_type!(WorktreeId);
 
 #[cfg(test)]
@@ -412,17 +526,18 @@ mod test {
     use lazy_static::lazy_static;
     use parking_lot::Mutex;
     use rand::prelude::*;
+    use sea_orm::ConnectionTrait;
     use sqlx::migrate::MigrateDatabase;
     use std::sync::Arc;
 
     pub struct TestDb {
         pub db: Option<Arc<Database>>,
+        pub connection: Option<sqlx::AnyConnection>,
     }
 
     impl TestDb {
         pub fn sqlite(background: Arc<Background>) -> Self {
-            let mut rng = StdRng::from_entropy();
-            let url = format!("sqlite://file:zed-test-{}?mode=memory", rng.gen::<u128>());
+            let url = format!("sqlite::memory:");
             let runtime = tokio::runtime::Builder::new_current_thread()
                 .enable_io()
                 .enable_time()
@@ -431,8 +546,17 @@ mod test {
 
             let mut db = runtime.block_on(async {
                 let db = Database::new(&url, 5).await.unwrap();
-                let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations.sqlite");
-                db.migrate(migrations_path.as_ref(), false).await.unwrap();
+                let sql = include_str!(concat!(
+                    env!("CARGO_MANIFEST_DIR"),
+                    "/migrations.sqlite/20221109000000_test_schema.sql"
+                ));
+                db.pool
+                    .execute(sea_orm::Statement::from_string(
+                        db.pool.get_database_backend(),
+                        sql.into(),
+                    ))
+                    .await
+                    .unwrap();
                 db
             });
 
@@ -441,6 +565,7 @@ mod test {
 
             Self {
                 db: Some(Arc::new(db)),
+                connection: None,
             }
         }
 
@@ -476,6 +601,7 @@ mod test {
 
             Self {
                 db: Some(Arc::new(db)),
+                connection: None,
             }
         }
 

crates/collab/src/db2/project.rs 🔗

@@ -1,12 +1,13 @@
+use super::{ProjectId, RoomId, UserId};
 use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "projects")]
 pub struct Model {
     #[sea_orm(primary_key)]
-    pub id: i32,
-    pub room_id: i32,
-    pub host_user_id: i32,
+    pub id: ProjectId,
+    pub room_id: RoomId,
+    pub host_user_id: UserId,
     pub host_connection_id: i32,
 }
 

crates/collab/src/db2/project_collaborator.rs 🔗

@@ -1,13 +1,14 @@
+use super::{ProjectCollaboratorId, ProjectId, UserId};
 use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "project_collaborators")]
 pub struct Model {
     #[sea_orm(primary_key)]
-    pub id: i32,
-    pub project_id: i32,
+    pub id: ProjectCollaboratorId,
+    pub project_id: ProjectId,
     pub connection_id: i32,
-    pub user_id: i32,
+    pub user_id: UserId,
     pub replica_id: i32,
     pub is_host: bool,
 }

crates/collab/src/db2/room.rs 🔗

@@ -1,10 +1,11 @@
+use super::RoomId;
 use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "room_participants")]
 pub struct Model {
     #[sea_orm(primary_key)]
-    pub id: i32,
+    pub id: RoomId,
     pub live_kit_room: String,
 }
 

crates/collab/src/db2/room_participant.rs 🔗

@@ -1,17 +1,18 @@
+use super::{ProjectId, RoomId, RoomParticipantId, UserId};
 use sea_orm::entity::prelude::*;
 
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "room_participants")]
 pub struct Model {
     #[sea_orm(primary_key)]
-    pub id: i32,
-    pub room_id: i32,
-    pub user_id: i32,
+    pub id: RoomParticipantId,
+    pub room_id: RoomId,
+    pub user_id: UserId,
     pub answering_connection_id: Option<i32>,
     pub location_kind: Option<i32>,
-    pub location_project_id: Option<i32>,
-    pub initial_project_id: Option<i32>,
-    pub calling_user_id: i32,
+    pub location_project_id: Option<ProjectId>,
+    pub initial_project_id: Option<ProjectId>,
+    pub calling_user_id: UserId,
     pub calling_connection_id: i32,
 }
 

crates/collab/src/db2/tests.rs 🔗

@@ -26,9 +26,10 @@ test_both_dbs!(
     db,
     {
         let mut user_ids = Vec::new();
+        let mut user_metric_ids = Vec::new();
         for i in 1..=4 {
-            user_ids.push(
-                db.create_user(
+            let user = db
+                .create_user(
                     &format!("user{i}@example.com"),
                     false,
                     NewUserParams {
@@ -38,9 +39,9 @@ test_both_dbs!(
                     },
                 )
                 .await
-                .unwrap()
-                .user_id,
-            );
+                .unwrap();
+            user_ids.push(user.user_id);
+            user_metric_ids.push(user.metrics_id);
         }
 
         assert_eq!(
@@ -52,6 +53,7 @@ test_both_dbs!(
                     github_user_id: Some(1),
                     email_address: Some("user1@example.com".to_string()),
                     admin: false,
+                    metrics_id: user_metric_ids[0].parse().unwrap(),
                     ..Default::default()
                 },
                 User {
@@ -60,6 +62,7 @@ test_both_dbs!(
                     github_user_id: Some(2),
                     email_address: Some("user2@example.com".to_string()),
                     admin: false,
+                    metrics_id: user_metric_ids[1].parse().unwrap(),
                     ..Default::default()
                 },
                 User {
@@ -68,6 +71,7 @@ test_both_dbs!(
                     github_user_id: Some(3),
                     email_address: Some("user3@example.com".to_string()),
                     admin: false,
+                    metrics_id: user_metric_ids[2].parse().unwrap(),
                     ..Default::default()
                 },
                 User {
@@ -76,6 +80,7 @@ test_both_dbs!(
                     github_user_id: Some(4),
                     email_address: Some("user4@example.com".to_string()),
                     admin: false,
+                    metrics_id: user_metric_ids[3].parse().unwrap(),
                     ..Default::default()
                 }
             ]
@@ -83,725 +88,725 @@ test_both_dbs!(
     }
 );
 
-test_both_dbs!(
-    test_get_user_by_github_account_postgres,
-    test_get_user_by_github_account_sqlite,
-    db,
-    {
-        let user_id1 = db
-            .create_user(
-                "user1@example.com",
-                false,
-                NewUserParams {
-                    github_login: "login1".into(),
-                    github_user_id: 101,
-                    invite_count: 0,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id;
-        let user_id2 = db
-            .create_user(
-                "user2@example.com",
-                false,
-                NewUserParams {
-                    github_login: "login2".into(),
-                    github_user_id: 102,
-                    invite_count: 0,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id;
-
-        let user = db
-            .get_user_by_github_account("login1", None)
-            .await
-            .unwrap()
-            .unwrap();
-        assert_eq!(user.id, user_id1);
-        assert_eq!(&user.github_login, "login1");
-        assert_eq!(user.github_user_id, Some(101));
-
-        assert!(db
-            .get_user_by_github_account("non-existent-login", None)
-            .await
-            .unwrap()
-            .is_none());
-
-        let user = db
-            .get_user_by_github_account("the-new-login2", Some(102))
-            .await
-            .unwrap()
-            .unwrap();
-        assert_eq!(user.id, user_id2);
-        assert_eq!(&user.github_login, "the-new-login2");
-        assert_eq!(user.github_user_id, Some(102));
-    }
-);
-
-test_both_dbs!(
-    test_create_access_tokens_postgres,
-    test_create_access_tokens_sqlite,
-    db,
-    {
-        let user = db
-            .create_user(
-                "u1@example.com",
-                false,
-                NewUserParams {
-                    github_login: "u1".into(),
-                    github_user_id: 1,
-                    invite_count: 0,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id;
-
-        db.create_access_token_hash(user, "h1", 3).await.unwrap();
-        db.create_access_token_hash(user, "h2", 3).await.unwrap();
-        assert_eq!(
-            db.get_access_token_hashes(user).await.unwrap(),
-            &["h2".to_string(), "h1".to_string()]
-        );
-
-        db.create_access_token_hash(user, "h3", 3).await.unwrap();
-        assert_eq!(
-            db.get_access_token_hashes(user).await.unwrap(),
-            &["h3".to_string(), "h2".to_string(), "h1".to_string(),]
-        );
-
-        db.create_access_token_hash(user, "h4", 3).await.unwrap();
-        assert_eq!(
-            db.get_access_token_hashes(user).await.unwrap(),
-            &["h4".to_string(), "h3".to_string(), "h2".to_string(),]
-        );
-
-        db.create_access_token_hash(user, "h5", 3).await.unwrap();
-        assert_eq!(
-            db.get_access_token_hashes(user).await.unwrap(),
-            &["h5".to_string(), "h4".to_string(), "h3".to_string()]
-        );
-    }
-);
-
-test_both_dbs!(test_add_contacts_postgres, test_add_contacts_sqlite, db, {
-    let mut user_ids = Vec::new();
-    for i in 0..3 {
-        user_ids.push(
-            db.create_user(
-                &format!("user{i}@example.com"),
-                false,
-                NewUserParams {
-                    github_login: format!("user{i}"),
-                    github_user_id: i,
-                    invite_count: 0,
-                },
-            )
-            .await
-            .unwrap()
-            .user_id,
-        );
-    }
-
-    let user_1 = user_ids[0];
-    let user_2 = user_ids[1];
-    let user_3 = user_ids[2];
-
-    // User starts with no contacts
-    assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
-
-    // User requests a contact. Both users see the pending request.
-    db.send_contact_request(user_1, user_2).await.unwrap();
-    assert!(!db.has_contact(user_1, user_2).await.unwrap());
-    assert!(!db.has_contact(user_2, user_1).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Outgoing { user_id: user_2 }],
-    );
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Incoming {
-            user_id: user_1,
-            should_notify: true
-        }]
-    );
-
-    // User 2 dismisses the contact request notification without accepting or rejecting.
-    // We shouldn't notify them again.
-    db.dismiss_contact_notification(user_1, user_2)
-        .await
-        .unwrap_err();
-    db.dismiss_contact_notification(user_2, user_1)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Incoming {
-            user_id: user_1,
-            should_notify: false
-        }]
-    );
-
-    // User can't accept their own contact request
-    db.respond_to_contact_request(user_1, user_2, true)
-        .await
-        .unwrap_err();
-
-    // User accepts a contact request. Both users see the contact.
-    db.respond_to_contact_request(user_2, user_1, true)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            should_notify: true,
-            busy: false,
-        }],
-    );
-    assert!(db.has_contact(user_1, user_2).await.unwrap());
-    assert!(db.has_contact(user_2, user_1).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-
-    // Users cannot re-request existing contacts.
-    db.send_contact_request(user_1, user_2).await.unwrap_err();
-    db.send_contact_request(user_2, user_1).await.unwrap_err();
-
-    // Users can't dismiss notifications of them accepting other users' requests.
-    db.dismiss_contact_notification(user_2, user_1)
-        .await
-        .unwrap_err();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            should_notify: true,
-            busy: false,
-        }]
-    );
-
-    // Users can dismiss notifications of other users accepting their requests.
-    db.dismiss_contact_notification(user_1, user_2)
-        .await
-        .unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_2,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-
-    // Users send each other concurrent contact requests and
-    // see that they are immediately accepted.
-    db.send_contact_request(user_1, user_3).await.unwrap();
-    db.send_contact_request(user_3, user_1).await.unwrap();
-    assert_eq!(
-        db.get_contacts(user_1).await.unwrap(),
-        &[
-            Contact::Accepted {
-                user_id: user_2,
-                should_notify: false,
-                busy: false,
-            },
-            Contact::Accepted {
-                user_id: user_3,
-                should_notify: false,
-                busy: false,
-            }
-        ]
-    );
-    assert_eq!(
-        db.get_contacts(user_3).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            should_notify: false,
-            busy: false,
-        }],
-    );
-
-    // User declines a contact request. Both users see that it is gone.
-    db.send_contact_request(user_2, user_3).await.unwrap();
-    db.respond_to_contact_request(user_3, user_2, false)
-        .await
-        .unwrap();
-    assert!(!db.has_contact(user_2, user_3).await.unwrap());
-    assert!(!db.has_contact(user_3, user_2).await.unwrap());
-    assert_eq!(
-        db.get_contacts(user_2).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_contacts(user_3).await.unwrap(),
-        &[Contact::Accepted {
-            user_id: user_1,
-            should_notify: false,
-            busy: false,
-        }],
-    );
-});
-
-test_both_dbs!(test_metrics_id_postgres, test_metrics_id_sqlite, db, {
-    let NewUserResult {
-        user_id: user1,
-        metrics_id: metrics_id1,
-        ..
-    } = db
-        .create_user(
-            "person1@example.com",
-            false,
-            NewUserParams {
-                github_login: "person1".into(),
-                github_user_id: 101,
-                invite_count: 5,
-            },
-        )
-        .await
-        .unwrap();
-    let NewUserResult {
-        user_id: user2,
-        metrics_id: metrics_id2,
-        ..
-    } = db
-        .create_user(
-            "person2@example.com",
-            false,
-            NewUserParams {
-                github_login: "person2".into(),
-                github_user_id: 102,
-                invite_count: 5,
-            },
-        )
-        .await
-        .unwrap();
-
-    assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
-    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
-    assert_eq!(metrics_id1.len(), 36);
-    assert_eq!(metrics_id2.len(), 36);
-    assert_ne!(metrics_id1, metrics_id2);
-});
-
-#[test]
-fn test_fuzzy_like_string() {
-    assert_eq!(DefaultDb::fuzzy_like_string("abcd"), "%a%b%c%d%");
-    assert_eq!(DefaultDb::fuzzy_like_string("x y"), "%x%y%");
-    assert_eq!(DefaultDb::fuzzy_like_string(" z  "), "%z%");
-}
-
-#[gpui::test]
-async fn test_fuzzy_search_users() {
-    let test_db = PostgresTestDb::new(build_background_executor());
-    let db = test_db.db();
-    for (i, github_login) in [
-        "California",
-        "colorado",
-        "oregon",
-        "washington",
-        "florida",
-        "delaware",
-        "rhode-island",
-    ]
-    .into_iter()
-    .enumerate()
-    {
-        db.create_user(
-            &format!("{github_login}@example.com"),
-            false,
-            NewUserParams {
-                github_login: github_login.into(),
-                github_user_id: i as i32,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap();
-    }
-
-    assert_eq!(
-        fuzzy_search_user_names(db, "clr").await,
-        &["colorado", "California"]
-    );
-    assert_eq!(
-        fuzzy_search_user_names(db, "ro").await,
-        &["rhode-island", "colorado", "oregon"],
-    );
-
-    async fn fuzzy_search_user_names(db: &Db<sqlx::Postgres>, query: &str) -> Vec<String> {
-        db.fuzzy_search_users(query, 10)
-            .await
-            .unwrap()
-            .into_iter()
-            .map(|user| user.github_login)
-            .collect::<Vec<_>>()
-    }
-}
-
-#[gpui::test]
-async fn test_invite_codes() {
-    let test_db = PostgresTestDb::new(build_background_executor());
-    let db = test_db.db();
-
-    let NewUserResult { user_id: user1, .. } = db
-        .create_user(
-            "user1@example.com",
-            false,
-            NewUserParams {
-                github_login: "user1".into(),
-                github_user_id: 0,
-                invite_count: 0,
-            },
-        )
-        .await
-        .unwrap();
-
-    // Initially, user 1 has no invite code
-    assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
-
-    // Setting invite count to 0 when no code is assigned does not assign a new code
-    db.set_invite_count_for_user(user1, 0).await.unwrap();
-    assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
-
-    // User 1 creates an invite code that can be used twice.
-    db.set_invite_count_for_user(user1, 2).await.unwrap();
-    let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(invite_count, 2);
-
-    // User 2 redeems the invite code and becomes a contact of user 1.
-    let user2_invite = db
-        .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
-        .await
-        .unwrap();
-    let NewUserResult {
-        user_id: user2,
-        inviting_user_id,
-        signup_device_id,
-        metrics_id,
-    } = db
-        .create_user_from_invite(
-            &user2_invite,
-            NewUserParams {
-                github_login: "user2".into(),
-                github_user_id: 2,
-                invite_count: 7,
-            },
-        )
-        .await
-        .unwrap()
-        .unwrap();
-    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(invite_count, 1);
-    assert_eq!(inviting_user_id, Some(user1));
-    assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
-    assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
-    assert_eq!(
-        db.get_contacts(user1).await.unwrap(),
-        [Contact::Accepted {
-            user_id: user2,
-            should_notify: true,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_contacts(user2).await.unwrap(),
-        [Contact::Accepted {
-            user_id: user1,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
-        7
-    );
-
-    // User 3 redeems the invite code and becomes a contact of user 1.
-    let user3_invite = db
-        .create_invite_from_code(&invite_code, "user3@example.com", None)
-        .await
-        .unwrap();
-    let NewUserResult {
-        user_id: user3,
-        inviting_user_id,
-        signup_device_id,
-        ..
-    } = db
-        .create_user_from_invite(
-            &user3_invite,
-            NewUserParams {
-                github_login: "user-3".into(),
-                github_user_id: 3,
-                invite_count: 3,
-            },
-        )
-        .await
-        .unwrap()
-        .unwrap();
-    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(invite_count, 0);
-    assert_eq!(inviting_user_id, Some(user1));
-    assert!(signup_device_id.is_none());
-    assert_eq!(
-        db.get_contacts(user1).await.unwrap(),
-        [
-            Contact::Accepted {
-                user_id: user2,
-                should_notify: true,
-                busy: false,
-            },
-            Contact::Accepted {
-                user_id: user3,
-                should_notify: true,
-                busy: false,
-            }
-        ]
-    );
-    assert_eq!(
-        db.get_contacts(user3).await.unwrap(),
-        [Contact::Accepted {
-            user_id: user1,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
-        3
-    );
-
-    // Trying to reedem the code for the third time results in an error.
-    db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
-        .await
-        .unwrap_err();
-
-    // Invite count can be updated after the code has been created.
-    db.set_invite_count_for_user(user1, 2).await.unwrap();
-    let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
-    assert_eq!(invite_count, 2);
-
-    // User 4 can now redeem the invite code and becomes a contact of user 1.
-    let user4_invite = db
-        .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
-        .await
-        .unwrap();
-    let user4 = db
-        .create_user_from_invite(
-            &user4_invite,
-            NewUserParams {
-                github_login: "user-4".into(),
-                github_user_id: 4,
-                invite_count: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .unwrap()
-        .user_id;
-
-    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(invite_count, 1);
-    assert_eq!(
-        db.get_contacts(user1).await.unwrap(),
-        [
-            Contact::Accepted {
-                user_id: user2,
-                should_notify: true,
-                busy: false,
-            },
-            Contact::Accepted {
-                user_id: user3,
-                should_notify: true,
-                busy: false,
-            },
-            Contact::Accepted {
-                user_id: user4,
-                should_notify: true,
-                busy: false,
-            }
-        ]
-    );
-    assert_eq!(
-        db.get_contacts(user4).await.unwrap(),
-        [Contact::Accepted {
-            user_id: user1,
-            should_notify: false,
-            busy: false,
-        }]
-    );
-    assert_eq!(
-        db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
-        5
-    );
-
-    // An existing user cannot redeem invite codes.
-    db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
-        .await
-        .unwrap_err();
-    let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
-    assert_eq!(invite_count, 1);
-}
-
-#[gpui::test]
-async fn test_signups() {
-    let test_db = PostgresTestDb::new(build_background_executor());
-    let db = test_db.db();
-
-    // people sign up on the waitlist
-    for i in 0..8 {
-        db.create_signup(Signup {
-            email_address: format!("person-{i}@example.com"),
-            platform_mac: true,
-            platform_linux: i % 2 == 0,
-            platform_windows: i % 4 == 0,
-            editor_features: vec!["speed".into()],
-            programming_languages: vec!["rust".into(), "c".into()],
-            device_id: Some(format!("device_id_{i}")),
-        })
-        .await
-        .unwrap();
-    }
-
-    assert_eq!(
-        db.get_waitlist_summary().await.unwrap(),
-        WaitlistSummary {
-            count: 8,
-            mac_count: 8,
-            linux_count: 4,
-            windows_count: 2,
-            unknown_count: 0,
-        }
-    );
-
-    // retrieve the next batch of signup emails to send
-    let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
-    let addresses = signups_batch1
-        .iter()
-        .map(|s| &s.email_address)
-        .collect::<Vec<_>>();
-    assert_eq!(
-        addresses,
-        &[
-            "person-0@example.com",
-            "person-1@example.com",
-            "person-2@example.com"
-        ]
-    );
-    assert_ne!(
-        signups_batch1[0].email_confirmation_code,
-        signups_batch1[1].email_confirmation_code
-    );
-
-    // the waitlist isn't updated until we record that the emails
-    // were successfully sent.
-    let signups_batch = db.get_unsent_invites(3).await.unwrap();
-    assert_eq!(signups_batch, signups_batch1);
-
-    // once the emails go out, we can retrieve the next batch
-    // of signups.
-    db.record_sent_invites(&signups_batch1).await.unwrap();
-    let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
-    let addresses = signups_batch2
-        .iter()
-        .map(|s| &s.email_address)
-        .collect::<Vec<_>>();
-    assert_eq!(
-        addresses,
-        &[
-            "person-3@example.com",
-            "person-4@example.com",
-            "person-5@example.com"
-        ]
-    );
-
-    // the sent invites are excluded from the summary.
-    assert_eq!(
-        db.get_waitlist_summary().await.unwrap(),
-        WaitlistSummary {
-            count: 5,
-            mac_count: 5,
-            linux_count: 2,
-            windows_count: 1,
-            unknown_count: 0,
-        }
-    );
-
-    // user completes the signup process by providing their
-    // github account.
-    let NewUserResult {
-        user_id,
-        inviting_user_id,
-        signup_device_id,
-        ..
-    } = db
-        .create_user_from_invite(
-            &Invite {
-                email_address: signups_batch1[0].email_address.clone(),
-                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
-            },
-            NewUserParams {
-                github_login: "person-0".into(),
-                github_user_id: 0,
-                invite_count: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .unwrap();
-    let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
-    assert!(inviting_user_id.is_none());
-    assert_eq!(user.github_login, "person-0");
-    assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
-    assert_eq!(user.invite_count, 5);
-    assert_eq!(signup_device_id.unwrap(), "device_id_0");
-
-    // cannot redeem the same signup again.
-    assert!(db
-        .create_user_from_invite(
-            &Invite {
-                email_address: signups_batch1[0].email_address.clone(),
-                email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
-            },
-            NewUserParams {
-                github_login: "some-other-github_account".into(),
-                github_user_id: 1,
-                invite_count: 5,
-            },
-        )
-        .await
-        .unwrap()
-        .is_none());
-
-    // cannot redeem a signup with the wrong confirmation code.
-    db.create_user_from_invite(
-        &Invite {
-            email_address: signups_batch1[1].email_address.clone(),
-            email_confirmation_code: "the-wrong-code".to_string(),
-        },
-        NewUserParams {
-            github_login: "person-1".into(),
-            github_user_id: 2,
-            invite_count: 5,
-        },
-    )
-    .await
-    .unwrap_err();
-}
+// test_both_dbs!(
+//     test_get_user_by_github_account_postgres,
+//     test_get_user_by_github_account_sqlite,
+//     db,
+//     {
+//         let user_id1 = db
+//             .create_user(
+//                 "user1@example.com",
+//                 false,
+//                 NewUserParams {
+//                     github_login: "login1".into(),
+//                     github_user_id: 101,
+//                     invite_count: 0,
+//                 },
+//             )
+//             .await
+//             .unwrap()
+//             .user_id;
+//         let user_id2 = db
+//             .create_user(
+//                 "user2@example.com",
+//                 false,
+//                 NewUserParams {
+//                     github_login: "login2".into(),
+//                     github_user_id: 102,
+//                     invite_count: 0,
+//                 },
+//             )
+//             .await
+//             .unwrap()
+//             .user_id;
+
+//         let user = db
+//             .get_user_by_github_account("login1", None)
+//             .await
+//             .unwrap()
+//             .unwrap();
+//         assert_eq!(user.id, user_id1);
+//         assert_eq!(&user.github_login, "login1");
+//         assert_eq!(user.github_user_id, Some(101));
+
+//         assert!(db
+//             .get_user_by_github_account("non-existent-login", None)
+//             .await
+//             .unwrap()
+//             .is_none());
+
+//         let user = db
+//             .get_user_by_github_account("the-new-login2", Some(102))
+//             .await
+//             .unwrap()
+//             .unwrap();
+//         assert_eq!(user.id, user_id2);
+//         assert_eq!(&user.github_login, "the-new-login2");
+//         assert_eq!(user.github_user_id, Some(102));
+//     }
+// );
+
+// test_both_dbs!(
+//     test_create_access_tokens_postgres,
+//     test_create_access_tokens_sqlite,
+//     db,
+//     {
+//         let user = db
+//             .create_user(
+//                 "u1@example.com",
+//                 false,
+//                 NewUserParams {
+//                     github_login: "u1".into(),
+//                     github_user_id: 1,
+//                     invite_count: 0,
+//                 },
+//             )
+//             .await
+//             .unwrap()
+//             .user_id;
+
+//         db.create_access_token_hash(user, "h1", 3).await.unwrap();
+//         db.create_access_token_hash(user, "h2", 3).await.unwrap();
+//         assert_eq!(
+//             db.get_access_token_hashes(user).await.unwrap(),
+//             &["h2".to_string(), "h1".to_string()]
+//         );
+
+//         db.create_access_token_hash(user, "h3", 3).await.unwrap();
+//         assert_eq!(
+//             db.get_access_token_hashes(user).await.unwrap(),
+//             &["h3".to_string(), "h2".to_string(), "h1".to_string(),]
+//         );
+
+//         db.create_access_token_hash(user, "h4", 3).await.unwrap();
+//         assert_eq!(
+//             db.get_access_token_hashes(user).await.unwrap(),
+//             &["h4".to_string(), "h3".to_string(), "h2".to_string(),]
+//         );
+
+//         db.create_access_token_hash(user, "h5", 3).await.unwrap();
+//         assert_eq!(
+//             db.get_access_token_hashes(user).await.unwrap(),
+//             &["h5".to_string(), "h4".to_string(), "h3".to_string()]
+//         );
+//     }
+// );
+
+// test_both_dbs!(test_add_contacts_postgres, test_add_contacts_sqlite, db, {
+//     let mut user_ids = Vec::new();
+//     for i in 0..3 {
+//         user_ids.push(
+//             db.create_user(
+//                 &format!("user{i}@example.com"),
+//                 false,
+//                 NewUserParams {
+//                     github_login: format!("user{i}"),
+//                     github_user_id: i,
+//                     invite_count: 0,
+//                 },
+//             )
+//             .await
+//             .unwrap()
+//             .user_id,
+//         );
+//     }
+
+//     let user_1 = user_ids[0];
+//     let user_2 = user_ids[1];
+//     let user_3 = user_ids[2];
+
+//     // User starts with no contacts
+//     assert_eq!(db.get_contacts(user_1).await.unwrap(), &[]);
+
+//     // User requests a contact. Both users see the pending request.
+//     db.send_contact_request(user_1, user_2).await.unwrap();
+//     assert!(!db.has_contact(user_1, user_2).await.unwrap());
+//     assert!(!db.has_contact(user_2, user_1).await.unwrap());
+//     assert_eq!(
+//         db.get_contacts(user_1).await.unwrap(),
+//         &[Contact::Outgoing { user_id: user_2 }],
+//     );
+//     assert_eq!(
+//         db.get_contacts(user_2).await.unwrap(),
+//         &[Contact::Incoming {
+//             user_id: user_1,
+//             should_notify: true
+//         }]
+//     );
+
+//     // User 2 dismisses the contact request notification without accepting or rejecting.
+//     // We shouldn't notify them again.
+//     db.dismiss_contact_notification(user_1, user_2)
+//         .await
+//         .unwrap_err();
+//     db.dismiss_contact_notification(user_2, user_1)
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         db.get_contacts(user_2).await.unwrap(),
+//         &[Contact::Incoming {
+//             user_id: user_1,
+//             should_notify: false
+//         }]
+//     );
+
+//     // User can't accept their own contact request
+//     db.respond_to_contact_request(user_1, user_2, true)
+//         .await
+//         .unwrap_err();
+
+//     // User accepts a contact request. Both users see the contact.
+//     db.respond_to_contact_request(user_2, user_1, true)
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         db.get_contacts(user_1).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_2,
+//             should_notify: true,
+//             busy: false,
+//         }],
+//     );
+//     assert!(db.has_contact(user_1, user_2).await.unwrap());
+//     assert!(db.has_contact(user_2, user_1).await.unwrap());
+//     assert_eq!(
+//         db.get_contacts(user_2).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_1,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+
+//     // Users cannot re-request existing contacts.
+//     db.send_contact_request(user_1, user_2).await.unwrap_err();
+//     db.send_contact_request(user_2, user_1).await.unwrap_err();
+
+//     // Users can't dismiss notifications of them accepting other users' requests.
+//     db.dismiss_contact_notification(user_2, user_1)
+//         .await
+//         .unwrap_err();
+//     assert_eq!(
+//         db.get_contacts(user_1).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_2,
+//             should_notify: true,
+//             busy: false,
+//         }]
+//     );
+
+//     // Users can dismiss notifications of other users accepting their requests.
+//     db.dismiss_contact_notification(user_1, user_2)
+//         .await
+//         .unwrap();
+//     assert_eq!(
+//         db.get_contacts(user_1).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_2,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+
+//     // Users send each other concurrent contact requests and
+//     // see that they are immediately accepted.
+//     db.send_contact_request(user_1, user_3).await.unwrap();
+//     db.send_contact_request(user_3, user_1).await.unwrap();
+//     assert_eq!(
+//         db.get_contacts(user_1).await.unwrap(),
+//         &[
+//             Contact::Accepted {
+//                 user_id: user_2,
+//                 should_notify: false,
+//                 busy: false,
+//             },
+//             Contact::Accepted {
+//                 user_id: user_3,
+//                 should_notify: false,
+//                 busy: false,
+//             }
+//         ]
+//     );
+//     assert_eq!(
+//         db.get_contacts(user_3).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_1,
+//             should_notify: false,
+//             busy: false,
+//         }],
+//     );
+
+//     // User declines a contact request. Both users see that it is gone.
+//     db.send_contact_request(user_2, user_3).await.unwrap();
+//     db.respond_to_contact_request(user_3, user_2, false)
+//         .await
+//         .unwrap();
+//     assert!(!db.has_contact(user_2, user_3).await.unwrap());
+//     assert!(!db.has_contact(user_3, user_2).await.unwrap());
+//     assert_eq!(
+//         db.get_contacts(user_2).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_1,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+//     assert_eq!(
+//         db.get_contacts(user_3).await.unwrap(),
+//         &[Contact::Accepted {
+//             user_id: user_1,
+//             should_notify: false,
+//             busy: false,
+//         }],
+//     );
+// });
+
+// test_both_dbs!(test_metrics_id_postgres, test_metrics_id_sqlite, db, {
+//     let NewUserResult {
+//         user_id: user1,
+//         metrics_id: metrics_id1,
+//         ..
+//     } = db
+//         .create_user(
+//             "person1@example.com",
+//             false,
+//             NewUserParams {
+//                 github_login: "person1".into(),
+//                 github_user_id: 101,
+//                 invite_count: 5,
+//             },
+//         )
+//         .await
+//         .unwrap();
+//     let NewUserResult {
+//         user_id: user2,
+//         metrics_id: metrics_id2,
+//         ..
+//     } = db
+//         .create_user(
+//             "person2@example.com",
+//             false,
+//             NewUserParams {
+//                 github_login: "person2".into(),
+//                 github_user_id: 102,
+//                 invite_count: 5,
+//             },
+//         )
+//         .await
+//         .unwrap();
+
+//     assert_eq!(db.get_user_metrics_id(user1).await.unwrap(), metrics_id1);
+//     assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id2);
+//     assert_eq!(metrics_id1.len(), 36);
+//     assert_eq!(metrics_id2.len(), 36);
+//     assert_ne!(metrics_id1, metrics_id2);
+// });
+
+// #[test]
+// fn test_fuzzy_like_string() {
+//     assert_eq!(DefaultDb::fuzzy_like_string("abcd"), "%a%b%c%d%");
+//     assert_eq!(DefaultDb::fuzzy_like_string("x y"), "%x%y%");
+//     assert_eq!(DefaultDb::fuzzy_like_string(" z  "), "%z%");
+// }
+
+// #[gpui::test]
+// async fn test_fuzzy_search_users() {
+//     let test_db = PostgresTestDb::new(build_background_executor());
+//     let db = test_db.db();
+//     for (i, github_login) in [
+//         "California",
+//         "colorado",
+//         "oregon",
+//         "washington",
+//         "florida",
+//         "delaware",
+//         "rhode-island",
+//     ]
+//     .into_iter()
+//     .enumerate()
+//     {
+//         db.create_user(
+//             &format!("{github_login}@example.com"),
+//             false,
+//             NewUserParams {
+//                 github_login: github_login.into(),
+//                 github_user_id: i as i32,
+//                 invite_count: 0,
+//             },
+//         )
+//         .await
+//         .unwrap();
+//     }
+
+//     assert_eq!(
+//         fuzzy_search_user_names(db, "clr").await,
+//         &["colorado", "California"]
+//     );
+//     assert_eq!(
+//         fuzzy_search_user_names(db, "ro").await,
+//         &["rhode-island", "colorado", "oregon"],
+//     );
+
+//     async fn fuzzy_search_user_names(db: &Db<sqlx::Postgres>, query: &str) -> Vec<String> {
+//         db.fuzzy_search_users(query, 10)
+//             .await
+//             .unwrap()
+//             .into_iter()
+//             .map(|user| user.github_login)
+//             .collect::<Vec<_>>()
+//     }
+// }
+
+// #[gpui::test]
+// async fn test_invite_codes() {
+//     let test_db = PostgresTestDb::new(build_background_executor());
+//     let db = test_db.db();
+
+//     let NewUserResult { user_id: user1, .. } = db
+//         .create_user(
+//             "user1@example.com",
+//             false,
+//             NewUserParams {
+//                 github_login: "user1".into(),
+//                 github_user_id: 0,
+//                 invite_count: 0,
+//             },
+//         )
+//         .await
+//         .unwrap();
+
+//     // Initially, user 1 has no invite code
+//     assert_eq!(db.get_invite_code_for_user(user1).await.unwrap(), None);
+
+//     // Setting invite count to 0 when no code is assigned does not assign a new code
+//     db.set_invite_count_for_user(user1, 0).await.unwrap();
+//     assert!(db.get_invite_code_for_user(user1).await.unwrap().is_none());
+
+//     // User 1 creates an invite code that can be used twice.
+//     db.set_invite_count_for_user(user1, 2).await.unwrap();
+//     let (invite_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(invite_count, 2);
+
+//     // User 2 redeems the invite code and becomes a contact of user 1.
+//     let user2_invite = db
+//         .create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
+//         .await
+//         .unwrap();
+//     let NewUserResult {
+//         user_id: user2,
+//         inviting_user_id,
+//         signup_device_id,
+//         metrics_id,
+//     } = db
+//         .create_user_from_invite(
+//             &user2_invite,
+//             NewUserParams {
+//                 github_login: "user2".into(),
+//                 github_user_id: 2,
+//                 invite_count: 7,
+//             },
+//         )
+//         .await
+//         .unwrap()
+//         .unwrap();
+//     let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(invite_count, 1);
+//     assert_eq!(inviting_user_id, Some(user1));
+//     assert_eq!(signup_device_id.unwrap(), "user-2-device-id");
+//     assert_eq!(db.get_user_metrics_id(user2).await.unwrap(), metrics_id);
+//     assert_eq!(
+//         db.get_contacts(user1).await.unwrap(),
+//         [Contact::Accepted {
+//             user_id: user2,
+//             should_notify: true,
+//             busy: false,
+//         }]
+//     );
+//     assert_eq!(
+//         db.get_contacts(user2).await.unwrap(),
+//         [Contact::Accepted {
+//             user_id: user1,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+//     assert_eq!(
+//         db.get_invite_code_for_user(user2).await.unwrap().unwrap().1,
+//         7
+//     );
+
+//     // User 3 redeems the invite code and becomes a contact of user 1.
+//     let user3_invite = db
+//         .create_invite_from_code(&invite_code, "user3@example.com", None)
+//         .await
+//         .unwrap();
+//     let NewUserResult {
+//         user_id: user3,
+//         inviting_user_id,
+//         signup_device_id,
+//         ..
+//     } = db
+//         .create_user_from_invite(
+//             &user3_invite,
+//             NewUserParams {
+//                 github_login: "user-3".into(),
+//                 github_user_id: 3,
+//                 invite_count: 3,
+//             },
+//         )
+//         .await
+//         .unwrap()
+//         .unwrap();
+//     let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(invite_count, 0);
+//     assert_eq!(inviting_user_id, Some(user1));
+//     assert!(signup_device_id.is_none());
+//     assert_eq!(
+//         db.get_contacts(user1).await.unwrap(),
+//         [
+//             Contact::Accepted {
+//                 user_id: user2,
+//                 should_notify: true,
+//                 busy: false,
+//             },
+//             Contact::Accepted {
+//                 user_id: user3,
+//                 should_notify: true,
+//                 busy: false,
+//             }
+//         ]
+//     );
+//     assert_eq!(
+//         db.get_contacts(user3).await.unwrap(),
+//         [Contact::Accepted {
+//             user_id: user1,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+//     assert_eq!(
+//         db.get_invite_code_for_user(user3).await.unwrap().unwrap().1,
+//         3
+//     );
+
+//     // Trying to reedem the code for the third time results in an error.
+//     db.create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
+//         .await
+//         .unwrap_err();
+
+//     // Invite count can be updated after the code has been created.
+//     db.set_invite_count_for_user(user1, 2).await.unwrap();
+//     let (latest_code, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(latest_code, invite_code); // Invite code doesn't change when we increment above 0
+//     assert_eq!(invite_count, 2);
+
+//     // User 4 can now redeem the invite code and becomes a contact of user 1.
+//     let user4_invite = db
+//         .create_invite_from_code(&invite_code, "user4@example.com", Some("user-4-device-id"))
+//         .await
+//         .unwrap();
+//     let user4 = db
+//         .create_user_from_invite(
+//             &user4_invite,
+//             NewUserParams {
+//                 github_login: "user-4".into(),
+//                 github_user_id: 4,
+//                 invite_count: 5,
+//             },
+//         )
+//         .await
+//         .unwrap()
+//         .unwrap()
+//         .user_id;
+
+//     let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(invite_count, 1);
+//     assert_eq!(
+//         db.get_contacts(user1).await.unwrap(),
+//         [
+//             Contact::Accepted {
+//                 user_id: user2,
+//                 should_notify: true,
+//                 busy: false,
+//             },
+//             Contact::Accepted {
+//                 user_id: user3,
+//                 should_notify: true,
+//                 busy: false,
+//             },
+//             Contact::Accepted {
+//                 user_id: user4,
+//                 should_notify: true,
+//                 busy: false,
+//             }
+//         ]
+//     );
+//     assert_eq!(
+//         db.get_contacts(user4).await.unwrap(),
+//         [Contact::Accepted {
+//             user_id: user1,
+//             should_notify: false,
+//             busy: false,
+//         }]
+//     );
+//     assert_eq!(
+//         db.get_invite_code_for_user(user4).await.unwrap().unwrap().1,
+//         5
+//     );
+
+//     // An existing user cannot redeem invite codes.
+//     db.create_invite_from_code(&invite_code, "user2@example.com", Some("user-2-device-id"))
+//         .await
+//         .unwrap_err();
+//     let (_, invite_count) = db.get_invite_code_for_user(user1).await.unwrap().unwrap();
+//     assert_eq!(invite_count, 1);
+// }
+
+// #[gpui::test]
+// async fn test_signups() {
+//     let test_db = PostgresTestDb::new(build_background_executor());
+//     let db = test_db.db();
+
+//     // people sign up on the waitlist
+//     for i in 0..8 {
+//         db.create_signup(Signup {
+//             email_address: format!("person-{i}@example.com"),
+//             platform_mac: true,
+//             platform_linux: i % 2 == 0,
+//             platform_windows: i % 4 == 0,
+//             editor_features: vec!["speed".into()],
+//             programming_languages: vec!["rust".into(), "c".into()],
+//             device_id: Some(format!("device_id_{i}")),
+//         })
+//         .await
+//         .unwrap();
+//     }
+
+//     assert_eq!(
+//         db.get_waitlist_summary().await.unwrap(),
+//         WaitlistSummary {
+//             count: 8,
+//             mac_count: 8,
+//             linux_count: 4,
+//             windows_count: 2,
+//             unknown_count: 0,
+//         }
+//     );
+
+//     // retrieve the next batch of signup emails to send
+//     let signups_batch1 = db.get_unsent_invites(3).await.unwrap();
+//     let addresses = signups_batch1
+//         .iter()
+//         .map(|s| &s.email_address)
+//         .collect::<Vec<_>>();
+//     assert_eq!(
+//         addresses,
+//         &[
+//             "person-0@example.com",
+//             "person-1@example.com",
+//             "person-2@example.com"
+//         ]
+//     );
+//     assert_ne!(
+//         signups_batch1[0].email_confirmation_code,
+//         signups_batch1[1].email_confirmation_code
+//     );
+
+//     // the waitlist isn't updated until we record that the emails
+//     // were successfully sent.
+//     let signups_batch = db.get_unsent_invites(3).await.unwrap();
+//     assert_eq!(signups_batch, signups_batch1);
+
+//     // once the emails go out, we can retrieve the next batch
+//     // of signups.
+//     db.record_sent_invites(&signups_batch1).await.unwrap();
+//     let signups_batch2 = db.get_unsent_invites(3).await.unwrap();
+//     let addresses = signups_batch2
+//         .iter()
+//         .map(|s| &s.email_address)
+//         .collect::<Vec<_>>();
+//     assert_eq!(
+//         addresses,
+//         &[
+//             "person-3@example.com",
+//             "person-4@example.com",
+//             "person-5@example.com"
+//         ]
+//     );
+
+//     // the sent invites are excluded from the summary.
+//     assert_eq!(
+//         db.get_waitlist_summary().await.unwrap(),
+//         WaitlistSummary {
+//             count: 5,
+//             mac_count: 5,
+//             linux_count: 2,
+//             windows_count: 1,
+//             unknown_count: 0,
+//         }
+//     );
+
+//     // user completes the signup process by providing their
+//     // github account.
+//     let NewUserResult {
+//         user_id,
+//         inviting_user_id,
+//         signup_device_id,
+//         ..
+//     } = db
+//         .create_user_from_invite(
+//             &Invite {
+//                 email_address: signups_batch1[0].email_address.clone(),
+//                 email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
+//             },
+//             NewUserParams {
+//                 github_login: "person-0".into(),
+//                 github_user_id: 0,
+//                 invite_count: 5,
+//             },
+//         )
+//         .await
+//         .unwrap()
+//         .unwrap();
+//     let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
+//     assert!(inviting_user_id.is_none());
+//     assert_eq!(user.github_login, "person-0");
+//     assert_eq!(user.email_address.as_deref(), Some("person-0@example.com"));
+//     assert_eq!(user.invite_count, 5);
+//     assert_eq!(signup_device_id.unwrap(), "device_id_0");
+
+//     // cannot redeem the same signup again.
+//     assert!(db
+//         .create_user_from_invite(
+//             &Invite {
+//                 email_address: signups_batch1[0].email_address.clone(),
+//                 email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(),
+//             },
+//             NewUserParams {
+//                 github_login: "some-other-github_account".into(),
+//                 github_user_id: 1,
+//                 invite_count: 5,
+//             },
+//         )
+//         .await
+//         .unwrap()
+//         .is_none());
+
+//     // cannot redeem a signup with the wrong confirmation code.
+//     db.create_user_from_invite(
+//         &Invite {
+//             email_address: signups_batch1[1].email_address.clone(),
+//             email_confirmation_code: "the-wrong-code".to_string(),
+//         },
+//         NewUserParams {
+//             github_login: "person-1".into(),
+//             github_user_id: 2,
+//             invite_count: 5,
+//         },
+//     )
+//     .await
+//     .unwrap_err();
+// }
 
 fn build_background_executor() -> Arc<Background> {
     Deterministic::new(0).build_background()

crates/collab/src/db2/user.rs 🔗

@@ -1,7 +1,7 @@
 use super::UserId;
 use sea_orm::entity::prelude::*;
 
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "users")]
 pub struct Model {
     #[sea_orm(primary_key)]
@@ -13,6 +13,7 @@ pub struct Model {
     pub invite_code: Option<String>,
     pub invite_count: i32,
     pub connected_once: bool,
+    pub metrics_id: Uuid,
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

crates/collab/src/db2/worktree.rs 🔗

@@ -1,12 +1,14 @@
 use sea_orm::entity::prelude::*;
 
+use super::ProjectId;
+
 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
 #[sea_orm(table_name = "worktrees")]
 pub struct Model {
     #[sea_orm(primary_key)]
     pub id: i32,
     #[sea_orm(primary_key)]
-    pub project_id: i32,
+    pub project_id: ProjectId,
     pub abs_path: String,
     pub root_name: String,
     pub visible: bool,