Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,332 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn explicit_migration_routes_models_roles_and_control_to_distinct_physical_storage() {
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let registry = Registry::prepare(&directory.0, settings(true, 4), &bindings(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(registry.tenant_enabled());
|
||||
assert!(registry.is_tenant(0));
|
||||
let mut missing = Context::new(registry.clone());
|
||||
assert_eq!(
|
||||
missing.on(0, scope).await.err().unwrap().kind,
|
||||
Kind::InvalidData
|
||||
);
|
||||
assert_eq!(
|
||||
missing.select_tenant(scope, 1).await.unwrap_err().kind,
|
||||
Kind::Migration
|
||||
);
|
||||
assert!(!directory.0.join("tenants").exists());
|
||||
let rows = missing
|
||||
.control(scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(
|
||||
scope,
|
||||
"SELECT COUNT(*) FROM sqlite_schema WHERE name='items'",
|
||||
vec![],
|
||||
1,
|
||||
|row| row.get::<_, i64>(0),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(rows, [0]);
|
||||
registry.migrate_tenant(scope, 1).await.unwrap();
|
||||
registry.migrate_tenant(scope, 2).await.unwrap();
|
||||
registry.migrate_tenant(scope, 1).await.unwrap();
|
||||
assert_eq!(
|
||||
registry.queue_tenants(0, scope).await.unwrap(),
|
||||
[Some(1), Some(2)]
|
||||
);
|
||||
assert_eq!(registry.queue_tenants(1, scope).await.unwrap(), [None]);
|
||||
for id in [1, 2] {
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, id).await.unwrap();
|
||||
assert_eq!(context.tenant_id(), Some(id));
|
||||
assert_eq!(context.detached().tenant_id(), Some(id));
|
||||
let owner = context.enter();
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(
|
||||
scope,
|
||||
"INSERT INTO items(id,value) VALUES(1,$1)",
|
||||
vec![crate::database::Parameter::Text(format!("tenant {id}"))],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.on(2, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(scope, "INSERT INTO roles(id) VALUES(1)", vec![])
|
||||
.await
|
||||
.unwrap();
|
||||
context.finish(scope, owner, true).await.unwrap();
|
||||
let rows = context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT value FROM items", vec![], 1, |row| {
|
||||
row.get::<_, String>(0)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(rows, [format!("tenant {id}")]);
|
||||
let count = context
|
||||
.control(scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM roles", vec![], 1, |row| {
|
||||
row.get::<_, i64>(0)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, [0]);
|
||||
}
|
||||
registry.close().await;
|
||||
assert!(directory.0.join("tenants/1.db").is_file());
|
||||
assert!(directory.0.join("tenants/2.db").is_file());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_switches_are_immediate_validated_and_do_not_hide_ready_queues() {
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let registry = Registry::prepare(&directory.0, settings(true, 2), &bindings(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
registry.migrate_tenant(scope, 1).await.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, 1).await.unwrap();
|
||||
assert!(context.components_enabled(scope, &["shop"]).await.unwrap());
|
||||
registry
|
||||
.set_component(scope, 1, "shop", false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!context.components_enabled(scope, &["shop"]).await.unwrap());
|
||||
assert!(context.require_components(scope, &["shop"]).await.is_err());
|
||||
assert!(
|
||||
context
|
||||
.components_enabled(scope, &["platform"])
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(registry.queue_tenants(0, scope).await.unwrap(), [Some(1)]);
|
||||
for invalid in ["platform", "unknown", "dever"] {
|
||||
assert_eq!(
|
||||
registry
|
||||
.set_component(scope, 1, invalid, false)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.kind,
|
||||
Kind::InvalidData
|
||||
);
|
||||
}
|
||||
registry
|
||||
.set_component(scope, 1, "shop", true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(context.components_enabled(scope, &["shop"]).await.unwrap());
|
||||
registry.close().await;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_component_listing_paginates_and_observes_current_tenant_state() {
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let mut models = bindings().to_vec();
|
||||
for component in ["billing", "catalog"] {
|
||||
models.push(crate::database::Binding {
|
||||
component,
|
||||
table: component,
|
||||
schema: if component == "billing" {
|
||||
Sql::same("CREATE TABLE billing(id BIGINT PRIMARY KEY)")
|
||||
} else {
|
||||
Sql::same("CREATE TABLE catalog(id BIGINT PRIMARY KEY)")
|
||||
},
|
||||
..bindings()[0].clone()
|
||||
});
|
||||
}
|
||||
let registry = Registry::prepare(&directory.0, settings(true, 2), &models, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
registry.migrate_tenant(scope, 1).await.unwrap();
|
||||
registry.migrate_tenant(scope, 2).await.unwrap();
|
||||
let components = ["billing", "catalog", "shop", "platform", "unknown"]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut context = Context::new(registry.clone());
|
||||
assert!(
|
||||
context
|
||||
.disabled_components(scope, &components)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
context.select_tenant(scope, 1).await.unwrap();
|
||||
for component in ["billing", "catalog", "shop"] {
|
||||
registry
|
||||
.set_component(scope, 1, component, false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
assert_eq!(
|
||||
context
|
||||
.disabled_components(scope, &components)
|
||||
.await
|
||||
.unwrap(),
|
||||
["billing", "catalog", "shop"]
|
||||
.map(String::from)
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
registry
|
||||
.set_component(scope, 1, "catalog", true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
context
|
||||
.disabled_components(scope, &components)
|
||||
.await
|
||||
.unwrap(),
|
||||
["billing", "shop"].map(String::from).into_iter().collect()
|
||||
);
|
||||
context.select_tenant(scope, 2).await.unwrap();
|
||||
assert!(
|
||||
context
|
||||
.disabled_components(scope, &components)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
registry.close().await;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transactions_and_foreign_keys_cannot_cross_tenant_boundaries() {
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let registry = Registry::prepare(&directory.0, settings(true, 2), &bindings(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(registry.check_transactions(&[&[0, 1]]).is_err());
|
||||
registry.check_transactions(&[&[0, 2]]).unwrap();
|
||||
registry.check_transactions(&[&[1, 2]]).unwrap();
|
||||
assert!(registry.check_transactions(&[&[2, 0, 1]]).is_err());
|
||||
registry.migrate_tenant(scope, 1).await.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, 1).await.unwrap();
|
||||
context
|
||||
.on(1, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM accounts", vec![], 1, |row| {
|
||||
row.get::<_, i64>(0)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let owner = context.enter();
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(
|
||||
scope,
|
||||
"INSERT INTO items(id,value) VALUES(1,'rollback')",
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(context.on(1, scope).await.is_err());
|
||||
assert!(context.select_tenant(scope, 2).await.is_err());
|
||||
context.finish(scope, owner, false).await.unwrap();
|
||||
assert_eq!(
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM items", vec![], 1, |row| row
|
||||
.get::<_, i64>(0))
|
||||
.await
|
||||
.unwrap(),
|
||||
[0]
|
||||
);
|
||||
let owner = context.enter();
|
||||
context
|
||||
.on(1, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(scope, "INSERT INTO accounts(id) VALUES(1)", vec![])
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(context.on(0, scope).await.is_err());
|
||||
context.finish(scope, owner, true).await.unwrap();
|
||||
assert_eq!(
|
||||
context
|
||||
.on(1, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM accounts", vec![], 1, |row| row
|
||||
.get::<_, i64>(0))
|
||||
.await
|
||||
.unwrap(),
|
||||
[1]
|
||||
);
|
||||
drop(context);
|
||||
registry.close().await;
|
||||
let mut invalid = bindings();
|
||||
invalid[0].references = &[1];
|
||||
assert_eq!(
|
||||
Registry::prepare(&directory.0, settings(true, 2), &invalid, scope)
|
||||
.await
|
||||
.err()
|
||||
.unwrap()
|
||||
.kind,
|
||||
Kind::Connection
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_tenant_configuration_preserves_ordinary_database_routing() {
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let registry = Registry::prepare(&directory.0, settings(false, 2), &bindings(), scope)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!registry.tenant_enabled());
|
||||
registry.check_transactions(&[&[0, 1, 2]]).unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
assert!(context.select_tenant(scope, 1).await.is_err());
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(
|
||||
scope,
|
||||
"INSERT INTO items(id,value) VALUES(1,'platform')",
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(context.require_components(scope, &["shop"]).await.is_ok());
|
||||
drop(context);
|
||||
registry.close().await;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user