06f48ef9e9
Cargo workspace (kvitt-domain, kvitt-server, kvitt-import), Rust 1.99.1 pinned. kvitt-domain passes every Rust vector and adds next_occurrence_date (D-015). kvitt-server: config, SQLite pool (WAL, §8.9 pragmas), migration 0001 identical to the contract schema, error type for all 25 codes, auth extractor and protocol layer, all 33 routes (E-1/E-2 implemented, the rest 501), utoipa document equal to contracts/openapi.yaml, seams for M3/M4/M9, serve/migrate/healthcheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.1 KiB
Rust
97 lines
3.1 KiB
Rust
//! `0001_init.sql` is the contract schema byte for byte (SPEC §14.1), and connections carry the §8.9 pragmas.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use kvitt_server::db;
|
|
|
|
use crate::support::TestApp;
|
|
|
|
fn manifest(path: &str) -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path)
|
|
}
|
|
|
|
#[test]
|
|
fn migration_0001_is_byte_identical_to_contract_schema() {
|
|
let migration = std::fs::read(manifest("migrations/0001_init.sql")).unwrap();
|
|
let contract = std::fs::read(manifest("../../../contracts/schema/server-v1.sql")).unwrap();
|
|
assert!(
|
|
migration == contract,
|
|
"migrations/0001_init.sql differs from contracts/schema/server-v1.sql"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn the_embedded_migrator_holds_exactly_that_migration() {
|
|
let contract =
|
|
std::fs::read_to_string(manifest("../../../contracts/schema/server-v1.sql")).unwrap();
|
|
let migrations: Vec<_> = db::MIGRATOR.iter().collect();
|
|
assert_eq!(migrations.len(), 1);
|
|
assert_eq!(migrations[0].version, 1);
|
|
assert_eq!(migrations[0].sql.as_ref(), contract.as_str());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn migrated_database_has_schema_version_seed_rows_and_pragmas() {
|
|
let app = TestApp::empty().await;
|
|
let pool = &app.state.db;
|
|
let version: String = sqlx::query_scalar("SELECT value FROM meta WHERE key = 'schema_version'")
|
|
.fetch_one(pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(version, db::SCHEMA_VERSION.to_string());
|
|
let epoch: String = sqlx::query_scalar("SELECT value FROM meta WHERE key = 'sync_epoch'")
|
|
.fetch_one(pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(epoch.len(), 32);
|
|
let categories: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM categories")
|
|
.fetch_one(pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(categories, 10);
|
|
|
|
let mut conn = pool.acquire().await.unwrap();
|
|
let journal: String = sqlx::query_scalar("PRAGMA journal_mode")
|
|
.fetch_one(&mut *conn)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(journal, "wal");
|
|
let fk: i64 = sqlx::query_scalar("PRAGMA foreign_keys")
|
|
.fetch_one(&mut *conn)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(fk, 1);
|
|
let sync: i64 = sqlx::query_scalar("PRAGMA synchronous")
|
|
.fetch_one(&mut *conn)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(sync, 1, "synchronous = NORMAL");
|
|
let busy: i64 = sqlx::query_scalar("PRAGMA busy_timeout")
|
|
.fetch_one(&mut *conn)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(busy, 5000);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn migrating_twice_is_a_no_op() {
|
|
let app = TestApp::empty().await;
|
|
db::migrate(&app.state.db).await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn server_version_sequence_starts_after_the_seed_and_increments() {
|
|
let app = TestApp::empty().await;
|
|
let mut tx = db::begin_immediate(&app.state.db).await.unwrap();
|
|
assert_eq!(db::next_server_version(&mut tx).await.unwrap(), 11);
|
|
assert_eq!(db::next_server_version(&mut tx).await.unwrap(), 12);
|
|
tx.commit().await.unwrap();
|
|
let mut conn = app.state.db.acquire().await.unwrap();
|
|
assert_eq!(
|
|
db::sequence::current_server_version(&mut conn)
|
|
.await
|
|
.unwrap(),
|
|
12
|
|
);
|
|
}
|