Skip to content

Worker tests: hold D417 and D124 to the clock the code reads (#1342) - #1378

Merged
guillaumelauzier merged 1 commit into
mainfrom
agent/claude/1342-midnight-date-tests
Oct 7, 2026
Merged

guillaumelauzier merged 1 commit into
mainfrom
agent/claude/1342-midnight-date-tests

Conversation

@guillaumelauzier

Copy link
Copy Markdown
Contributor

Objective

Two Worker test files went wrong when a run crossed midnight UTC: D417 failed and D124 stopped catching the bug it guards. PR #1329's test:drift (23:58 to 00:02 UTC) failed on D417. This PR holds each file to the clock its code reads. Test files only: no route change, no decision, no migration.

Closes #1342

Implementation

  • entity_obligations_d417.test.ts. TODAY was a module constant, fixed when the file loaded, while legal_entities.ts reads new Date() on every request.
    • A beforeEach now freezes the JS Date at the real instant each test starts (mock.timers.enable({ apis: ['Date'] })), and an afterEach resets it.
    • today() and plusDays() read that clock when called.
    • So the fixture's dates and the route's today() agree, even if midnight passes during a test.
    • Only Date is mocked; timers and SQLite's clock are untouched. Every assertion is unchanged.
  • expiry_gate_datetime_d124.test.ts. expiredIso pins the spent row to SQLite's date when the row is written; the gate reads datetime('now') when it runs.
    • Across midnight, that row is on yesterday's date, where even the broken bare comparison is right. The tests still passed, but they no longer tested anything.
    • The six behavioural tests now run through onOneUtcDay. It records each expired fixture's day, re-reads SQLite's date after the last assertion, and runs a body that straddled midnight once more on the new day.
    • If the date moves during both runs, the test fails.
    • _timeFixture.mjs is not touched; it is outside this task's files.

Files changed

  • cloudflare-worker/test/entity_obligations_d417.test.ts
  • cloudflare-worker/test/expiry_gate_datetime_d124.test.ts

Testing

  • npm run build, then npm run test:drift > drift.log 2>&1; echo EXIT=$? → EXIT=0 (Worker 5,994, frontend 5,151, retention 127).
  • The clock across midnight, with faketime (libfaketime), which moves both the JS clock and SQLite's.
    • D417, started at 23:59:50 to 23:59:59 UTC at ×10 speed, so midnight falls between the file's tests:

      • before the fix, starts at 23:59:54, :55 and :56 failed 3, 4 and 5 tests (days_left, resolved_on, as_of);
      • after it, every start passes 15 of 15.
      TZ=UTC faketime -f '@2026-10-07 23:59:55 x10' node --experimental-strip-types --no-warnings \
        --import ./cloudflare-worker/test/_ts-loader.mjs --test cloudflare-worker/test/entity_obligations_d417.test.ts
    • D124, started at 23:59:59 UTC. A preload shim holds the first expired-fixture read for 1.2 s, so the first test writes its spent row on 10-07 and runs the gate on 10-08 (the shim logs fixture read 2026-10-07 23:59:59, now 2026-10-08 00:00:00).

      Gate Old test New test
      fixed 7 of 7 pass 7 of 7 pass
      broken (bare expires_at > datetime('now') in the magic-link gate) only the static shape test fails; the behavioural test misses it the behavioural test and the shape test fail
      The shim
      import { DatabaseSync } from 'node:sqlite';
      const orig = DatabaseSync.prototype.prepare;
      let held = false;
      DatabaseSync.prototype.prepare = function (sql, ...rest) {
        const stmt = orig.call(this, sql, ...rest);
        if (!held && /SELECT date\('now'\) AS day, datetime\('now'\) AS now/.test(sql)) {
          const get = stmt.get.bind(stmt);
          stmt.get = (...a) => {
            const row = get(...a);
            held = true;
            Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1200);
            return row;
          };
        }
        return stmt;
      };

      Run with TZ=UTC faketime -f '@2026-10-07 23:59:59' node … --import ./midnight-shim.mjs --test …d124.test.ts.

  • Mutation checks, both ways:
    • D417:
      • daysUntil off by one: 5 tests fail, at the real time and under the ×10 shim at 23:59:55;
      • the route's today() reading yesterday: 4 tests fail.
      • Restored: 15 of 15 pass. Freezing the clock does not hide a route that reads the wrong day.
    • D124: the bare magic-link comparison fails test 1 (and the shape test), at the real time and across midnight. Restored: 7 of 7 pass.

Risks

  • Test-only. Nothing ships to the Worker.
  • mock.timers with Date is in Node 22 (the CI version). It mocks Date only during each D417 test, and resets after.

Dependencies

Agent

S10 · Claude Code

Review requested

Any agent.

STATUS
task: #1342   slot: S10   agent: claude
branch: agent/claude/1342-midnight-date-tests
state: READY_FOR_REVIEW
files: cloudflare-worker/test/entity_obligations_d417.test.ts, cloudflare-worker/test/expiry_gate_datetime_d124.test.ts
blockers: none
questions: none
tests: npm run build, then npm run test:drift → EXIT=0; faketime across midnight: D417 passes at every start, D124 now catches a broken gate across midnight; mutants caught both ways
pr: (this PR)

🤖 Generated with Claude Code

https://claude.ai/code/session_01VCuRYkf1et4rs1gQ9QWFwR


Generated by Claude Code

entity_obligations_d417.test.ts fixed TODAY when the file loaded, while
legal_entities.ts reads new Date() on every request, so a run that crossed
midnight UTC got days_left off by one. Each test now freezes the JS Date at
the real instant it starts (node:test mock.timers, Date only), and today()
and plusDays() read that clock when called.

expiry_gate_datetime_d124.test.ts pins its spent row to SQLite's date when
the row is written; the gate reads datetime('now') when it runs. Across
midnight the row is on yesterday's date, where even the broken bare
comparison is right, so the behavioural tests stopped catching it. Each now
runs through onOneUtcDay, which re-reads SQLite's date after the last
assertion and runs a body that straddled midnight once more.

Test files only. No route change, no decision, no migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCuRYkf1et4rs1gQ9QWFwR
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Preview: https://studioos-pr-1378.guillaumelauzier.workers.dev (built from 4266a22)

The pull request's SPA build on a Worker with no bindings: pages and deep links work, /api/* is a 404, and nothing here can reach production data. Redeployed on every push; deleted when the PR closes.

@guillaumelauzier
guillaumelauzier marked this pull request as ready for review October 7, 2026 07:10
@guillaumelauzier
guillaumelauzier merged commit 8589a80 into main Oct 7, 2026
21 checks passed
@guillaumelauzier
guillaumelauzier deleted the agent/claude/1342-midnight-date-tests branch October 7, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Worker tests: two date tests fail when a run crosses midnight UTC (D417, D124)

2 participants