feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,261 @@
|
||||
mod support;
|
||||
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
use dever_runtime::config::{self, RuntimeProfile};
|
||||
use dever_runtime::database::{self, Executor, ModelScope, Sql, StorageBinding};
|
||||
use dever_runtime::orm::{self, ErrorKind, Value};
|
||||
|
||||
const SCHEMA: &str = "tenant-schema-v1";
|
||||
|
||||
#[test]
|
||||
fn sqlite_tenant_databases_are_ready_bounded_and_transaction_isolated() {
|
||||
let executable = std::env::current_exe().unwrap();
|
||||
let directory = executable.parent().unwrap();
|
||||
if !directory.join("config/setting.json").is_file() {
|
||||
let project = support::temp::TemporaryDirectory::new();
|
||||
fs::create_dir(project.path().join("config")).unwrap();
|
||||
fs::write(
|
||||
project.path().join("config/setting.json"),
|
||||
r#"{
|
||||
"tenant": {
|
||||
"database": "default",
|
||||
"max_pools": 2,
|
||||
"idle_timeout_ms": 1000
|
||||
},
|
||||
"database": {
|
||||
"default": {
|
||||
"type": "sqlite",
|
||||
"path": "data/platform.db",
|
||||
"tenant_directory": "data/tenants",
|
||||
"max_connections": 2
|
||||
}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let child = project.path().join("tenant-database-case");
|
||||
fs::copy(executable, &child).unwrap();
|
||||
let output = Command::new(child)
|
||||
.args([
|
||||
"--exact",
|
||||
"sqlite_tenant_databases_are_ready_bounded_and_transaction_isolated",
|
||||
"--nocapture",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async {
|
||||
config::bootstrap(
|
||||
RuntimeProfile {
|
||||
sqlite: true,
|
||||
postgres: false,
|
||||
},
|
||||
&[],
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
database::prepare().await.unwrap();
|
||||
dever_runtime::tenant::initialize(SCHEMA).await.unwrap();
|
||||
|
||||
let tenant_binding = StorageBinding::new(None, "content", ModelScope::Tenant);
|
||||
let missing = match dever_runtime::tenant::scope(1, tenant_binding.database()).await {
|
||||
Ok(_) => panic!("an unprovisioned tenant must not resolve a database"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(missing.kind(), ErrorKind::Migration);
|
||||
assert!(!directory.join("data/tenants/tenant_1.db").exists());
|
||||
|
||||
for tenant_id in [1, 2, 3] {
|
||||
let database = dever_runtime::tenant::begin_migration("default", tenant_id, SCHEMA)
|
||||
.await
|
||||
.unwrap();
|
||||
database.execute(Sql {
|
||||
sqlite: "CREATE TABLE profile (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
|
||||
postgres: "CREATE TABLE profile (id BIGINT PRIMARY KEY, name TEXT NOT NULL)",
|
||||
}, Vec::new()).await.unwrap();
|
||||
dever_runtime::tenant::mark_database_ready(&database, tenant_id, SCHEMA)
|
||||
.await
|
||||
.unwrap();
|
||||
dever_runtime::tenant::finish_migration(tenant_id, SCHEMA)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let tenant_components = ["media", "news"];
|
||||
dever_runtime::tenant::require_components(1, &["news"], &tenant_components)
|
||||
.await
|
||||
.unwrap();
|
||||
dever_runtime::tenant::disable_component(1, "news", &tenant_components)
|
||||
.await
|
||||
.unwrap();
|
||||
let disabled = dever_runtime::tenant::require_components(
|
||||
1,
|
||||
&["media", "news", "news"],
|
||||
&tenant_components,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(disabled.kind(), ErrorKind::InvalidData);
|
||||
assert!(
|
||||
disabled
|
||||
.to_string()
|
||||
.contains("tenant component 'news' is disabled")
|
||||
);
|
||||
dever_runtime::tenant::require_components(1, &["media"], &tenant_components)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for component in ["platform", "unknown"] {
|
||||
let error = dever_runtime::tenant::disable_component(1, component, &tenant_components)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::InvalidData);
|
||||
assert!(error.to_string().contains("is not a tenant component"));
|
||||
}
|
||||
|
||||
let tenant_one = dever_runtime::tenant::scope(1, tenant_binding.database())
|
||||
.await
|
||||
.unwrap();
|
||||
tenant_one
|
||||
.execute(
|
||||
Sql {
|
||||
sqlite: "INSERT INTO profile (id,name) VALUES (?1,?2)",
|
||||
postgres: "INSERT INTO profile (id,name) VALUES ($1,$2)",
|
||||
},
|
||||
vec![Value::Int(1), Value::Text("one".into())],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let tenant_two = dever_runtime::tenant::scope(2, tenant_binding.database())
|
||||
.await
|
||||
.unwrap();
|
||||
tenant_two
|
||||
.execute(
|
||||
Sql {
|
||||
sqlite: "INSERT INTO profile (id,name) VALUES (?1,?2)",
|
||||
postgres: "INSERT INTO profile (id,name) VALUES ($1,$2)",
|
||||
},
|
||||
vec![Value::Int(1), Value::Text("two".into())],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let rows = tenant_two
|
||||
.query(
|
||||
Sql {
|
||||
sqlite: "SELECT name FROM profile WHERE id = ?1",
|
||||
postgres: "SELECT name FROM profile WHERE id = $1",
|
||||
},
|
||||
vec![Value::Int(1)],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(orm::text(rows[0].get(0).unwrap().clone()).unwrap(), "two");
|
||||
|
||||
let rows = tenant_one
|
||||
.query(
|
||||
Sql {
|
||||
sqlite: "SELECT name FROM profile WHERE id = ?1",
|
||||
postgres: "SELECT name FROM profile WHERE id = $1",
|
||||
},
|
||||
vec![Value::Int(1)],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(orm::text(rows[0].get(0).unwrap().clone()).unwrap(), "one");
|
||||
assert!(
|
||||
tenant_one
|
||||
.query(
|
||||
Sql {
|
||||
sqlite: "SELECT component FROM _dever_tenant_component",
|
||||
postgres: "SELECT component FROM _dever_tenant_component",
|
||||
},
|
||||
Vec::new()
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
|
||||
dever_runtime::tenant::enable_component(1, "news", &tenant_components)
|
||||
.await
|
||||
.unwrap();
|
||||
dever_runtime::tenant::require_components(1, &["news"], &tenant_components)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let transaction = tenant_one.begin().await.unwrap();
|
||||
let cross_tenant = match Executor::new(tenant_two.clone(), Some(&transaction)) {
|
||||
Ok(_) => panic!("one transaction must not cross tenant databases"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(cross_tenant.kind(), ErrorKind::Database);
|
||||
transaction.rollback().await.unwrap();
|
||||
|
||||
let pool_error = match dever_runtime::tenant::scope(3, tenant_binding.database()).await {
|
||||
Ok(_) => panic!("the tenant pool cap must be enforced"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert_eq!(pool_error.kind(), ErrorKind::PoolExhausted);
|
||||
drop(tenant_one);
|
||||
tokio::time::sleep(Duration::from_millis(1_050)).await;
|
||||
let tenant_three = dever_runtime::tenant::scope(3, tenant_binding.database())
|
||||
.await
|
||||
.unwrap();
|
||||
drop(tenant_three);
|
||||
|
||||
let global = StorageBinding::new(None, "content", ModelScope::Global)
|
||||
.database()
|
||||
.await
|
||||
.unwrap();
|
||||
global.execute(Sql {
|
||||
sqlite: "CREATE TABLE platform_flag (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
|
||||
postgres: "CREATE TABLE platform_flag (id BIGINT PRIMARY KEY, name TEXT NOT NULL)",
|
||||
}, Vec::new()).await.unwrap();
|
||||
global
|
||||
.execute(
|
||||
Sql {
|
||||
sqlite: "INSERT INTO platform_flag (id,name) VALUES (?1,?2)",
|
||||
postgres: "INSERT INTO platform_flag (id,name) VALUES ($1,$2)",
|
||||
},
|
||||
vec![Value::Int(1), Value::Text("shared".into())],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let global_from_other_tenant = dever_runtime::tenant::scope(
|
||||
2,
|
||||
StorageBinding::new(None, "content", ModelScope::Global).database(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let rows = global_from_other_tenant
|
||||
.query(
|
||||
Sql {
|
||||
sqlite: "SELECT name FROM platform_flag WHERE id = ?1",
|
||||
postgres: "SELECT name FROM platform_flag WHERE id = $1",
|
||||
},
|
||||
vec![Value::Int(1)],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
orm::text(rows[0].get(0).unwrap().clone()).unwrap(),
|
||||
"shared"
|
||||
);
|
||||
|
||||
database::shutdown().await.unwrap();
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user