use std::sync::Arc;
use dbos::sysdb::DEFAULT_SCHEMA;
use dbos_test_support::{Backend, SharedSlot, raw_database, test_database};
#[tokio::test]
async fn connects_to_a_fresh_database() {
let db = raw_database().await;
let pool = db.pool().await;
let one: i64 = sqlx::query_scalar("SELECT 1::BIGINT")
.fetch_one(&pool)
.await
.expect("SELECT 1 failed");
assert_eq!(one, 1);
let tables: i64 = sqlx::query_scalar(
"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public'",
)
.fetch_one(&pool)
.await
.expect("failed to count tables");
assert_eq!(tables, 0, "a fresh test database should have no tables");
}
#[tokio::test]
async fn overlapping_tests_share_one_container() {
let first = raw_database().await;
let second = raw_database().await;
assert_eq!(
first.server().container_id(),
second.server().container_id(),
"databases held at the same time should share one container",
);
assert_ne!(
first.url(),
second.url(),
"each test should get its own database",
);
}
const CATALOGUE: &str = "\
SELECT 'column|'||table_name||'|'||column_name||'|'||ordinal_position||'|'||data_type \
||'|'||is_nullable||'|'||coalesce(column_default, '-') \
FROM information_schema.columns WHERE table_schema = $1 \
UNION ALL \
SELECT 'constraint|'||table_name||'|'||constraint_name||'|'||constraint_type \
FROM information_schema.table_constraints \
WHERE table_schema = $1 AND constraint_type <> 'CHECK' \
UNION ALL \
SELECT 'index|'||tablename||'|'||indexname||'|'||replace(indexdef, current_database()||'.', '') \
FROM pg_indexes WHERE schemaname = $1 \
UNION ALL \
SELECT 'routine|'||routine_name||'|'||coalesce(data_type, '-') \
FROM information_schema.routines WHERE routine_schema = $1 \
ORDER BY 1";
async fn catalogue(pool: &sqlx::PgPool) -> Vec<String> {
sqlx::query_scalar(CATALOGUE)
.bind(DEFAULT_SCHEMA)
.fetch_all(pool)
.await
.expect("failed to read the catalogue")
}
async fn recorded_version(pool: &sqlx::PgPool) -> i64 {
sqlx::query_scalar("SELECT version FROM dbos.dbos_migrations")
.fetch_one(pool)
.await
.expect("failed to read the recorded migration version")
}
#[tokio::test]
async fn a_pooled_database_matches_one_the_migrations_built() {
let migrated = raw_database().await;
let pool = migrated.pool().await;
dbos::sysdb::migrations::runner::run(&pool, DEFAULT_SCHEMA, true)
.await
.expect("failed to migrate the comparison database");
let from_migrations = catalogue(&pool).await;
let migrated_version = recorded_version(&pool).await;
pool.close().await;
let leased = test_database().await;
let pool = leased.pool().await;
let from_baseline = catalogue(&pool).await;
let leased_version = recorded_version(&pool).await;
pool.close().await;
assert!(
!from_migrations.is_empty(),
"the comparison database came up empty, so this test would pass on anything",
);
let missing: Vec<_> = from_migrations
.iter()
.filter(|row| !from_baseline.contains(row))
.collect();
let extra: Vec<_> = from_baseline
.iter()
.filter(|row| !from_migrations.contains(row))
.collect();
assert!(
missing.is_empty() && extra.is_empty(),
"a pooled database's schema differs from what the migrations build on {:?}\n\
missing from the pooled database: {missing:#?}\n\
present only in the pooled database: {extra:#?}",
leased.backend(),
);
assert_eq!(
leased_version, migrated_version,
"a pooled database records a different migration version than a migrated one, so \
anything reading it would try to migrate over the top of a finished schema",
);
}
#[tokio::test]
async fn a_leased_database_is_already_migrated() {
let db = test_database().await;
let pool = db.pool().await;
let tables: i64 = sqlx::query_scalar(
"SELECT count(*) FROM information_schema.tables WHERE table_schema = $1",
)
.bind(DEFAULT_SCHEMA)
.fetch_one(&pool)
.await
.expect("failed to count tables");
assert!(
tables > 10,
"the DBOS schema should already exist, saw {tables}"
);
}
#[tokio::test]
async fn resetting_clears_a_previous_holders_rows() {
let db = test_database().await;
let pool = db.pool().await;
sqlx::query(
"INSERT INTO dbos.workflow_status (workflow_uuid, status, name) \
VALUES ($1, 'PENDING', 'leftover')",
)
.bind("wf-from-a-previous-test")
.execute(&pool)
.await
.expect("failed to write a row");
let count = || async {
sqlx::query_scalar::<_, i64>("SELECT count(*) FROM dbos.workflow_status")
.fetch_one(&pool)
.await
.expect("failed to count rows")
};
assert_eq!(count().await, 1, "the row should be there to begin with");
db.reset().await;
assert_eq!(count().await, 0, "reset should have cleared it");
}
#[tokio::test]
async fn shared_slot_releases_once_nobody_holds_it() {
let slot: SharedSlot<u32> = SharedSlot::new();
let first = slot.get_or_init(|| async { 1 }).await;
let second = slot.get_or_init(|| async { 2 }).await;
assert!(
Arc::ptr_eq(&first, &second),
"a second caller should share the first caller's value, not create another",
);
assert_eq!(*second, 1, "the factory should not have run a second time");
drop(first);
drop(second);
let third = slot.get_or_init(|| async { 3 }).await;
assert_eq!(
*third, 3,
"once the last holder drops, the next caller should get a fresh value — if this \
is 1, the slot is holding a strong reference and containers will leak",
);
}
#[tokio::test]
async fn serial_width_diverges_between_backends() {
let db = raw_database().await;
let pool = db.pool().await;
sqlx::raw_sql("CREATE TABLE serial_probe (id SERIAL PRIMARY KEY)")
.execute(&pool)
.await
.expect("failed to create the probe table");
let data_type: String = sqlx::query_scalar(
"SELECT data_type FROM information_schema.columns \
WHERE table_name = 'serial_probe' AND column_name = 'id'",
)
.fetch_one(&pool)
.await
.expect("failed to read the column type");
let expected = match db.backend() {
Backend::Postgres => "integer",
Backend::Cockroach => "bigint",
};
assert_eq!(
data_type.to_ascii_lowercase(),
expected,
"SERIAL width on {:?} — if this changed, re-check every integer column in the \
migrations before trusting it",
db.backend(),
);
}