Files
worldmonitor/tests/resilience-freshness.test.mts
Elie Habib cf028b34b2 fix(resilience): address PR #2947 review (3 P2 nits on staleness classifier) (#2953)
Addresses all three Greptile P2 comments on PR #2947:

1. **Redundant `Math.max(0, ...)` guard** (line 97).
   The defensive branch three lines above already rejects null,
   undefined, NaN, and future timestamps, so `nowMs - lastObservedAtMs`
   is guaranteed to be `>= 0` by the time execution reaches the age
   calculation. The `Math.max` was a misleading hint that a negative
   value could arrive. Removed, with a comment explaining why the
   branch above covers the case.

2. **`ageMs` can be `Infinity`, worth documenting on the field.**
   When the defensive branch returns, `ageMs` and
   `ageInCadenceUnits` are both `Number.POSITIVE_INFINITY`, but the
   field type was just `number`, so callers doing arithmetic or
   string formatting on the value (e.g. a "last seen X ago" label)
   would silently emit `Infinity` / `NaN` strings. Added JSDoc to
   both fields explaining the infinity contract and recommending
   `Number.isFinite` before arithmetic.

3. **Missing `ageInCadenceUnits` assertion for defensive cases.**
   The earlier test checked `ageMs === POSITIVE_INFINITY` only for
   the null branch, and never checked `ageInCadenceUnits` at all.
   Hardened the defensive-cases test to pin BOTH fields as
   `Number.POSITIVE_INFINITY` across every branch (null, undefined,
   NaN, future). Coverage went from 1 of 8 field/branch pairs to all
   8. If a future regression drops `ageInCadenceUnits` from the
   defensive return, the test now fails loudly.

Testing:
- npx tsx --test tests/resilience-freshness.test.mts: 10/10 pass
- npm run typecheck: clean

Generated with Claude Opus 4.6 (1M context) via Claude Code
+ Compound Engineering v2.49.0

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 20:04:26 +04:00

