Lines
100 %
Functions
Branches
//! Slice A: per-user database provisioning, exercised end to end against a real
//! Postgres via `server::provision::create_user_database`.
//!
//! Gated on the `db` feature (which turns on `server/test-utils`). These tests
//! create real databases through the admin `DATABASE_URL` and drop each one in
//! teardown — `sqlx::test` only cleans up the database it created itself, not
//! ones the application creates.
//! All scenarios run inside ONE `#[tokio::test]`. `server::db`'s admin pool is a
//! process-static `OnceCell<PgPool>` initialized lazily in the first runtime
//! that touches it; a separate `#[tokio::test]` per scenario would each spin up
//! its own runtime, and the pool bound to the first (now-dropped) runtime would
//! then time out for later tests. One runtime keeps the pool alive throughout —
//! and mirrors production, which provisions serially on one long-lived runtime.
//! Run via:
//! DATABASE_URL=postgres://… cargo test -p tests-integration --features db
#![cfg(feature = "db")]
use sqlx::postgres::PgPoolOptions;
use sqlx::{Executor, Row};
use uuid::Uuid;
async fn admin_connect() -> sqlx::PgPool {
let url =
std::env::var("DATABASE_URL").expect("DATABASE_URL must be set for provisioning tests");
PgPoolOptions::new()
.max_connections(1)
.connect(&url)
.await
.expect("connect to admin DB")
}
fn per_user_dsn(base: &str, database_name: &str) -> String {
let (prefix, rest) = base.split_once("://").expect("scheme");
let authority = rest.split('/').next().expect("authority");
format!("{prefix}://{authority}/{database_name}")
async fn drop_database(admin: &sqlx::PgPool, database_name: &str) {
// Terminate any lingering backends (a per-user pool may still hold a
// connection) before dropping.
let _ = admin
.execute(sqlx::query(sqlx::AssertSqlSafe(format!(
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity \
WHERE datname = '{database_name}' AND pid <> pg_backend_pid()"
))))
.await;
admin
"DROP DATABASE IF EXISTS \"{database_name}\""
.expect("drop provisioned database");
#[tokio::test]
async fn per_user_provisioning_end_to_end() -> anyhow::Result<()> {
let admin = admin_connect().await;
provisions_a_migrated_isolated_db(&admin).await?;
is_idempotent_on_retry(&admin).await?;
bootstrap_seed_is_idempotent(&admin).await?;
admin_has_backfilled_keypair(&admin).await?;
two_users_get_distinct_databases(&admin).await?;
private_key_is_stored_in_per_user_db(&admin).await?;
per_user_config_is_isolated(&admin).await?;
Ok(())
/// A freshly provisioned DB has the full schema, is usable, and contains no
/// seeded signing keys (those live only in the admin DB via `bootstrap::seed`).
async fn provisions_a_migrated_isolated_db(admin: &sqlx::PgPool) -> anyhow::Result<()> {
let user_id = Uuid::new_v4();
let database_name = format!("nomi_user_{}", user_id.simple());
// Returns the database NAME, not a DSN: a connection string in
// `users.database_name` would put a live credential in a row (ADR 0032).
let returned = server::provision::create_user_database(user_id).await?;
assert_eq!(
returned, database_name,
"provisioning must return the bare database name"
);
let base = std::env::var("DATABASE_URL")?;
let user_pool = PgPoolOptions::new()
.connect(&per_user_dsn(&base, &database_name))
.await?;
let accounts: i64 = sqlx::query("SELECT count(*) FROM accounts")
.fetch_one(&user_pool)
.await?
.get(0);
accounts, 0,
"fresh per-user DB has the accounts table, empty"
let secret_rows: i64 =
sqlx::query("SELECT count(*) FROM config WHERE lower(field) LIKE '%token_private_key'")
assert_eq!(secret_rows, 0, "per-user DB must not contain signing keys");
user_pool.close().await;
drop_database(admin, &database_name).await;
/// A second provision for the same user id hits SQLSTATE 42P04 (duplicate
/// database) and succeeds, returning the same DSN.
async fn is_idempotent_on_retry(admin: &sqlx::PgPool) -> anyhow::Result<()> {
let first = server::provision::create_user_database(user_id).await?;
let second = server::provision::create_user_database(user_id).await?;
assert_eq!(first, second);
/// `seed()` is guarded by the `seed_complete` marker, so a second call is a
/// no-op and never duplicates rows (config has no UNIQUE(field) yet).
async fn bootstrap_seed_is_idempotent(admin: &sqlx::PgPool) -> anyhow::Result<()> {
server::bootstrap::seed().await?;
let after_first: i64 = sqlx::query("SELECT count(*) FROM config")
.fetch_one(admin)
let after_second: i64 = sqlx::query("SELECT count(*) FROM config")
after_first, after_second,
"re-running seed must not change config row count"
let marker: i64 =
sqlx::query("SELECT count(*) FROM config WHERE lower(field) = 'seed_complete'")
assert_eq!(marker, 1, "exactly one seed_complete marker");
// The admin's data lives in the admin database itself, so bootstrap points
// its database_name at *this* database — a bare name, never the empty seed
// placeholder and never a DSN, which would store a live credential in a row.
let admin_db_name: String = sqlx::query(
"SELECT database_name FROM users WHERE id = '22d36bde-d987-4927-b070-842efdcf9c59'",
)
let expected: String = sqlx::query("SELECT current_database()")
admin_db_name, expected,
"admin database_name must be this database's name, not a placeholder or a DSN"
assert!(
!admin_db_name.contains("://"),
"database_name must never hold a connection string: {admin_db_name}"
/// After `bootstrap::seed`, the admin user has a public signing key in the
/// global directory and a matching private key in the admin DB — so admin login
/// keeps working once the global seed keys are retired (Slice B cutover).
async fn admin_has_backfilled_keypair(admin: &sqlx::PgPool) -> anyhow::Result<()> {
let public_key: Option<String> = sqlx::query(
"SELECT jwt_public_key FROM users WHERE id = '22d36bde-d987-4927-b070-842efdcf9c59'",
public_key.is_some_and(|k| !k.is_empty()),
"admin must have a backfilled public key"
let private_keys: i64 = sqlx::query("SELECT count(*) FROM user_auth_keys")
private_keys >= 1,
"admin DB must hold the admin private key"
// Repair path: a prior run that wrote only the public key (then crashed
// before the private insert) must be healed on the next seed, not skipped.
sqlx::query("DELETE FROM user_auth_keys")
.execute(admin)
let repaired: i64 = sqlx::query("SELECT count(*) FROM user_auth_keys")
repaired, 1,
"seed must repair a missing admin private key (public-set-but-private-missing)"
/// `store_user_private_key` writes the private key into the per-user DB only.
async fn private_key_is_stored_in_per_user_db(admin: &sqlx::PgPool) -> anyhow::Result<()> {
// Both the provisioner and the key store speak database *names*; only
// this test composes a DSN, to look inside the tenant database directly.
let name = server::provision::create_user_database(user_id).await?;
let keypair = server::auth_keys::generate().await?;
server::provision::store_user_private_key(&name, &keypair.private_pem_b64).await?;
let stored: String = sqlx::query("SELECT private_key FROM user_auth_keys")
stored, keypair.private_pem_b64,
"private key round-trips in per-user DB"
// Concurrent stores must never leave more than one key: the singleton
// constraint (0005) forces them to collide-and-upsert, not duplicate. Each
// store_user_private_key opens its own pool, so this is a real race.
let kp2 = server::auth_keys::generate().await?;
let kp3 = server::auth_keys::generate().await?;
let (r2, r3) = tokio::join!(
server::provision::store_user_private_key(&name, &kp2.private_pem_b64),
server::provision::store_user_private_key(&name, &kp3.private_pem_b64),
r2?;
r3?;
let key_count: i64 = sqlx::query("SELECT count(*) FROM user_auth_keys")
key_count, 1,
"concurrent stores must keep exactly one private key"
/// Two users in distinct provisioned databases set the SAME config key to
/// different values; each reads back only their own — per-user config is
/// isolated, and neither leaks into the other's DB.
async fn per_user_config_is_isolated(admin: &sqlx::PgPool) -> anyhow::Result<()> {
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let da = format!("nomi_user_{}", a.simple());
let dbn = format!("nomi_user_{}", b.simple());
for (id, dsn) in [
(a, server::provision::create_user_database(a).await?),
(b, server::provision::create_user_database(b).await?),
] {
// Register in the global directory so User::get_connection routes to the
// per-user DB.
sqlx::query(
// `verified` defaults FALSE and the tenant pool refuses a disabled
// account, so this fixture must opt in explicitly.
"INSERT INTO users (id, user_name, email, user_password, database_name, verified) \
VALUES ($1, 'cfg-iso', $2, 'x', $3, TRUE)",
.bind(id)
.bind(format!("cfg-iso-{id}@example.test"))
.bind(&dsn)
server::user::User { id: a }
.set_config("theme", "dark".into())
server::user::User { id: b }
.set_config("theme", "light".into())
// Re-set the same key (case-variant) on a freshly-provisioned DB: this
// exercises the ON CONFLICT (LOWER(field)) upsert against the 0006 index
// that provisioning installs, proving new per-user DBs carry the index.
.set_config("Theme", "midnight".into())
let a_val = server::user::User { id: a }.config("theme").await?;
let b_val = server::user::User { id: b }.config("theme").await?;
assert_eq!(a_val.map(|v| v.to_string()), Some("midnight".to_string()));
assert_eq!(b_val.map(|v| v.to_string()), Some("light".to_string()));
sqlx::query("DELETE FROM users WHERE id = $1 OR id = $2")
.bind(a)
.bind(b)
drop_database(admin, &da).await;
drop_database(admin, &dbn).await;
/// Distinct users get distinct, deterministically-named databases.
async fn two_users_get_distinct_databases(admin: &sqlx::PgPool) -> anyhow::Result<()> {
let db = format!("nomi_user_{}", b.simple());
let name_a = server::provision::create_user_database(a).await?;
let name_b = server::provision::create_user_database(b).await?;
assert_ne!(name_a, name_b, "distinct users must get distinct databases");
assert_eq!(name_a, da);
assert_eq!(name_b, db);
drop_database(admin, &db).await;