collab: Extract tests into integration tests crate (#47668)
Reduces time needed to build collab tests from 48s to 38s. Release Notes: - N/A
This commit is contained in:
1 parent
5a234bec6e
commit
8c8a0d9c4d
35 files changed
+552
-2998
No files matched your search
Generated
+1
@@ -3293,6 +3293,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
"collab",
|
||||
"collab_ui",
|
||||
"collections",
|
||||
"command_palette_hooks",
|
||||
|
||||
@@ -278,6 +278,7 @@ clock = { path = "crates/clock" }
|
||||
cloud_api_client = { path = "crates/cloud_api_client" }
|
||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections", version = "0.1.0" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
|
||||
@@ -17,6 +17,14 @@ name = "collab"
|
||||
sqlite = ["sea-orm/sqlx-sqlite", "sqlx/sqlite"]
|
||||
test-support = ["sqlite"]
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
|
||||
[[test]]
|
||||
name = "collab_tests"
|
||||
required-features = ["test-support"]
|
||||
path = "tests/integration/collab_tests.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
@@ -81,6 +89,7 @@ buffer_diff.workspace = true
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
channel.workspace = true
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
collab = { workspace = true, features = ["test-support"] }
|
||||
collab_ui = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
command_palette_hooks.workspace = true
|
||||
|
||||
+5
-178
@@ -108,13 +108,13 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
|
||||
))
|
||||
}
|
||||
|
||||
const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
|
||||
pub const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct AccessTokenJson {
|
||||
version: usize,
|
||||
id: AccessTokenId,
|
||||
token: String,
|
||||
pub struct AccessTokenJson {
|
||||
pub version: usize,
|
||||
pub id: AccessTokenId,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
/// Creates a new access token to identify the given user. before returning it, you should
|
||||
@@ -224,176 +224,3 @@ pub async fn verify_access_token(
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::prelude::*;
|
||||
use scrypt::password_hash::{PasswordHasher, SaltString};
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use super::*;
|
||||
use crate::db::{NewUserParams, access_token};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_verify_access_token(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = crate::db::TestDb::sqlite(cx.executor());
|
||||
let db = test_db.db();
|
||||
|
||||
let user = db
|
||||
.create_user(
|
||||
"example@example.com",
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "example".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token = create_access_token(db, user.user_id, None).await.unwrap();
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
let old_token = create_previous_access_token(user.user_id, None, db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let old_token_id = serde_json::from_str::<AccessTokenJson>(&old_token)
|
||||
.unwrap()
|
||||
.id;
|
||||
|
||||
let hash = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(access_token::Entity::find_by_id(old_token_id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.hash;
|
||||
assert!(hash.starts_with("$scrypt$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
let hash = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(access_token::Entity::find_by_id(old_token_id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.hash;
|
||||
assert!(hash.starts_with("$sha256$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
async fn create_previous_access_token(
|
||||
user_id: UserId,
|
||||
impersonated_user_id: Option<UserId>,
|
||||
db: &Database,
|
||||
) -> Result<String> {
|
||||
let access_token = rpc::auth::random_token();
|
||||
let access_token_hash = previous_hash_access_token(&access_token)?;
|
||||
let id = db
|
||||
.create_access_token(
|
||||
user_id,
|
||||
impersonated_user_id,
|
||||
&access_token_hash,
|
||||
MAX_ACCESS_TOKENS_TO_STORE,
|
||||
)
|
||||
.await?;
|
||||
Ok(serde_json::to_string(&AccessTokenJson {
|
||||
version: 1,
|
||||
id,
|
||||
token: access_token,
|
||||
})?)
|
||||
}
|
||||
|
||||
fn previous_hash_access_token(token: &str) -> Result<String> {
|
||||
// Avoid slow hashing in debug mode.
|
||||
let params = if cfg!(debug_assertions) {
|
||||
scrypt::Params::new(1, 1, 1, scrypt::Params::RECOMMENDED_LEN).unwrap()
|
||||
} else {
|
||||
scrypt::Params::new(14, 8, 1, scrypt::Params::RECOMMENDED_LEN).unwrap()
|
||||
};
|
||||
|
||||
Ok(Scrypt
|
||||
.hash_password_customized(
|
||||
token.as_bytes(),
|
||||
None,
|
||||
None,
|
||||
params,
|
||||
&SaltString::generate(PasswordHashRngCompat::new()),
|
||||
)
|
||||
.map_err(anyhow::Error::new)?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
// TODO: remove once we password_hash v0.6 is released.
|
||||
struct PasswordHashRngCompat(rand::rngs::ThreadRng);
|
||||
|
||||
impl PasswordHashRngCompat {
|
||||
fn new() -> Self {
|
||||
Self(rand::rng())
|
||||
}
|
||||
}
|
||||
|
||||
impl scrypt::password_hash::rand_core::RngCore for PasswordHashRngCompat {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.0.fill_bytes(dest);
|
||||
}
|
||||
|
||||
fn try_fill_bytes(
|
||||
&mut self,
|
||||
dest: &mut [u8],
|
||||
) -> Result<(), scrypt::password_hash::rand_core::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl scrypt::password_hash::rand_core::CryptoRng for PasswordHashRngCompat {}
|
||||
}
|
||||
+10
-15
@@ -1,8 +1,6 @@
|
||||
mod ids;
|
||||
mod queries;
|
||||
pub mod queries;
|
||||
mod tables;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use crate::{Error, Result};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
@@ -37,15 +35,12 @@ use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
use util::paths::PathStyle;
|
||||
use worktree_settings_file::LocalSettingsKind;
|
||||
|
||||
#[cfg(test)]
|
||||
pub use tests::TestDb;
|
||||
|
||||
pub use ids::*;
|
||||
pub use sea_orm::ConnectOptions;
|
||||
pub use tables::user::Model as User;
|
||||
pub use tables::*;
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub struct DatabaseTestOptions {
|
||||
pub executor: gpui::BackgroundExecutor,
|
||||
pub runtime: tokio::runtime::Runtime,
|
||||
@@ -55,14 +50,14 @@ pub struct DatabaseTestOptions {
|
||||
/// Database gives you a handle that lets you access the database.
|
||||
/// It handles pooling internally.
|
||||
pub struct Database {
|
||||
options: ConnectOptions,
|
||||
pool: DatabaseConnection,
|
||||
pub options: ConnectOptions,
|
||||
pub pool: DatabaseConnection,
|
||||
rooms: DashMap<RoomId, Arc<Mutex<()>>>,
|
||||
projects: DashMap<ProjectId, Arc<Mutex<()>>>,
|
||||
notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
|
||||
notification_kinds_by_name: HashMap<String, NotificationKindId>,
|
||||
#[cfg(test)]
|
||||
test_options: Option<DatabaseTestOptions>,
|
||||
#[cfg(feature = "test-support")]
|
||||
pub test_options: Option<DatabaseTestOptions>,
|
||||
}
|
||||
|
||||
// The `Database` type has so many methods that its impl blocks are split into
|
||||
@@ -78,7 +73,7 @@ impl Database {
|
||||
projects: DashMap::with_capacity(16384),
|
||||
notification_kinds_by_id: HashMap::default(),
|
||||
notification_kinds_by_name: HashMap::default(),
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
test_options: None,
|
||||
})
|
||||
}
|
||||
@@ -87,7 +82,7 @@ impl Database {
|
||||
&self.options
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn reset(&self) {
|
||||
self.rooms.clear();
|
||||
self.projects.clear();
|
||||
@@ -248,7 +243,7 @@ impl Database {
|
||||
where
|
||||
F: Future<Output = Result<T>>,
|
||||
{
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
{
|
||||
let test_options = self.test_options.as_ref().unwrap();
|
||||
test_options.executor.simulate_random_delay().await;
|
||||
@@ -260,7 +255,7 @@ impl Database {
|
||||
test_options.runtime.block_on(future)
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[cfg(not(feature = "test-support"))]
|
||||
{
|
||||
future.await
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use rpc::{
|
||||
use sea_orm::{ActiveValue, DbBackend, TryGetableMany};
|
||||
|
||||
impl Database {
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn all_channels(&self) -> Result<Vec<(ChannelId, String)>> {
|
||||
self.transaction(move |tx| async move {
|
||||
let mut channels = Vec::new();
|
||||
@@ -21,12 +21,12 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn create_root_channel(&self, name: &str, creator_id: UserId) -> Result<ChannelId> {
|
||||
Ok(self.create_channel(name, None, creator_id).await?.0.id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn create_sub_channel(
|
||||
&self,
|
||||
name: &str,
|
||||
@@ -226,7 +226,7 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn set_channel_requires_zed_cla(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
@@ -885,7 +885,7 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_channel_internal(
|
||||
pub async fn get_channel_internal(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
tx: &DatabaseTransaction,
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
|
||||
impl Database {
|
||||
/// Records that a given user has signed the CLA.
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub async fn add_contributor(
|
||||
&self,
|
||||
github_login: &str,
|
||||
|
||||
@@ -1,218 +1 @@
|
||||
mod buffer_tests;
|
||||
mod channel_tests;
|
||||
mod db_tests;
|
||||
mod extension_tests;
|
||||
mod migrations;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI32, Ordering::SeqCst};
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::BackgroundExecutor;
|
||||
use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
use sea_orm::ConnectionTrait;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
|
||||
use self::migrations::run_database_migrations;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct TestDb {
|
||||
pub db: Option<Arc<Database>>,
|
||||
pub connection: Option<sqlx::AnyConnection>,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
pub fn sqlite(executor: BackgroundExecutor) -> Self {
|
||||
let url = "sqlite::memory:";
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut db = runtime.block_on(async {
|
||||
let mut options = ConnectOptions::new(url);
|
||||
options.max_connections(5);
|
||||
let mut db = Database::new(options).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,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
db.initialize_notification_kinds().await.unwrap();
|
||||
db
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
|
||||
Self {
|
||||
db: Some(Arc::new(db)),
|
||||
connection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn postgres(executor: BackgroundExecutor) -> Self {
|
||||
static LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
let _guard = LOCK.lock();
|
||||
let mut rng = StdRng::from_os_rng();
|
||||
let url = format!(
|
||||
"postgres://postgres@localhost/zed-test-{}",
|
||||
rng.random::<u128>()
|
||||
);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut db = runtime.block_on(async {
|
||||
sqlx::Postgres::create_database(&url)
|
||||
.await
|
||||
.expect("failed to create test db");
|
||||
let mut options = ConnectOptions::new(url);
|
||||
options
|
||||
.max_connections(5)
|
||||
.idle_timeout(Duration::from_secs(0));
|
||||
let mut db = Database::new(options).await.unwrap();
|
||||
let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
|
||||
run_database_migrations(db.options(), migrations_path)
|
||||
.await
|
||||
.unwrap();
|
||||
db.initialize_notification_kinds().await.unwrap();
|
||||
db
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
|
||||
Self {
|
||||
db: Some(Arc::new(db)),
|
||||
connection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db(&self) -> &Arc<Database> {
|
||||
self.db.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_query_failure_probability(&self, probability: f64) {
|
||||
let database = self.db.as_ref().unwrap();
|
||||
let test_options = database.test_options.as_ref().unwrap();
|
||||
*test_options.query_failure_probability.lock() = probability;
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_both_dbs {
|
||||
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = $crate::db::TestDb::postgres(cx.executor().clone());
|
||||
$test_name(test_db.db()).await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = $crate::db::TestDb::sqlite(cx.executor().clone());
|
||||
$test_name(test_db.db()).await;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Drop for TestDb {
|
||||
fn drop(&mut self) {
|
||||
let db = self.db.take().unwrap();
|
||||
if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
|
||||
db.test_options.as_ref().unwrap().runtime.block_on(async {
|
||||
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();
|
||||
";
|
||||
db.pool
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
db.pool.get_database_backend(),
|
||||
query,
|
||||
))
|
||||
.await
|
||||
.log_err();
|
||||
sqlx::Postgres::drop_database(db.options.get_url())
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_channel_tree_matches(actual: Vec<Channel>, expected: Vec<Channel>) {
|
||||
let expected_channels = expected.into_iter().collect::<HashSet<_>>();
|
||||
let actual_channels = actual.into_iter().collect::<HashSet<_>>();
|
||||
pretty_assertions::assert_eq!(expected_channels, actual_channels);
|
||||
}
|
||||
|
||||
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str)]) -> Vec<Channel> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut order_by_parent: HashMap<Vec<ChannelId>, i32> = HashMap::new();
|
||||
|
||||
for (id, parent_path, name) in channels {
|
||||
let parent_key = parent_path.to_vec();
|
||||
let order = if parent_key.is_empty() {
|
||||
1
|
||||
} else {
|
||||
*order_by_parent
|
||||
.entry(parent_key.clone())
|
||||
.and_modify(|e| *e += 1)
|
||||
.or_insert(1)
|
||||
};
|
||||
|
||||
result.push(Channel {
|
||||
id: *id,
|
||||
name: (*name).to_owned(),
|
||||
visibility: ChannelVisibility::Members,
|
||||
parent_path: parent_key,
|
||||
channel_order: order,
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
|
||||
|
||||
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
|
||||
db.create_user(
|
||||
email,
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: email[0..email.find('@').unwrap()].to_string(),
|
||||
github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::{future::Future, time::Duration};
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
use gpui::BackgroundExecutor;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Executor {
|
||||
Production,
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
Deterministic(BackgroundExecutor),
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ impl Executor {
|
||||
Executor::Production => {
|
||||
tokio::spawn(future);
|
||||
}
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
Executor::Deterministic(background) => {
|
||||
background.spawn(future).detach();
|
||||
}
|
||||
@@ -31,7 +31,7 @@ impl Executor {
|
||||
async move {
|
||||
match this {
|
||||
Executor::Production => tokio::time::sleep(duration).await,
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
Executor::Deterministic(background) => background.timer(duration).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ pub mod executor;
|
||||
pub mod rpc;
|
||||
pub mod seed;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use aws_config::{BehaviorVersion, Region};
|
||||
use axum::{
|
||||
@@ -169,7 +166,7 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
http_port: 0,
|
||||
|
||||
@@ -204,16 +204,16 @@ struct Session {
|
||||
|
||||
impl Session {
|
||||
async fn db(&self) -> tokio::sync::MutexGuard<'_, DbHandle> {
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
tokio::task::yield_now().await;
|
||||
let guard = self.db.lock().await;
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
tokio::task::yield_now().await;
|
||||
guard
|
||||
}
|
||||
|
||||
async fn connection_pool(&self) -> ConnectionPoolGuard<'_> {
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
tokio::task::yield_now().await;
|
||||
let guard = self.connection_pool.lock();
|
||||
ConnectionPoolGuard {
|
||||
@@ -267,13 +267,13 @@ impl Deref for DbHandle {
|
||||
pub struct Server {
|
||||
id: parking_lot::Mutex<ServerId>,
|
||||
peer: Arc<Peer>,
|
||||
pub(crate) connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
||||
pub connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
|
||||
app_state: Arc<AppState>,
|
||||
handlers: HashMap<TypeId, MessageHandler>,
|
||||
teardown: watch::Sender<bool>,
|
||||
}
|
||||
|
||||
pub(crate) struct ConnectionPoolGuard<'a> {
|
||||
struct ConnectionPoolGuard<'a> {
|
||||
guard: parking_lot::MutexGuard<'a, ConnectionPool>,
|
||||
_not_send: PhantomData<Rc<()>>,
|
||||
}
|
||||
@@ -651,7 +651,7 @@ impl Server {
|
||||
let _ = self.teardown.send(true);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn reset(&self, id: ServerId) {
|
||||
self.teardown();
|
||||
*self.id.lock() = id;
|
||||
@@ -659,7 +659,7 @@ impl Server {
|
||||
let _ = self.teardown.send(false);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn id(&self) -> ServerId {
|
||||
*self.id.lock()
|
||||
}
|
||||
@@ -1013,7 +1013,7 @@ impl DerefMut for ConnectionPoolGuard<'_> {
|
||||
|
||||
impl Drop for ConnectionPoolGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
self.check_invariants();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ impl ConnectionPool {
|
||||
.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-support")]
|
||||
pub fn check_invariants(&self) {
|
||||
for (connection_id, connection) in &self.connections {
|
||||
assert!(
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
use call::Room;
|
||||
use client::ChannelId;
|
||||
use gpui::{Entity, TestAppContext};
|
||||
|
||||
mod agent_sharing_tests;
|
||||
mod channel_buffer_tests;
|
||||
mod channel_guest_tests;
|
||||
mod channel_tests;
|
||||
mod editor_tests;
|
||||
mod following_tests;
|
||||
mod git_tests;
|
||||
mod integration_tests;
|
||||
mod notification_tests;
|
||||
mod random_channel_buffer_tests;
|
||||
mod random_project_collaboration_tests;
|
||||
mod randomized_test_helpers;
|
||||
mod remote_editing_collaboration_tests;
|
||||
mod test_server;
|
||||
|
||||
pub use randomized_test_helpers::{
|
||||
RandomizedTest, TestError, UserTestPlan, run_randomized_test, save_randomized_test_plan,
|
||||
};
|
||||
pub use test_server::{TestClient, TestServer};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct RoomParticipants {
|
||||
remote: Vec<String>,
|
||||
pending: Vec<String>,
|
||||
}
|
||||
|
||||
fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomParticipants {
|
||||
room.read_with(cx, |room, _| {
|
||||
let mut remote = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.map(|participant| participant.user.github_login.clone().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let mut pending = room
|
||||
.pending_participants()
|
||||
.iter()
|
||||
.map(|user| user.github_login.clone().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
remote.sort();
|
||||
pending.sort();
|
||||
RoomParticipants { remote, pending }
|
||||
})
|
||||
}
|
||||
|
||||
fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
||||
cx.read(|cx| room.read(cx).channel_id())
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -3,7 +3,7 @@ use gpui::{BackgroundExecutor, TestAppContext};
|
||||
use rpc::proto;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::tests::TestServer;
|
||||
use crate::TestServer;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_share_and_retrieve_thread(
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
use crate::{
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{TestServer, test_server::open_channel_notes},
|
||||
};
|
||||
use crate::{TestServer, test_server::open_channel_notes};
|
||||
use call::ActiveCall;
|
||||
use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
|
||||
use client::{Collaborator, ParticipantIndex, UserId};
|
||||
use collab::rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use editor::{Anchor, Editor, MultiBufferOffset, ToOffset};
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use crate::{db::ChannelId, tests::TestServer};
|
||||
use crate::TestServer;
|
||||
use call::ActiveCall;
|
||||
use chrono::Utc;
|
||||
use collab::db::ChannelId;
|
||||
use editor::Editor;
|
||||
use gpui::{BackgroundExecutor, TestAppContext};
|
||||
use rpc::proto;
|
||||
use util::rel_path::rel_path;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_channel_guests(
|
||||
executor: BackgroundExecutor,
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
db::{self, UserId},
|
||||
rpc::RECONNECT_TIMEOUT,
|
||||
tests::{RoomParticipants, TestServer, room_participants},
|
||||
};
|
||||
use crate::{RoomParticipants, TestServer, room_participants};
|
||||
use call::ActiveCall;
|
||||
use channel::{ChannelMembership, ChannelStore};
|
||||
use client::{ChannelId, User};
|
||||
use collab::{
|
||||
db::{self, UserId},
|
||||
rpc::RECONNECT_TIMEOUT,
|
||||
};
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{BackgroundExecutor, Entity, SharedString, TestAppContext};
|
||||
use rpc::{
|
||||
@@ -0,0 +1,230 @@
|
||||
use call::Room;
|
||||
use client::ChannelId;
|
||||
use gpui::{Entity, TestAppContext};
|
||||
|
||||
mod agent_sharing_tests;
|
||||
mod channel_buffer_tests;
|
||||
mod channel_guest_tests;
|
||||
mod channel_tests;
|
||||
mod db_tests;
|
||||
mod editor_tests;
|
||||
mod following_tests;
|
||||
mod git_tests;
|
||||
mod integration_tests;
|
||||
mod notification_tests;
|
||||
mod random_channel_buffer_tests;
|
||||
mod random_project_collaboration_tests;
|
||||
mod randomized_test_helpers;
|
||||
mod remote_editing_collaboration_tests;
|
||||
mod test_server;
|
||||
|
||||
pub use randomized_test_helpers::{
|
||||
RandomizedTest, TestError, UserTestPlan, run_randomized_test, save_randomized_test_plan,
|
||||
};
|
||||
pub use test_server::{TestClient, TestServer};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct RoomParticipants {
|
||||
remote: Vec<String>,
|
||||
pending: Vec<String>,
|
||||
}
|
||||
|
||||
fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomParticipants {
|
||||
room.read_with(cx, |room, _| {
|
||||
let mut remote = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.map(|participant| participant.user.github_login.clone().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let mut pending = room
|
||||
.pending_participants()
|
||||
.iter()
|
||||
.map(|user| user.github_login.clone().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
remote.sort();
|
||||
pending.sort();
|
||||
RoomParticipants { remote, pending }
|
||||
})
|
||||
}
|
||||
|
||||
fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
||||
cx.read(|cx| room.read(cx).channel_id())
|
||||
}
|
||||
|
||||
mod auth_token_tests {
|
||||
use collab::auth::{
|
||||
AccessTokenJson, MAX_ACCESS_TOKENS_TO_STORE, VerifyAccessTokenResult, create_access_token,
|
||||
verify_access_token,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use scrypt::Scrypt;
|
||||
use scrypt::password_hash::{PasswordHasher, SaltString};
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use collab::db::{Database, NewUserParams, UserId, access_token};
|
||||
use collab::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_verify_access_token(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = crate::db_tests::TestDb::sqlite(cx.executor());
|
||||
let db = test_db.db();
|
||||
|
||||
let user = db
|
||||
.create_user(
|
||||
"example@example.com",
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: "example".into(),
|
||||
github_user_id: 1,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let token = create_access_token(db, user.user_id, None).await.unwrap();
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
let old_token = create_previous_access_token(user.user_id, None, db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let old_token_id = serde_json::from_str::<AccessTokenJson>(&old_token)
|
||||
.unwrap()
|
||||
.id;
|
||||
|
||||
let hash = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(access_token::Entity::find_by_id(old_token_id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.hash;
|
||||
assert!(hash.starts_with("$scrypt$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
let hash = db
|
||||
.transaction(|tx| async move {
|
||||
Ok(access_token::Entity::find_by_id(old_token_id)
|
||||
.one(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.hash;
|
||||
assert!(hash.starts_with("$sha256$"));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&old_token, user.user_id, db)
|
||||
.await
|
||||
.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
verify_access_token(&token, user.user_id, db).await.unwrap(),
|
||||
VerifyAccessTokenResult {
|
||||
is_valid: true,
|
||||
impersonator_id: None,
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
async fn create_previous_access_token(
|
||||
user_id: UserId,
|
||||
impersonated_user_id: Option<UserId>,
|
||||
db: &Database,
|
||||
) -> Result<String> {
|
||||
let access_token = collab::auth::random_token();
|
||||
let access_token_hash = previous_hash_access_token(&access_token)?;
|
||||
let id = db
|
||||
.create_access_token(
|
||||
user_id,
|
||||
impersonated_user_id,
|
||||
&access_token_hash,
|
||||
MAX_ACCESS_TOKENS_TO_STORE,
|
||||
)
|
||||
.await?;
|
||||
Ok(serde_json::to_string(&AccessTokenJson {
|
||||
version: 1,
|
||||
id,
|
||||
token: access_token,
|
||||
})?)
|
||||
}
|
||||
|
||||
#[expect(clippy::result_large_err)]
|
||||
fn previous_hash_access_token(token: &str) -> Result<String> {
|
||||
// Avoid slow hashing in debug mode.
|
||||
let params = if cfg!(debug_assertions) {
|
||||
scrypt::Params::new(1, 1, 1, scrypt::Params::RECOMMENDED_LEN).unwrap()
|
||||
} else {
|
||||
scrypt::Params::new(14, 8, 1, scrypt::Params::RECOMMENDED_LEN).unwrap()
|
||||
};
|
||||
|
||||
Ok(Scrypt
|
||||
.hash_password_customized(
|
||||
token.as_bytes(),
|
||||
None,
|
||||
None,
|
||||
params,
|
||||
&SaltString::generate(PasswordHashRngCompat::new()),
|
||||
)
|
||||
.map_err(anyhow::Error::new)?
|
||||
.to_string())
|
||||
}
|
||||
|
||||
// TODO: remove once we password_hash v0.6 is released.
|
||||
struct PasswordHashRngCompat(rand::rngs::ThreadRng);
|
||||
|
||||
impl PasswordHashRngCompat {
|
||||
fn new() -> Self {
|
||||
Self(rand::rng())
|
||||
}
|
||||
}
|
||||
|
||||
impl scrypt::password_hash::rand_core::RngCore for PasswordHashRngCompat {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
self.0.fill_bytes(dest);
|
||||
}
|
||||
|
||||
fn try_fill_bytes(
|
||||
&mut self,
|
||||
dest: &mut [u8],
|
||||
) -> Result<(), scrypt::password_hash::rand_core::Error> {
|
||||
self.fill_bytes(dest);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl scrypt::password_hash::rand_core::CryptoRng for PasswordHashRngCompat {}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
mod buffer_tests;
|
||||
mod channel_tests;
|
||||
mod db_tests;
|
||||
mod extension_tests;
|
||||
mod migrations;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI32, Ordering::SeqCst};
|
||||
use std::time::Duration;
|
||||
|
||||
use collections::HashSet;
|
||||
use gpui::BackgroundExecutor;
|
||||
use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
use sea_orm::ConnectionTrait;
|
||||
use sqlx::migrate::MigrateDatabase;
|
||||
|
||||
use self::migrations::run_database_migrations;
|
||||
|
||||
use collab::db::*;
|
||||
|
||||
pub struct TestDb {
|
||||
pub db: Option<Arc<Database>>,
|
||||
pub connection: Option<sqlx::AnyConnection>,
|
||||
}
|
||||
|
||||
impl TestDb {
|
||||
pub fn sqlite(executor: BackgroundExecutor) -> Self {
|
||||
let url = "sqlite::memory:";
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut db = runtime.block_on(async {
|
||||
let mut options = ConnectOptions::new(url);
|
||||
options.max_connections(5);
|
||||
let mut db = Database::new(options).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,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
db.initialize_notification_kinds().await.unwrap();
|
||||
db
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
|
||||
Self {
|
||||
db: Some(Arc::new(db)),
|
||||
connection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn postgres(executor: BackgroundExecutor) -> Self {
|
||||
static LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
let _guard = LOCK.lock();
|
||||
let mut rng = StdRng::from_os_rng();
|
||||
let url = format!(
|
||||
"postgres://postgres@localhost/zed-test-{}",
|
||||
rng.random::<u128>()
|
||||
);
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut db = runtime.block_on(async {
|
||||
sqlx::Postgres::create_database(&url)
|
||||
.await
|
||||
.expect("failed to create test db");
|
||||
let mut options = ConnectOptions::new(url);
|
||||
options
|
||||
.max_connections(5)
|
||||
.idle_timeout(Duration::from_secs(0));
|
||||
let mut db = Database::new(options).await.unwrap();
|
||||
let migrations_path = concat!(env!("CARGO_MANIFEST_DIR"), "/migrations");
|
||||
run_database_migrations(db.options(), migrations_path)
|
||||
.await
|
||||
.unwrap();
|
||||
db.initialize_notification_kinds().await.unwrap();
|
||||
db
|
||||
});
|
||||
|
||||
db.test_options = Some(DatabaseTestOptions {
|
||||
executor,
|
||||
runtime,
|
||||
query_failure_probability: parking_lot::Mutex::new(0.0),
|
||||
});
|
||||
|
||||
Self {
|
||||
db: Some(Arc::new(db)),
|
||||
connection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn db(&self) -> &Arc<Database> {
|
||||
self.db.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_query_failure_probability(&self, probability: f64) {
|
||||
let database = self.db.as_ref().unwrap();
|
||||
let test_options = database.test_options.as_ref().unwrap();
|
||||
*test_options.query_failure_probability.lock() = probability;
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_both_dbs {
|
||||
($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
async fn $postgres_test_name(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = $crate::db_tests::TestDb::postgres(cx.executor().clone());
|
||||
$test_name(test_db.db()).await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = $crate::db_tests::TestDb::sqlite(cx.executor().clone());
|
||||
$test_name(test_db.db()).await;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Drop for TestDb {
|
||||
fn drop(&mut self) {
|
||||
let db = self.db.take().unwrap();
|
||||
if let sea_orm::DatabaseBackend::Postgres = db.pool.get_database_backend() {
|
||||
db.test_options.as_ref().unwrap().runtime.block_on(async {
|
||||
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();
|
||||
";
|
||||
db.pool
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
db.pool.get_database_backend(),
|
||||
query,
|
||||
))
|
||||
.await
|
||||
.log_err();
|
||||
sqlx::Postgres::drop_database(db.options.get_url())
|
||||
.await
|
||||
.log_err();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_channel_tree_matches(actual: Vec<Channel>, expected: Vec<Channel>) {
|
||||
let expected_channels = expected.into_iter().collect::<HashSet<_>>();
|
||||
let actual_channels = actual.into_iter().collect::<HashSet<_>>();
|
||||
pretty_assertions::assert_eq!(expected_channels, actual_channels);
|
||||
}
|
||||
|
||||
fn channel_tree(channels: &[(ChannelId, &[ChannelId], &'static str)]) -> Vec<Channel> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut order_by_parent: HashMap<Vec<ChannelId>, i32> = HashMap::new();
|
||||
|
||||
for (id, parent_path, name) in channels {
|
||||
let parent_key = parent_path.to_vec();
|
||||
let order = if parent_key.is_empty() {
|
||||
1
|
||||
} else {
|
||||
*order_by_parent
|
||||
.entry(parent_key.clone())
|
||||
.and_modify(|e| *e += 1)
|
||||
.or_insert(1)
|
||||
};
|
||||
|
||||
result.push(Channel {
|
||||
id: *id,
|
||||
name: (*name).to_owned(),
|
||||
visibility: ChannelVisibility::Members,
|
||||
parent_path: parent_key,
|
||||
channel_order: order,
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5);
|
||||
|
||||
async fn new_test_user(db: &Arc<Database>, email: &str) -> UserId {
|
||||
db.create_user(
|
||||
email,
|
||||
None,
|
||||
false,
|
||||
NewUserParams {
|
||||
github_login: email[0..email.find('@').unwrap()].to_string(),
|
||||
github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.user_id
|
||||
}
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
use language::proto::{self, serialize_version};
|
||||
use rpc::ConnectionId;
|
||||
use text::{Buffer, ReplicaId};
|
||||
|
||||
test_both_dbs!(
|
||||
+5
-9
@@ -1,10 +1,6 @@
|
||||
use crate::{
|
||||
db::{
|
||||
Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId, UserId,
|
||||
tests::{assert_channel_tree_matches, channel_tree, new_test_user},
|
||||
},
|
||||
test_both_dbs,
|
||||
};
|
||||
use super::{assert_channel_tree_matches, channel_tree, new_test_user};
|
||||
use crate::test_both_dbs;
|
||||
use collab::db::{Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId, UserId};
|
||||
use rpc::{
|
||||
ConnectionId,
|
||||
proto::{self, reorder_channel},
|
||||
@@ -689,12 +685,12 @@ async fn test_user_is_channel_participant(db: &Arc<Database>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.set_channel_visibility(zed_channel, crate::db::ChannelVisibility::Public, admin)
|
||||
db.set_channel_visibility(zed_channel, collab::db::ChannelVisibility::Public, admin)
|
||||
.await
|
||||
.unwrap();
|
||||
db.set_channel_visibility(
|
||||
public_channel_id,
|
||||
crate::db::ChannelVisibility::Public,
|
||||
collab::db::ChannelVisibility::Public,
|
||||
admin,
|
||||
)
|
||||
.await
|
||||
+10
-6
@@ -1,7 +1,11 @@
|
||||
use super::*;
|
||||
use crate::test_both_dbs;
|
||||
|
||||
use super::*;
|
||||
use chrono::Utc;
|
||||
use collab::db::RoomId;
|
||||
use collab::db::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rpc::ConnectionId;
|
||||
use std::sync::Arc;
|
||||
|
||||
test_both_dbs!(
|
||||
@@ -541,7 +545,7 @@ fn test_fuzzy_like_string() {
|
||||
#[cfg(target_os = "macos")]
|
||||
#[gpui::test]
|
||||
async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) {
|
||||
let test_db = tests::TestDb::postgres(cx.executor());
|
||||
let test_db = TestDb::postgres(cx.executor());
|
||||
let db = test_db.db();
|
||||
for (i, github_login) in [
|
||||
"California",
|
||||
@@ -594,7 +598,7 @@ test_both_dbs!(
|
||||
);
|
||||
|
||||
async fn test_upsert_shared_thread(db: &Arc<Database>) {
|
||||
use crate::db::SharedThreadId;
|
||||
use collab::db::SharedThreadId;
|
||||
use uuid::Uuid;
|
||||
|
||||
let user_id = new_test_user(db, "user1@example.com").await;
|
||||
@@ -624,7 +628,7 @@ test_both_dbs!(
|
||||
);
|
||||
|
||||
async fn test_upsert_shared_thread_updates_existing(db: &Arc<Database>) {
|
||||
use crate::db::SharedThreadId;
|
||||
use collab::db::SharedThreadId;
|
||||
use uuid::Uuid;
|
||||
|
||||
let user_id = new_test_user(db, "user1@example.com").await;
|
||||
@@ -665,7 +669,7 @@ test_both_dbs!(
|
||||
);
|
||||
|
||||
async fn test_cannot_update_another_users_shared_thread(db: &Arc<Database>) {
|
||||
use crate::db::SharedThreadId;
|
||||
use collab::db::SharedThreadId;
|
||||
use uuid::Uuid;
|
||||
|
||||
let user1_id = new_test_user(db, "user1@example.com").await;
|
||||
@@ -694,7 +698,7 @@ test_both_dbs!(
|
||||
);
|
||||
|
||||
async fn test_get_nonexistent_shared_thread(db: &Arc<Database>) {
|
||||
use crate::db::SharedThreadId;
|
||||
use collab::db::SharedThreadId;
|
||||
use uuid::Uuid;
|
||||
|
||||
let result = db
|
||||
+5
-7
@@ -3,13 +3,11 @@ use std::sync::Arc;
|
||||
|
||||
use rpc::ExtensionProvides;
|
||||
|
||||
use super::Database;
|
||||
use crate::db::ExtensionVersionConstraints;
|
||||
use crate::{
|
||||
db::{ExtensionMetadata, NewExtensionVersion, queries::extensions::convert_time_to_chrono},
|
||||
test_both_dbs,
|
||||
};
|
||||
|
||||
use crate::test_both_dbs;
|
||||
use collab::db::Database;
|
||||
use collab::db::ExtensionVersionConstraints;
|
||||
use collab::db::{NewExtensionVersion, queries::extensions::convert_time_to_chrono};
|
||||
use rpc::ExtensionMetadata;
|
||||
test_both_dbs!(
|
||||
test_extensions,
|
||||
test_extensions_postgres,
|
||||
File renamed without changes.
+2
-1
@@ -1,5 +1,6 @@
|
||||
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
use crate::TestServer;
|
||||
use call::ActiveCall;
|
||||
use collab::rpc::RECONNECT_TIMEOUT;
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
#![allow(clippy::reversed_empty_ranges)]
|
||||
use crate::tests::TestServer;
|
||||
use crate::TestServer;
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::ChannelId;
|
||||
use collab_ui::{
|
||||
@@ -462,7 +462,7 @@ async fn test_basic_following(
|
||||
|
||||
// #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
|
||||
{
|
||||
use crate::rpc::RECONNECT_TIMEOUT;
|
||||
use collab::rpc::RECONNECT_TIMEOUT;
|
||||
use gpui::TestScreenCaptureSource;
|
||||
use workspace::{
|
||||
dock::{DockPosition, test::TestPanel},
|
||||
@@ -10,7 +10,7 @@ use util::{path, rel_path::rel_path};
|
||||
use workspace::Workspace;
|
||||
|
||||
//
|
||||
use crate::tests::TestServer;
|
||||
use crate::TestServer;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
|
||||
+3
-5
@@ -1,9 +1,6 @@
|
||||
use crate::{
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{
|
||||
RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
|
||||
room_participants,
|
||||
},
|
||||
RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
|
||||
room_participants,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
@@ -11,6 +8,7 @@ use assistant_text_thread::TextThreadStore;
|
||||
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
|
||||
use call::{ActiveCall, ParticipantLocation, Room, room};
|
||||
use client::{RECEIVE_TIMEOUT, User};
|
||||
use collab::rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::{StreamExt as _, channel::mpsc};
|
||||
+1
-1
@@ -6,7 +6,7 @@ use parking_lot::Mutex;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rpc::{Notification, proto};
|
||||
|
||||
use crate::tests::TestServer;
|
||||
use crate::TestServer;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_notifications(
|
||||
+2
-5
@@ -1,4 +1,4 @@
|
||||
use crate::db::ChannelRole;
|
||||
use collab::db::*;
|
||||
|
||||
use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan, run_randomized_test};
|
||||
use anyhow::Result;
|
||||
@@ -13,10 +13,7 @@ use std::{
|
||||
};
|
||||
use text::Bias;
|
||||
|
||||
#[gpui::test(
|
||||
iterations = 100,
|
||||
on_failure = "crate::tests::save_randomized_test_plan"
|
||||
)]
|
||||
#[gpui::test(iterations = 100, on_failure = "crate::save_randomized_test_plan")]
|
||||
async fn test_random_channel_buffers(
|
||||
cx: &mut TestAppContext,
|
||||
executor: BackgroundExecutor,
|
||||
+3
-6
@@ -1,8 +1,8 @@
|
||||
use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan};
|
||||
use crate::{db::UserId, tests::run_randomized_test};
|
||||
use crate::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan, run_randomized_test};
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use call::ActiveCall;
|
||||
use collab::db::UserId;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use editor::Bias;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
@@ -33,10 +33,7 @@ use util::{
|
||||
rel_path::{RelPath, RelPathBuf, rel_path},
|
||||
};
|
||||
|
||||
#[gpui::test(
|
||||
iterations = 100,
|
||||
on_failure = "crate::tests::save_randomized_test_plan"
|
||||
)]
|
||||
#[gpui::test(iterations = 100, on_failure = "crate::save_randomized_test_plan")]
|
||||
async fn test_random_project_collaboration(
|
||||
cx: &mut TestAppContext,
|
||||
executor: BackgroundExecutor,
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
use crate::{TestClient, TestServer};
|
||||
use async_trait::async_trait;
|
||||
use collab::{
|
||||
db::{self, NewUserParams, UserId},
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{TestClient, TestServer},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{BackgroundExecutor, Task, TestAppContext};
|
||||
use parking_lot::Mutex;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::tests::TestServer;
|
||||
use crate::TestServer;
|
||||
use call::ActiveCall;
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
+8
-6
@@ -1,9 +1,3 @@
|
||||
use crate::{
|
||||
AppState, Config,
|
||||
db::{NewUserParams, UserId, tests::TestDb},
|
||||
executor::Executor,
|
||||
rpc::{CLEANUP_TIMEOUT, Principal, RECONNECT_TIMEOUT, Server, ZedVersion},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use call::ActiveCall;
|
||||
use channel::{ChannelBuffer, ChannelStore};
|
||||
@@ -13,6 +7,12 @@ use client::{
|
||||
proto::PeerId,
|
||||
};
|
||||
use clock::FakeSystemClock;
|
||||
use collab::{
|
||||
AppState, Config,
|
||||
db::{NewUserParams, UserId},
|
||||
executor::Executor,
|
||||
rpc::{CLEANUP_TIMEOUT, Principal, RECONNECT_TIMEOUT, Server, ZedVersion},
|
||||
};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
@@ -49,6 +49,8 @@ use workspace::{Workspace, WorkspaceStore};
|
||||
|
||||
use livekit_client::test::TestServer as LivekitTestServer;
|
||||
|
||||
use crate::db_tests::TestDb;
|
||||
|
||||
pub struct TestServer {
|
||||
pub app_state: Arc<AppState>,
|
||||
pub test_livekit_server: Arc<LivekitTestServer>,
|
||||
Reference in new issue
Block a user