183 lines
7.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
AGING_MULTIPLIER,
FRESH_MULTIPLIER,
cadenceUnitMs,
classifyStaleness,
type ResilienceCadence,
} from '../server/_shared/resilience-freshness.ts';
// T1.5 Phase 1 of the country-resilience reference-grade upgrade plan.
//
// Foundation-only slice: these tests pin the staleness classifier so
// T1.6 (widget dimension confidence bar) and the later T1.5 scorer
// propagation pass can consume the classifier with confidence.
describe('resilience freshness classifier (T1.5)', () => {
const NOW = 1_700_000_000_000; // fixed anchor: any arbitrary ms timestamp
const CADENCES: ResilienceCadence[] = ['realtime', 'daily', 'weekly', 'monthly', 'annual'];
it('cadenceUnitMs returns a positive duration for every cadence', () => {
for (const cadence of CADENCES) {
const unit = cadenceUnitMs(cadence);
assert.ok(unit > 0, `${cadence} should have a positive cadence unit`);
}
});
it('cadence units are ordered strictly: realtime < daily < weekly < monthly < annual', () => {
const units = CADENCES.map((c) => cadenceUnitMs(c));
for (let i = 1; i < units.length; i += 1) {
assert.ok(
units[i] > units[i - 1],
`${CADENCES[i]} cadence unit (${units[i]}) should be strictly greater than ${CADENCES[i - 1]} (${units[i - 1]})`,
);
}
});
it('fresh when age is well below FRESH_MULTIPLIER * cadence unit', () => {
for (const cadence of CADENCES) {
const unit = cadenceUnitMs(cadence);
// Age = 0.5 * unit, well under FRESH_MULTIPLIER = 1.5
const result = classifyStaleness({
lastObservedAtMs: NOW - unit * 0.5,
cadence,
nowMs: NOW,
});
assert.equal(result.staleness, 'fresh', `${cadence} at 0.5x should be fresh`);
assert.ok(result.ageInCadenceUnits >= 0 && result.ageInCadenceUnits < FRESH_MULTIPLIER);
}
});
it('aging when age sits between FRESH_MULTIPLIER and AGING_MULTIPLIER', () => {
for (const cadence of CADENCES) {
const unit = cadenceUnitMs(cadence);
// Age = 2 * unit, between FRESH_MULTIPLIER (1.5) and AGING_MULTIPLIER (3)
const result = classifyStaleness({
lastObservedAtMs: NOW - unit * 2,
cadence,
nowMs: NOW,
});
assert.equal(result.staleness, 'aging', `${cadence} at 2x should be aging`);
assert.ok(result.ageInCadenceUnits >= FRESH_MULTIPLIER && result.ageInCadenceUnits < AGING_MULTIPLIER);
}
});
it('stale when age is at or beyond AGING_MULTIPLIER * cadence unit', () => {
for (const cadence of CADENCES) {
const unit = cadenceUnitMs(cadence);
// Age = 5 * unit, well beyond AGING_MULTIPLIER
const result = classifyStaleness({
lastObservedAtMs: NOW - unit * 5,
cadence,
nowMs: NOW,
});
assert.equal(result.staleness, 'stale', `${cadence} at 5x should be stale`);
assert.ok(result.ageInCadenceUnits >= AGING_MULTIPLIER);
}
});
it('stale when lastObservedAtMs is null, undefined, NaN, or in the future', () => {
// Raised in PR #2947 review: pin `ageMs` AND `ageInCadenceUnits` as
// POSITIVE_INFINITY on every defensive branch so a future regression
// that accidentally omits one field from the defensive return is
// caught immediately. The earlier version only checked `ageMs` on
// the null branch and staleness on the rest.
for (const cadence of CADENCES) {
const missingNull = classifyStaleness({ lastObservedAtMs: null, cadence, nowMs: NOW });
assert.equal(missingNull.staleness, 'stale', `${cadence} null should be stale`);
assert.equal(missingNull.ageMs, Number.POSITIVE_INFINITY, `${cadence} null ageMs should be Infinity`);
assert.equal(missingNull.ageInCadenceUnits, Number.POSITIVE_INFINITY, `${cadence} null ageInCadenceUnits should be Infinity`);
const missingUndefined = classifyStaleness({ lastObservedAtMs: undefined, cadence, nowMs: NOW });
assert.equal(missingUndefined.staleness, 'stale', `${cadence} undefined should be stale`);
assert.equal(missingUndefined.ageMs, Number.POSITIVE_INFINITY, `${cadence} undefined ageMs should be Infinity`);
assert.equal(missingUndefined.ageInCadenceUnits, Number.POSITIVE_INFINITY, `${cadence} undefined ageInCadenceUnits should be Infinity`);
const nanResult = classifyStaleness({ lastObservedAtMs: Number.NaN, cadence, nowMs: NOW });
assert.equal(nanResult.staleness, 'stale', `${cadence} NaN should be stale`);
assert.equal(nanResult.ageMs, Number.POSITIVE_INFINITY, `${cadence} NaN ageMs should be Infinity`);
assert.equal(nanResult.ageInCadenceUnits, Number.POSITIVE_INFINITY, `${cadence} NaN ageInCadenceUnits should be Infinity`);
// A timestamp 10 minutes in the future is nonsensical and treated as stale.
const futureResult = classifyStaleness({
lastObservedAtMs: NOW + 10 * 60 * 1000,
cadence,
nowMs: NOW,
});
assert.equal(futureResult.staleness, 'stale', `${cadence} future timestamp should be stale`);
assert.equal(futureResult.ageMs, Number.POSITIVE_INFINITY, `${cadence} future ageMs should be Infinity`);
assert.equal(futureResult.ageInCadenceUnits, Number.POSITIVE_INFINITY, `${cadence} future ageInCadenceUnits should be Infinity`);
}
});
it('defaults to Date.now() when nowMs is omitted', () => {
const now = Date.now();
// A recent observation should always be fresh against real Date.now()
// without specifying nowMs explicitly. Small tolerance for clock drift.
const result = classifyStaleness({
lastObservedAtMs: now - 60_000, // 60 seconds ago
cadence: 'daily',
});
assert.equal(result.staleness, 'fresh');
assert.ok(result.ageMs >= 60_000 && result.ageMs < 120_000);
});
it('exact threshold boundaries (FRESH and AGING multipliers are strict upper bounds for their class)', () => {
const unit = cadenceUnitMs('daily');
// age = FRESH_MULTIPLIER * unit (exactly at boundary) should be aging,
// not fresh, because the comparison is strict `<`.
const atFreshBoundary = classifyStaleness({
lastObservedAtMs: NOW - unit * FRESH_MULTIPLIER,
cadence: 'daily',
nowMs: NOW,
});
assert.equal(atFreshBoundary.staleness, 'aging', 'exact FRESH_MULTIPLIER boundary is aging');
// age = AGING_MULTIPLIER * unit (exactly at boundary) should be stale,
// not aging, because the comparison is strict `<`.
const atAgingBoundary = classifyStaleness({
lastObservedAtMs: NOW - unit * AGING_MULTIPLIER,
cadence: 'daily',
nowMs: NOW,
});
assert.equal(atAgingBoundary.staleness, 'stale', 'exact AGING_MULTIPLIER boundary is stale');
// age = 0 should be fresh.
const atZero = classifyStaleness({
lastObservedAtMs: NOW,
cadence: 'daily',
nowMs: NOW,
});
assert.equal(atZero.staleness, 'fresh', 'zero age is fresh');
});
it('ageMs and ageInCadenceUnits are internally consistent', () => {
const result = classifyStaleness({
lastObservedAtMs: NOW - 36 * 60 * 60 * 1000, // 36 hours ago
cadence: 'daily',
nowMs: NOW,
});
assert.equal(result.ageMs, 36 * 60 * 60 * 1000);
assert.equal(result.ageInCadenceUnits, 1.5);
// 36 hours at daily cadence is exactly FRESH_MULTIPLIER, so aging.
assert.equal(result.staleness, 'aging');
});
it('classifier is pure: same inputs produce same outputs', () => {
const args = {
lastObservedAtMs: NOW - 12 * 60 * 60 * 1000,
cadence: 'daily' as const,
nowMs: NOW,
};
const a = classifyStaleness(args);
const b = classifyStaleness(args);
const c = classifyStaleness({ ...args });
assert.deepEqual(a, b);
assert.deepEqual(a, c);
});
});