/** * earth-now.co * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/counter/test/property.test.ts * Purpose: Property-based tests (fast-check) — monotonicity, continuity, anchor identity, determinism */ import { describe, expect, it } from "vitest"; import fc from "fast-check"; import { type CounterModel, counterValue } from "../src/index"; const T0 = "2026-01-01T00:00:00.000Z"; const t0 = Date.parse(T0); function model(rateFn: CounterModel["rateFn"], anchorValue = 0): CounterModel { return { metricId: "prop_metric", anchorValue, anchorTime: T0, rateFn, observedAt: T0, sourceId: "test_source", modelVersion: "test-v1", displayHints: { decimals: 0, unit: "units" }, }; } describe("property: cumulative monotonicity", () => { it("non-negative linear/piecewise rates never go backwards", () => { fc.assert( fc.property( fc.array(fc.double({ min: 0, max: 1e6, noNaN: true }), { minLength: 1, maxLength: 5 }), fc.integer({ min: 0, max: 86_400_000 }), fc.integer({ min: 0, max: 86_400_000 }), (rates, dtA, dtB) => { const segments = rates.map((perSecond, i) => ({ from: new Date(t0 + i * 3_600_000).toISOString(), perSecond, })); const m = model({ kind: "piecewise", segments }); const [lo, hi] = dtA <= dtB ? [dtA, dtB] : [dtB, dtA]; expect(counterValue(m, t0 + hi)).toBeGreaterThanOrEqual( counterValue(m, t0 + lo) - 1e-9, ); }, ), ); }); it("monotone spline knots produce monotone values (no overshoot, ever)", () => { fc.assert( fc.property( fc.array(fc.double({ min: 0.001, max: 1e6, noNaN: true }), { minLength: 2, maxLength: 8, }), fc.integer({ min: 0, max: 999 }), (increments, sample) => { let v = 0; const knots: Array<[string, number]> = increments.map((inc, i) => { v += inc; return [new Date(t0 + i * 86_400_000).toISOString(), v]; }); const m = model({ kind: "spline", knots }, knots[0]![1]); const span = (knots.length - 1) * 86_400_000; const tA = t0 + (span * sample) / 1000; const tB = t0 + (span * Math.min(sample + 1, 1000)) / 1000; expect(counterValue(m, tB)).toBeGreaterThanOrEqual(counterValue(m, tA) - 1e-6); }, ), ); }); }); describe("property: continuity at piecewise junctions", () => { it("value is continuous through every segment boundary", () => { fc.assert( fc.property( fc.array(fc.double({ min: -1e3, max: 1e3, noNaN: true }), { minLength: 2, maxLength: 6, }), (rates) => { const segments = rates.map((perSecond, i) => ({ from: new Date(t0 + i * 60_000).toISOString(), perSecond, })); const m = model({ kind: "piecewise", segments }); for (let i = 1; i < rates.length; i++) { const tj = t0 + i * 60_000; const before = counterValue(m, tj - 1); const after = counterValue(m, tj + 1); // Max drift across 2 ms is bounded by max |rate| * 2 ms. expect(Math.abs(after - before)).toBeLessThanOrEqual(1e3 * 0.002 + 1e-9); } }, ), ); }); }); describe("property: anchor identity & determinism", () => { it("value(anchorTime) === anchorValue for every kind", () => { fc.assert( fc.property( fc.double({ min: -1e9, max: 1e9, noNaN: true }), fc.double({ min: -1e3, max: 1e3, noNaN: true }), (anchorValue, perSecond) => { for (const rateFn of [ { kind: "linear", perSecond } as const, { kind: "seasonal", base: perSecond, harmonics: [{ period: "year", order: 1, amplitude: 1, phase: 0.5 }], } as const, ]) { const m = model(rateFn, anchorValue); expect(counterValue(m, t0)).toBeCloseTo(anchorValue, 6); } }, ), ); }); it("same model + same t ⇒ identical value (repeatable)", () => { fc.assert( fc.property(fc.integer({ min: -1e9, max: 1e9 }), (dt) => { const m = model({ kind: "seasonal", base: 3, harmonics: [ { period: "year", order: 1, amplitude: 2, phase: 1 }, { period: "day", order: 1, amplitude: 0.5, phase: 2 }, ], }); expect(counterValue(m, t0 + dt)).toBe(counterValue(m, t0 + dt)); }), ); }); });