333 lines
12 KiB
Rust
333 lines
12 KiB
Rust
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();
|
|
}
|