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:
+10
-3
@@ -1,4 +1,5 @@
|
||||
//! The M3 test harness: a migrated temporary database, a fixed clock, recording fakes of `AuditWriter` and
|
||||
//! The integration-test harness shared by every test binary of this crate (included with `#[path]`, so it is not a
|
||||
//! test target itself): a migrated temporary database, a fixed clock, recording fakes of `AuditWriter` and
|
||||
//! `PushQueue`, a seeded household with the ids of `contracts/examples/`, one router per app (so the rate limiter
|
||||
//! keeps its state across requests) and helpers to compare responses with the contract examples.
|
||||
|
||||
@@ -49,7 +50,7 @@ pub fn old_device_token() -> String {
|
||||
token('R')
|
||||
}
|
||||
|
||||
/// Records every audit event (M4 replaces the real writer; M3 only calls it).
|
||||
/// Records every audit event (`sync_fixtures::app` installs the real writer instead).
|
||||
#[derive(Default)]
|
||||
pub struct RecordingAudit(pub Mutex<Vec<AuditEvent>>);
|
||||
|
||||
@@ -85,6 +86,12 @@ impl TempDir {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TempDir {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
@@ -291,7 +298,7 @@ impl TestApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// An expense inserted directly (sync is M4's): `shares` are (admin öre, member öre).
|
||||
/// An expense inserted directly (not through sync): `shares` are (admin öre, member öre).
|
||||
pub async fn insert_expense(
|
||||
&self,
|
||||
account: &str,
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
//! M4 additions to M3's harness: an app with the real audit writer, entity and mutation builders, push and pull
|
||||
//! helpers, and audit row access. Shared by the `sync_api`, `push_fcm` and `scheduler_daily` test binaries.
|
||||
//! Sync additions to the harness of `common`: an app with the real audit writer, entity and mutation builders, push
|
||||
//! and pull helpers, and audit row access. Shared by the `sync_api`, `push_fcm` and `scheduler_daily` test binaries.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
//! M3 integration tests: every endpoint of E-2 to E-30 and E-33 with each error code SPEC §14.3.4 lists for it, the
|
||||
//! recurring generation cases of SPEC §9.5, rate limiting (§8.6, AC-2.12), pictures (AC-4.4), the landing page
|
||||
//! (AC-2.5), APK serving (§10.6), the `bootstrap` and `pair-device` subcommands (FR-1) and the logging rule (§8.7).
|
||||
//! REST integration tests: every endpoint of E-2 to E-30 and E-33 with each error code SPEC §14.3.4 lists for it,
|
||||
//! the recurring generation cases of SPEC §9.5, rate limiting (§8.6, AC-2.12), pictures (AC-4.4), the landing page
|
||||
//! (AC-2.5), APK serving (§10.6), the `bootstrap`, `pair-device` and `invite-partner` subcommands (FR-1, D-113) and
|
||||
//! the logging rule (§8.7).
|
||||
//!
|
||||
//! One test binary (faster to build than one per file); `common` is the harness.
|
||||
//! One test binary (faster to build than one per file); the harness is `tests/common/`.
|
||||
|
||||
#[path = "../common/mod.rs"]
|
||||
pub mod common;
|
||||
|
||||
mod accounts;
|
||||
mod apk;
|
||||
mod categories;
|
||||
mod cli;
|
||||
mod common;
|
||||
mod devices;
|
||||
mod generation;
|
||||
mod invites;
|
||||
|
||||
@@ -16,8 +16,8 @@ use serde_json::{Value, json};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4;
|
||||
use crate::stub::*;
|
||||
use crate::sync_fixtures;
|
||||
|
||||
const EXPENSE: &str = "01929c5e-1111-7aaa-8bbb-000000000001";
|
||||
const FAST: [u64; 3] = [10, 20, 40];
|
||||
@@ -31,7 +31,7 @@ struct Setup {
|
||||
|
||||
impl Setup {
|
||||
async fn new() -> Self {
|
||||
let app = m4::app().await;
|
||||
let app = sync_fixtures::app().await;
|
||||
for (device, tok) in [
|
||||
(ADMIN_DEVICE_ID, "tok-admin"),
|
||||
(MEMBER_DEVICE_ID, "tok-member"),
|
||||
@@ -445,12 +445,12 @@ async fn sync_push_reaches_fcm_through_the_queue_and_the_sender_task() {
|
||||
delays: vec![Duration::from_millis(10)],
|
||||
},
|
||||
);
|
||||
m4::with_push(&mut s.app, queue);
|
||||
let id = m4::new_id();
|
||||
m4::push_one(
|
||||
sync_fixtures::with_push(&mut s.app, queue);
|
||||
let id = sync_fixtures::new_id();
|
||||
sync_fixtures::push_one(
|
||||
&s.app,
|
||||
&member_token(),
|
||||
m4::exp(m4::expense(&id, "2026-10-17T11:00:00.000Z")),
|
||||
sync_fixtures::exp(sync_fixtures::expense(&id, "2026-10-17T11:00:00.000Z")),
|
||||
)
|
||||
.await;
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
//! M4 push tests (SPEC §14.6, FR-13): payloads of every type against `contracts/examples/push/`, recipients, the
|
||||
//! Push tests (SPEC §14.6, FR-13): payloads of every type against `contracts/examples/push/`, recipients, the
|
||||
//! OAuth2 service-account flow and token cache, retries and their delays, token clearing on `UNREGISTERED`/404,
|
||||
//! queue overflow, push disabled without credentials, all against a local stub of Google's two endpoints.
|
||||
|
||||
#[path = "../core_api/common.rs"]
|
||||
#[path = "../common/mod.rs"]
|
||||
pub mod common;
|
||||
#[path = "../sync_api/m4.rs"]
|
||||
pub mod m4;
|
||||
pub mod stub;
|
||||
#[path = "../common/sync_fixtures.rs"]
|
||||
pub mod sync_fixtures;
|
||||
|
||||
mod fcm;
|
||||
|
||||
impl Default for common::TempDir {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//! Integration tests of the M2a server skeleton: migration identity and pragmas, routes, authentication and the
|
||||
//! protocol rule, error shapes, and the wire types against `contracts/examples/`.
|
||||
//! Base server tests: migration identity and pragmas, routes, authentication and the protocol rule, error shapes,
|
||||
//! and the wire types against `contracts/examples/`.
|
||||
|
||||
#[path = "../common/mod.rs"]
|
||||
pub mod common;
|
||||
|
||||
mod errors;
|
||||
mod migration;
|
||||
|
||||
@@ -80,7 +80,7 @@ async fn every_route_is_registered_and_implemented() {
|
||||
33,
|
||||
"SPEC §7 has 33 operations"
|
||||
);
|
||||
// M3 implemented E-3 to E-30 and E-33 (tests/core_api), M4 the sync routes (tests/sync_api): nothing is 501.
|
||||
// Every route has its handler (behaviour in tests/core_api and tests/sync_api): nothing is 501.
|
||||
for (method, path, _) in routes {
|
||||
let reply = app
|
||||
.send(authed(method.clone(), &path, &admin_token()))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Shared helpers of the skeleton tests: a migrated temporary database, seeded users and devices, a fixed
|
||||
//! clock and a one-shot request helper.
|
||||
//! Helpers of the base tests: a database migrated to a chosen schema version, seeded users and devices (the
|
||||
//! revoked device here is the member's), a fixed clock and a one-shot request helper. Ids, tokens and the
|
||||
//! temporary directory come from the shared harness (`tests/common/`).
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::body::Body;
|
||||
@@ -12,45 +12,14 @@ use kvitt_server::time::{FixedClock, parse_ts};
|
||||
use kvitt_server::{AppState, auth, db, router};
|
||||
use serde_json::Value;
|
||||
use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const ADMIN_ID: &str = "0192a000-0000-7000-8000-000000000001";
|
||||
pub const MEMBER_ID: &str = "0192a000-0000-7000-8000-000000000002";
|
||||
pub const ADMIN_DEVICE_ID: &str = "0192a001-0000-7000-8000-000000000001";
|
||||
pub const MEMBER_DEVICE_ID: &str = "0192a001-0000-7000-8000-000000000002";
|
||||
pub const REVOKED_DEVICE_ID: &str = "0192a001-0000-7000-8000-000000000003";
|
||||
pub const NOW: &str = "2026-10-17T12:00:00.000Z";
|
||||
pub use crate::common::{
|
||||
ADMIN_DEVICE_ID, ADMIN_ID, MEMBER_DEVICE_ID, MEMBER_ID, NOW, TempDir, admin_token,
|
||||
member_token, old_device_token as revoked_token, token,
|
||||
};
|
||||
|
||||
pub fn token(tag: char) -> String {
|
||||
format!("kvt_{}", tag.to_string().repeat(43))
|
||||
}
|
||||
|
||||
pub fn admin_token() -> String {
|
||||
token('A')
|
||||
}
|
||||
pub fn member_token() -> String {
|
||||
token('M')
|
||||
}
|
||||
pub fn revoked_token() -> String {
|
||||
token('R')
|
||||
}
|
||||
|
||||
/// A data directory removed on drop.
|
||||
pub struct TempDir(pub PathBuf);
|
||||
|
||||
impl TempDir {
|
||||
pub fn new() -> Self {
|
||||
let dir = std::env::temp_dir().join(format!("kvitt-test-{}", Uuid::now_v7()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
Self(dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TempDir {
|
||||
fn drop(&mut self) {
|
||||
let _ = std::fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
/// The member's revoked device (the id of the shared harness's old admin device).
|
||||
pub const REVOKED_DEVICE_ID: &str = crate::common::ADMIN_OLD_DEVICE_ID;
|
||||
|
||||
pub struct TestApp {
|
||||
/// Kept alive so the directory is removed when the app is dropped.
|
||||
|
||||
@@ -103,7 +103,7 @@ fn every_example_round_trips() {
|
||||
round_trip::<ErrorBody>(&name, &example);
|
||||
checked += 1;
|
||||
} else if dir_name == "push" {
|
||||
// FCM payloads (SPEC §14.6) are built by M4's sender, not by a wire type of this crate.
|
||||
// FCM payloads (SPEC §14.6) are built by the push sender, not by a wire type of this crate.
|
||||
continue;
|
||||
} else if check(&name, &example) {
|
||||
checked += 1;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! The real audit writer (SPEC §14.4.7, FR-12) through M3's recurring generation and through sync: source, actor,
|
||||
//! The real audit writer (SPEC §14.4.7, FR-12) through the recurring generation and through sync: source, actor,
|
||||
//! times and append-only entries.
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn generation_writes_recurring_audit_with_server_now_and_no_device() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use serde_json::json;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
const T0: &str = "2026-10-17T10:00:00.000Z";
|
||||
|
||||
@@ -24,7 +24,7 @@ fn delete(id: &str, at: &str) -> serde_json::Value {
|
||||
#[tokio::test]
|
||||
async fn both_users_edit_offline_the_later_updated_at_wins_in_either_arrival_order() {
|
||||
for later_arrives_first in [true, false] {
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
let id = new_id();
|
||||
push_one(&app, &admin_token(), exp(expense(&id, T0))).await;
|
||||
let anna = exp(edit(&id, "2026-10-17T11:00:00.000Z", "Anna's"));
|
||||
@@ -50,7 +50,7 @@ async fn both_users_edit_offline_the_later_updated_at_wins_in_either_arrival_ord
|
||||
#[tokio::test]
|
||||
async fn one_deletes_the_other_edits_delete_wins_regardless_of_order_and_timestamps() {
|
||||
// Delete first, then a newer edit: rejected deleted.
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
let id = new_id();
|
||||
push_one(&app, &admin_token(), exp(expense(&id, T0))).await;
|
||||
accepted(
|
||||
@@ -69,7 +69,7 @@ async fn one_deletes_the_other_edits_delete_wins_regardless_of_order_and_timesta
|
||||
.await;
|
||||
rejected(&r, "deleted");
|
||||
// Edit first, then an older delete: applied.
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
let id = new_id();
|
||||
push_one(&app, &admin_token(), exp(expense(&id, T0))).await;
|
||||
accepted(
|
||||
@@ -94,7 +94,7 @@ async fn one_deletes_the_other_edits_delete_wins_regardless_of_order_and_timesta
|
||||
|
||||
#[tokio::test]
|
||||
async fn same_user_on_two_devices_is_lww_per_row() {
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
// A second, non-revoked device of the admin.
|
||||
let second = token('S');
|
||||
app.insert_device(
|
||||
@@ -129,7 +129,7 @@ async fn same_user_on_two_devices_is_lww_per_row() {
|
||||
#[tokio::test]
|
||||
async fn an_expense_in_an_account_deleted_meanwhile_is_reference_not_found_archived_category_accepted()
|
||||
{
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
tombstone(&app, "accounts", ACCOUNT_1).await;
|
||||
rejected(
|
||||
&push_one(&app, &admin_token(), exp(expense(&new_id(), T0))).await,
|
||||
@@ -150,7 +150,7 @@ async fn an_expense_in_an_account_deleted_meanwhile_is_reference_not_found_archi
|
||||
async fn epoch_recovery_re_push_of_identical_rows_comes_back_stale_with_equal_content() {
|
||||
// §14.4.8: after a restore the phone re-pushes everything; identical rows are stale with current equal to the
|
||||
// pushed client-writable fields (the phone treats that as accepted), missing rows are inserted.
|
||||
let app = crate::m4::app().await;
|
||||
let app = crate::sync_fixtures::app().await;
|
||||
let kept = new_id();
|
||||
let entity = expense(&kept, T0);
|
||||
push_one(&app, &admin_token(), exp(entity.clone())).await;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
use kvitt_server::push::{PushHub, PushType};
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_on_device_a_pull_on_device_b_row_audit_and_one_push() {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! M4 sync integration tests: every rule of SPEC §14.4.2 (pull) and §14.4.4 (push steps 1 to 12), each rejection
|
||||
//! Sync integration tests: every rule of SPEC §14.4.2 (pull) and §14.4.4 (push steps 1 to 12), each rejection
|
||||
//! reason of §14.4.6, the §9.10 conflict cases, batch limits, `cursor_ahead`, the epoch, the 14-day offline scenario
|
||||
//! (AC-15.6), the audit writer (§14.4.7), `rotate-epoch` (§10.4), the end-to-end path device A push → device B pull,
|
||||
//! and the orchestrator-assigned `serve` fixes (ConnectInfo for the rate limiter, `/i/` HTML 404).
|
||||
//! and `serve` itself (ConnectInfo for the rate limiter, `/i/` HTML 404, the shutdown drain).
|
||||
//!
|
||||
//! Reuses M3's harness (`tests/core_api/common.rs`) with the real `SqlAuditWriter` swapped in (`m4.rs`).
|
||||
//! The harness is `tests/common/`, with the real `SqlAuditWriter` swapped in by `sync_fixtures`.
|
||||
|
||||
#[path = "../core_api/common.rs"]
|
||||
#[path = "../common/mod.rs"]
|
||||
pub mod common;
|
||||
pub mod m4;
|
||||
#[path = "../common/sync_fixtures.rs"]
|
||||
pub mod sync_fixtures;
|
||||
|
||||
mod audit;
|
||||
mod conflicts;
|
||||
@@ -16,9 +17,3 @@ mod offline;
|
||||
mod pull;
|
||||
mod push_rules;
|
||||
mod serve;
|
||||
|
||||
impl Default for common::TempDir {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use kvitt_server::time::{format_ts, parse_ts};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn fourteen_days_offline_200_changes_all_sync() {
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum::http::Method;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
const T1: &str = "2026-10-17T11:00:00.000Z";
|
||||
const ARRAYS: [&str; 7] = [
|
||||
|
||||
@@ -5,7 +5,7 @@ use axum::http::Method;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::common::*;
|
||||
use crate::m4::*;
|
||||
use crate::sync_fixtures::*;
|
||||
|
||||
/// A change applied to a pushed entity.
|
||||
type Mutator = Box<dyn Fn(&mut Value)>;
|
||||
|
||||
Reference in New Issue
Block a user