Server overhaul: one test harness in tests/common for every test binary

core_api/common.rs becomes tests/common/mod.rs and sync_api/m4.rs tests/common/sync_fixtures.rs; each binary
includes them through #[path] (the binaries of SPEC §14.7 stay as they are). TempDir's Default moves into the
harness instead of three copies; the skeleton helpers take ids, tokens and TempDir from it. Milestone wording in
test docs and the server README replaced by what the code does; README seams and test layout updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-18 22:31:04 +02:00
parent 6c405e142d
commit 8cb42f85a9
19 changed files with 118 additions and 146 deletions
@@ -1,16 +1,10 @@
//! M4 scheduler tests (SPEC §9.5 scheduler cases, §14.1 pruning, D-021): the daily run planned at local 06:00 and
//! Scheduler tests (SPEC §9.5 scheduler cases, §14.1 pruning, D-021): the daily run planned at local 06:00 and
//! 04:00 across both DST changes, the run at start, clock jumps with an injected clock, catch-up after downtime
//! through M3's generation function, paused and deleted templates skipped, and the 90-day pruning.
//! through the recurring generation, paused and deleted templates skipped, and the 90-day pruning.
#[path = "../core_api/common.rs"]
#[path = "../common/mod.rs"]
pub mod common;
#[path = "../sync_api/m4.rs"]
pub mod m4;
#[path = "../common/sync_fixtures.rs"]
pub mod sync_fixtures;
mod scheduler;
impl Default for common::TempDir {
fn default() -> Self {
Self::new()
}
}
@@ -9,7 +9,7 @@ use kvitt_server::time::{FixedClock, format_ts, parse_ts};
use tokio::sync::mpsc;
use crate::common::*;
use crate::m4;
use crate::sync_fixtures;
fn ts(text: &str) -> chrono::DateTime<chrono::Utc> {
parse_ts(text).unwrap()
@@ -175,10 +175,10 @@ async fn periods(app: &TestApp, template: &str) -> Vec<String> {
#[tokio::test]
async fn start_runs_generation_catching_up_after_downtime_and_skips_paused_and_deleted() {
let mut app = m4::app().await;
let active = m4::new_id();
let paused = m4::new_id();
let deleted = m4::new_id();
let mut app = sync_fixtures::app().await;
let active = sync_fixtures::new_id();
let paused = sync_fixtures::new_id();
let deleted = sync_fixtures::new_id();
// Last generated July; the server was down since. Now is 17 October 14:00.
insert_template(&app, &active, 15, "2026-06", Some("2026-07"), false, false).await;
insert_template(&app, &paused, 1, "2026-06", None, true, false).await;
@@ -204,8 +204,8 @@ async fn start_runs_generation_catching_up_after_downtime_and_skips_paused_and_d
#[tokio::test]
async fn the_06_00_tick_generates_the_period_due_that_morning() {
let app = m4::app().await;
let id = m4::new_id();
let app = sync_fixtures::app().await;
let id = sync_fixtures::new_id();
insert_template(&app, &id, 15, "2026-11", None, false, false).await;
// 15 November 2026, 05:59 CET: not yet.
app.set_now("2026-11-15T04:59:00.000Z");
@@ -239,14 +239,14 @@ async fn the_06_00_tick_generates_the_period_due_that_morning() {
}
async fn insert_mutation(app: &TestApp, received_at: &str) -> String {
let id = m4::new_id();
let id = sync_fixtures::new_id();
sqlx::query(
"INSERT INTO sync_mutations (mutation_id, device_id, entity_type, entity_id, result, received_at) \
VALUES (?, ?, 'expense', ?, '{}', ?)",
)
.bind(&id)
.bind(ADMIN_DEVICE_ID)
.bind(m4::new_id())
.bind(sync_fixtures::new_id())
.bind(received_at)
.execute(&app.state.db)
.await
@@ -256,7 +256,7 @@ async fn insert_mutation(app: &TestApp, received_at: &str) -> String {
#[tokio::test]
async fn pruning_deletes_rows_older_than_90_days_and_keeps_the_rest() {
let app = m4::app().await;
let app = sync_fixtures::app().await;
let now = ts(NOW);
insert_mutation(
&app,
@@ -277,7 +277,7 @@ async fn pruning_deletes_rows_older_than_90_days_and_keeps_the_rest() {
#[tokio::test]
async fn pruning_runs_at_04_00_local() {
let app = m4::app().await;
let app = sync_fixtures::app().await;
// 03:59 CEST on 18 October.
app.set_now("2026-10-18T01:59:00.000Z");
insert_mutation(&app, "2026-07-01T00:00:00.000Z").await;
@@ -290,11 +290,13 @@ async fn pruning_runs_at_04_00_local() {
.await;
});
tokio::time::sleep(Duration::from_millis(30)).await;
assert_eq!(m4::count(&app, "sync_mutations").await, 1);
assert_eq!(sync_fixtures::count(&app, "sync_mutations").await, 1);
app.set_now("2026-10-18T02:00:00.000Z");
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while m4::count(&app, "sync_mutations").await > 0 && std::time::Instant::now() < deadline {
while sync_fixtures::count(&app, "sync_mutations").await > 0
&& std::time::Instant::now() < deadline
{
tokio::time::sleep(Duration::from_millis(5)).await;
}
assert_eq!(m4::count(&app, "sync_mutations").await, 0);
assert_eq!(sync_fixtures::count(&app, "sync_mutations").await, 0);
}