From b724b3e9328fd9b8c394d8011de7f5d5ff924eba Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 12 Jun 2026 20:51:49 -0400 Subject: [PATCH] fix(09): WR-03/IN-03/IN-04 add drain-listener teardown, test-only __resetDrainState, and remove stale RED @ts-ignore --- apps/api/src/broker/outboxWorker.ts | 12 +++++++ apps/api/tests/broker/outboxWorker.test.ts | 39 +++++++++++++--------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/apps/api/src/broker/outboxWorker.ts b/apps/api/src/broker/outboxWorker.ts index d326c64..6667ffd 100644 --- a/apps/api/src/broker/outboxWorker.ts +++ b/apps/api/src/broker/outboxWorker.ts @@ -873,6 +873,18 @@ export function stopOutboxTrigger(): void { unsubscribeDrain = null; } +/** + * IN-03: test-only reset for the module-level concurrency flags. + * isDraining/drainRequested are the entire concurrency contract for the + * single-process deployment and are NOT touched by vi.resetAllMocks(). Tests call + * this in beforeEach so each test starts from a known-quiescent state instead of + * relying on the previous test having drained cleanly. Not for production use. + */ +export function __resetDrainState(): void { + isDraining = false; + drainRequested = false; +} + /** * Starts the 15-second background outbox drain schedule (polling fallback, D-08). * Call once at API startup (wired in index.ts beside startBrokerPoller). diff --git a/apps/api/tests/broker/outboxWorker.test.ts b/apps/api/tests/broker/outboxWorker.test.ts index 75dbef5..f324ddd 100644 --- a/apps/api/tests/broker/outboxWorker.test.ts +++ b/apps/api/tests/broker/outboxWorker.test.ts @@ -1,5 +1,5 @@ /** - * RED test scaffold: broker/outboxWorker.ts — outbox state machine (D-04, D-07, D-08) + * broker/outboxWorker.ts — outbox state machine (D-04, D-07, D-08) * * Behaviors under test: * 1. runOutboxDrain transitions pending→done on mock 204 response @@ -8,17 +8,19 @@ * on mock 500 (transient error) * 4. runOutboxDrain transitions pending→dead when attemptCount reaches MAX_ATTEMPTS * 5. Edit-as-move (D-04): create row processed BEFORE the linked delete row (groupId) - * - * These tests FAIL (RED) because broker/outboxWorker.ts does not exist yet. - * They will turn GREEN in Plan 03-03 when the implementation is added. + * 6. scheduleOutboxDrain trigger wiring (D-09): signal → prompt drain, trailing re-drain collapse */ -import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest'; +import { describe, it, expect, vi, beforeEach, beforeAll, afterAll } from 'vitest'; -// This import fails (RED) — broker/outboxWorker.ts does not exist yet. -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore intentional RED import -import { runOutboxDrain, assembleRruleString, scheduleOutboxDrain, initOutboxTrigger } from '../../src/broker/outboxWorker.js'; +import { + runOutboxDrain, + assembleRruleString, + scheduleOutboxDrain, + initOutboxTrigger, + stopOutboxTrigger, + __resetDrainState, +} from '../../src/broker/outboxWorker.js'; import { signalOutboxDrain } from '../../src/lib/outboxTrigger.js'; // ── Drizzle DB mock ──────────────────────────────────────────────────────── @@ -667,8 +669,7 @@ describe('runOutboxDrain — fail closed on bad credentials (CR-03) + backoff in }); // ─── D-06: assembleRruleString unit tests ───────────────────────────────────── -// These tests import the NOT-YET-EXPORTED `assembleRruleString` helper. -// RED: will fail because assembleRruleString is not exported yet. +// Exercise the exported `assembleRruleString` helper. describe('assembleRruleString (D-06)', () => { it('returns base preset unchanged when no bound given', () => { @@ -701,8 +702,8 @@ describe('assembleRruleString (D-06)', () => { }); // ─── D-07: FREQ-persistence regression lock ──────────────────────────────────── -// RED: will fail because the outbox worker does not yet wire recurrenceUntil/recurrenceCount -// and the FREQ-persistence assertion catches the D-07 regression scenario. +// The FREQ-persistence assertion catches the D-07 regression scenario +// (recurrenceUntil/recurrenceCount wiring must not drop the FREQ). describe('FREQ persistence (D-07 regression)', () => { beforeEach(() => { @@ -741,8 +742,6 @@ describe('FREQ persistence (D-07 regression)', () => { // SC-1: signalOutboxDrain() → drain fires promptly (no 15s wait) // D-05: mid-drain signal collapses to exactly one trailing re-drain // D-07: concurrent scheduleOutboxDrain() calls dispatch each row exactly once -// -// RED: these tests fail because scheduleOutboxDrain is not yet exported. describe('scheduleOutboxDrain — trigger wiring (D-09)', () => { // Wire the EventEmitter signal → scheduleOutboxDrain once for this describe block. @@ -752,8 +751,18 @@ describe('scheduleOutboxDrain — trigger wiring (D-09)', () => { initOutboxTrigger(); }); + // WR-03: remove the leaked 'drain' listener so it cannot fire against the mocked + // DB in subsequent describe blocks (fileParallelism:false shares one module instance). + afterAll(() => { + stopOutboxTrigger(); + }); + beforeEach(() => { vi.resetAllMocks(); + // WR-03 / IN-03: module-level isDraining/drainRequested are not reset by + // vi.resetAllMocks(); reset them explicitly so each test starts quiescent + // instead of relying on the previous test having drained cleanly. + __resetDrainState(); mockPendingRows = []; wireMockChain(); });