diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 79a6610eef..ab0833b38e 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -12,8 +12,16 @@ use sqlx::{ use std::{cmp, ops::Range, path::Path, time::Duration}; use time::{OffsetDateTime, PrimitiveDateTime}; +#[cfg(test)] +pub type DefaultDb = Db; + +#[cfg(not(test))] +pub type DefaultDb = Db; + pub struct Db { pool: sqlx::Pool, + #[cfg(test)] + background: Option>, } macro_rules! test_support { @@ -23,6 +31,10 @@ macro_rules! test_support { }; if cfg!(test) { + #[cfg(test)] + if let Some(background) = $self.background.as_ref() { + background.simulate_random_delay().await; + } tokio::runtime::Builder::new_current_thread().enable_io().enable_time().build().unwrap().block_on(body) } else { body.await @@ -30,7 +42,7 @@ macro_rules! test_support { }}; } -trait RowsAffected { +pub trait RowsAffected { fn rows_affected(&self) -> u64; } @@ -48,32 +60,37 @@ impl RowsAffected for sqlx::postgres::PgQueryResult { impl Db { #[cfg(test)] - pub async fn sqlite(url: &str, max_connections: u32) -> Result { + pub async fn new(url: &str, max_connections: u32) -> Result { let pool = sqlx::sqlite::SqlitePoolOptions::new() .max_connections(max_connections) .connect(url) .await?; - Ok(Self { pool }) + Ok(Self { + pool, + background: None, + }) } } impl Db { - pub async fn postgres(url: &str, max_connections: u32) -> Result { + pub async fn new(url: &str, max_connections: u32) -> Result { let pool = sqlx::postgres::PgPoolOptions::new() .max_connections(max_connections) .connect(url) .await?; - Ok(Self { pool }) + Ok(Self { + pool, + #[cfg(test)] + background: None, + }) } } impl Db where D: sqlx::Database + sqlx::migrate::MigrateDatabase, - for<'a> >::Arguments: sqlx::IntoArguments<'a, D>, - D: for<'a> sqlx::database::HasValueRef<'a>, - D: for<'a> sqlx::database::HasArguments<'a>, D::Connection: sqlx::migrate::Migrate, + for<'a> >::Arguments: sqlx::IntoArguments<'a, D>, for<'a> &'a mut D::Connection: sqlx::Executor<'a, Database = D>, for<'a, 'b> &'b mut sqlx::Transaction<'a, D>: sqlx::Executor<'b, Database = D>, D::QueryResult: RowsAffected, @@ -452,19 +469,18 @@ where pub async fn record_sent_invites(&self, invites: &[Invite]) -> Result<()> { test_support!(self, { + let emails = invites + .iter() + .map(|s| s.email_address.as_str()) + .collect::>(); sqlx::query( " UPDATE signups SET email_confirmation_sent = TRUE - WHERE email_address = ANY ($1) + WHERE email_address IN (SELECT value from json_each($1)) ", ) - // .bind( - // &invites - // .iter() - // .map(|s| s.email_address.as_str()) - // .collect::>(), - // ) + .bind(&serde_json::json!(emails)) .execute(&self.pool) .await?; Ok(()) @@ -808,7 +824,7 @@ where count = excluded.count ", ); - query.build().execute(&self.pool).await?; + // query.build().execute(&self.pool).await?; Ok(()) }) @@ -1604,24 +1620,6 @@ where .await?) }) } - - #[cfg(test)] - pub async fn teardown(&self, url: &str) { - test_support!(self, { - use util::ResultExt; - - let query = " - SELECT pg_terminate_backend(pg_stat_activity.pid) - FROM pg_stat_activity - WHERE pg_stat_activity.datname = current_database() AND pid <> pg_backend_pid(); - "; - sqlx::query(query).execute(&self.pool).await.log_err(); - self.pool.close().await; - ::drop_database(url) - .await - .log_err(); - }) - } } macro_rules! id_type { @@ -1833,51 +1831,37 @@ mod test { use super::*; use gpui::executor::Background; use rand::prelude::*; - use sqlx::{migrate::MigrateDatabase, Sqlite}; + use sqlx::migrate::MigrateDatabase; use std::sync::Arc; pub struct TestDb { - pub db: Option>>, + pub db: Option>, pub url: String, } impl TestDb { - #[allow(clippy::await_holding_lock)] - pub async fn real() -> Self { - todo!() - // eprintln!("creating database..."); - // let start = std::time::Instant::now(); - // let mut rng = StdRng::from_entropy(); - // let url = format!("/tmp/zed-test-{}", rng.gen::()); - // Sqlite::create_database(&url).await.unwrap(); - // let db = Db::new(&url, 5).await.unwrap(); - // db.migrate(Path::new(DEFAULT_MIGRATIONS_PATH.unwrap()), false) - // .await - // .unwrap(); - - // eprintln!("created database: {:?}", start.elapsed()); - // Self { - // db: Some(Arc::new(db)), - // url, - // } - } - - pub async fn fake(background: Arc) -> Self { - let start = std::time::Instant::now(); + pub async fn new(background: Arc) -> Self { let mut rng = StdRng::from_entropy(); - let url = format!("file:db-{}?mode=memory&cache=shared", rng.gen::()); - let db = Db::sqlite(&url, 5).await.unwrap(); + let url = format!("/tmp/zed-test-{}", rng.gen::()); + sqlx::Sqlite::create_database(&url).await.unwrap(); + let mut db = DefaultDb::new(&url, 5).await.unwrap(); + db.background = Some(background); let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations.sqlite"); db.migrate(Path::new(migrations_path), false).await.unwrap(); - Self { db: Some(Arc::new(db)), url, } } - pub fn db(&self) -> &Arc> { + pub fn db(&self) -> &Arc { self.db.as_ref().unwrap() } } + + impl Drop for TestDb { + fn drop(&mut self) { + std::fs::remove_file(&self.url).ok(); + } + } } diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index 98aac44400..ecedc9973d 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -6,133 +6,125 @@ use time::OffsetDateTime; #[tokio::test(flavor = "multi_thread")] async fn test_get_users_by_ids() { - for test_db in [ - TestDb::real().await, - TestDb::fake(build_background_executor()), - ] { - let db = test_db.db(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); - let mut user_ids = Vec::new(); - for i in 1..=4 { - 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, - ); - } - - assert_eq!( - db.get_users_by_ids(user_ids.clone()).await.unwrap(), - vec![ - User { - id: user_ids[0], - github_login: "user1".to_string(), - github_user_id: Some(1), - email_address: Some("user1@example.com".to_string()), - admin: false, - ..Default::default() + let mut user_ids = Vec::new(); + for i in 1..=4 { + 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, }, - User { - id: user_ids[1], - github_login: "user2".to_string(), - github_user_id: Some(2), - email_address: Some("user2@example.com".to_string()), - admin: false, - ..Default::default() - }, - User { - id: user_ids[2], - github_login: "user3".to_string(), - github_user_id: Some(3), - email_address: Some("user3@example.com".to_string()), - admin: false, - ..Default::default() - }, - User { - id: user_ids[3], - github_login: "user4".to_string(), - github_user_id: Some(4), - email_address: Some("user4@example.com".to_string()), - admin: false, - ..Default::default() - } - ] + ) + .await + .unwrap() + .user_id, ); } + + assert_eq!( + db.get_users_by_ids(user_ids.clone()).await.unwrap(), + vec![ + User { + id: user_ids[0], + github_login: "user1".to_string(), + github_user_id: Some(1), + email_address: Some("user1@example.com".to_string()), + admin: false, + ..Default::default() + }, + User { + id: user_ids[1], + github_login: "user2".to_string(), + github_user_id: Some(2), + email_address: Some("user2@example.com".to_string()), + admin: false, + ..Default::default() + }, + User { + id: user_ids[2], + github_login: "user3".to_string(), + github_user_id: Some(3), + email_address: Some("user3@example.com".to_string()), + admin: false, + ..Default::default() + }, + User { + id: user_ids[3], + github_login: "user4".to_string(), + github_user_id: Some(4), + email_address: Some("user4@example.com".to_string()), + admin: false, + ..Default::default() + } + ] + ); } #[tokio::test(flavor = "multi_thread")] async fn test_get_user_by_github_account() { - for test_db in [ - TestDb::real().await, - TestDb::fake(build_background_executor()), - ] { - let db = test_db.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 test_db = TestDb::new(build_background_executor()).await; + let db = test_db.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)); + 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()); + 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)); - } + 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)); } #[tokio::test(flavor = "multi_thread")] async fn test_worktree_extensions() { - let test_db = TestDb::real().await; + let test_db = TestDb::new(build_background_executor()).await; let db = test_db.db(); let user = db @@ -204,7 +196,7 @@ async fn test_worktree_extensions() { #[tokio::test(flavor = "multi_thread")] async fn test_user_activity() { - let test_db = TestDb::real().await; + let test_db = TestDb::new(build_background_executor()).await; let db = test_db.db(); let mut user_ids = Vec::new(); @@ -447,98 +439,90 @@ async fn test_user_activity() { #[tokio::test(flavor = "multi_thread")] async fn test_recent_channel_messages() { - for test_db in [ - TestDb::real().await, - TestDb::fake(build_background_executor()), - ] { - let db = test_db.db(); - let user = db - .create_user( - "u@example.com", - false, - NewUserParams { - github_login: "u".into(), - github_user_id: 1, - invite_count: 0, - }, - ) - .await - .unwrap() - .user_id; - let org = db.create_org("org", "org").await.unwrap(); - let channel = db.create_org_channel(org, "channel").await.unwrap(); - for i in 0..10 { - db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i) - .await - .unwrap(); - } - - let messages = db.get_channel_messages(channel, 5, None).await.unwrap(); - assert_eq!( - messages.iter().map(|m| &m.body).collect::>(), - ["5", "6", "7", "8", "9"] - ); - - let prev_messages = db - .get_channel_messages(channel, 4, Some(messages[0].id)) + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); + let user = db + .create_user( + "u@example.com", + false, + NewUserParams { + github_login: "u".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + let org = db.create_org("org", "org").await.unwrap(); + let channel = db.create_org_channel(org, "channel").await.unwrap(); + for i in 0..10 { + db.create_channel_message(channel, user, &i.to_string(), OffsetDateTime::now_utc(), i) .await .unwrap(); - assert_eq!( - prev_messages.iter().map(|m| &m.body).collect::>(), - ["1", "2", "3", "4"] - ); } + + let messages = db.get_channel_messages(channel, 5, None).await.unwrap(); + assert_eq!( + messages.iter().map(|m| &m.body).collect::>(), + ["5", "6", "7", "8", "9"] + ); + + let prev_messages = db + .get_channel_messages(channel, 4, Some(messages[0].id)) + .await + .unwrap(); + assert_eq!( + prev_messages.iter().map(|m| &m.body).collect::>(), + ["1", "2", "3", "4"] + ); } #[tokio::test(flavor = "multi_thread")] async fn test_channel_message_nonces() { - for test_db in [ - TestDb::real().await, - TestDb::fake(build_background_executor()), - ] { - let db = test_db.db(); - let user = db - .create_user( - "user@example.com", - false, - NewUserParams { - github_login: "user".into(), - github_user_id: 1, - invite_count: 0, - }, - ) - .await - .unwrap() - .user_id; - let org = db.create_org("org", "org").await.unwrap(); - let channel = db.create_org_channel(org, "channel").await.unwrap(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); + let user = db + .create_user( + "user@example.com", + false, + NewUserParams { + github_login: "user".into(), + github_user_id: 1, + invite_count: 0, + }, + ) + .await + .unwrap() + .user_id; + let org = db.create_org("org", "org").await.unwrap(); + let channel = db.create_org_channel(org, "channel").await.unwrap(); - let msg1_id = db - .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1) - .await - .unwrap(); - let msg2_id = db - .create_channel_message(channel, user, "2", OffsetDateTime::now_utc(), 2) - .await - .unwrap(); - let msg3_id = db - .create_channel_message(channel, user, "3", OffsetDateTime::now_utc(), 1) - .await - .unwrap(); - let msg4_id = db - .create_channel_message(channel, user, "4", OffsetDateTime::now_utc(), 2) - .await - .unwrap(); + let msg1_id = db + .create_channel_message(channel, user, "1", OffsetDateTime::now_utc(), 1) + .await + .unwrap(); + let msg2_id = db + .create_channel_message(channel, user, "2", OffsetDateTime::now_utc(), 2) + .await + .unwrap(); + let msg3_id = db + .create_channel_message(channel, user, "3", OffsetDateTime::now_utc(), 1) + .await + .unwrap(); + let msg4_id = db + .create_channel_message(channel, user, "4", OffsetDateTime::now_utc(), 2) + .await + .unwrap(); - assert_ne!(msg1_id, msg2_id); - assert_eq!(msg1_id, msg3_id); - assert_eq!(msg2_id, msg4_id); - } + assert_ne!(msg1_id, msg2_id); + assert_eq!(msg1_id, msg3_id); + assert_eq!(msg2_id, msg4_id); } #[tokio::test(flavor = "multi_thread")] async fn test_create_access_tokens() { - let test_db = TestDb::real().await; + let test_db = TestDb::new(build_background_executor()).await; let db = test_db.db(); let user = db .create_user( @@ -582,14 +566,14 @@ async fn test_create_access_tokens() { #[test] fn test_fuzzy_like_string() { - assert_eq!(RealDb::fuzzy_like_string("abcd"), "%a%b%c%d%"); - assert_eq!(RealDb::fuzzy_like_string("x y"), "%x%y%"); - assert_eq!(RealDb::fuzzy_like_string(" z "), "%z%"); + 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%"); } #[tokio::test(flavor = "multi_thread")] async fn test_fuzzy_search_users() { - let test_db = TestDb::real().await; + let test_db = TestDb::new(build_background_executor()).await; let db = test_db.db(); for (i, github_login) in [ "California", @@ -625,7 +609,7 @@ async fn test_fuzzy_search_users() { &["rhode-island", "colorado", "oregon"], ); - async fn fuzzy_search_user_names(db: &Arc, query: &str) -> Vec { + async fn fuzzy_search_user_names(db: &DefaultDb, query: &str) -> Vec { db.fuzzy_search_users(query, 10) .await .unwrap() @@ -637,176 +621,172 @@ async fn test_fuzzy_search_users() { #[tokio::test(flavor = "multi_thread")] async fn test_add_contacts() { - for test_db in [ - TestDb::real().await, - TestDb::fake(build_background_executor()), - ] { - let db = test_db.db(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.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 - }], - ); - 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, - }] - ); - - // 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, - }] - ); - - // 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, - }] - ); - - // 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, + 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, }, - Contact::Accepted { - user_id: user_3, - should_notify: false - } - ] - ); - assert_eq!( - db.get_contacts(user_3).await.unwrap(), - &[Contact::Accepted { - user_id: user_1, - should_notify: 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 - }] - ); - assert_eq!( - db.get_contacts(user_3).await.unwrap(), - &[Contact::Accepted { - user_id: user_1, - should_notify: false - }], + .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 + }], + ); + 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, + }] + ); + + // 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, + }] + ); + + // 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, + }] + ); + + // 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, + }, + Contact::Accepted { + user_id: user_3, + should_notify: false + } + ] + ); + assert_eq!( + db.get_contacts(user_3).await.unwrap(), + &[Contact::Accepted { + user_id: user_1, + should_notify: 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 + }] + ); + assert_eq!( + db.get_contacts(user_3).await.unwrap(), + &[Contact::Accepted { + user_id: user_1, + should_notify: false + }], + ); } #[tokio::test(flavor = "multi_thread")] async fn test_invite_codes() { - let postgres = TestDb::real().await; - let db = postgres.db(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); let NewUserResult { user_id: user1, .. } = db .create_user( "user1@example.com", @@ -1000,8 +980,8 @@ async fn test_invite_codes() { #[tokio::test(flavor = "multi_thread")] async fn test_signups() { - let postgres = TestDb::real().await; - let db = postgres.db(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); // people sign up on the waitlist for i in 0..8 { @@ -1146,8 +1126,8 @@ async fn test_signups() { #[tokio::test(flavor = "multi_thread")] async fn test_metrics_id() { - let postgres = TestDb::real().await; - let db = postgres.db(); + let test_db = TestDb::new(build_background_executor()).await; + let db = test_db.db(); let NewUserResult { user_id: user1, diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index b5787c0277..ef51ff7152 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -6104,7 +6104,7 @@ impl TestServer { .enable_time() .build() .unwrap() - .block_on(TestDb::real()); + .block_on(TestDb::new(background.clone())); let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst); let live_kit_server = live_kit_client::TestServer::create( format!("http://livekit.{}.test", live_kit_server_id), diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index fd37990674..334e67ead9 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -18,7 +18,7 @@ use serde::Deserialize; use std::{ env::args, net::{SocketAddr, TcpListener}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, time::Duration, }; @@ -101,8 +101,7 @@ async fn main() -> Result<()> { let migrations_path = config .migrations_path .as_deref() - .or(db::DEFAULT_MIGRATIONS_PATH.map(|s| s.as_ref())) - .ok_or_else(|| anyhow!("missing MIGRATIONS_PATH environment variable"))?; + .unwrap_or_else(|| Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations"))); let migrations = db.migrate(&migrations_path, false).await?; for (migration, duration) in migrations {