feat(09-01): add scheduleOutboxDrain, drainRequested, initOutboxTrigger; route setInterval through wrapper
- Add import { onOutboxDrain } from outboxTrigger.js
- Add let drainRequested = false (D-05 trailing-re-drain flag)
- Export scheduleOutboxDrain(): void — isDraining guard + drainRequested loop (D-05/T-09-01)
drainRequested=false reset precedes recursive call (Pitfall 3)
errors caught via .catch to prevent crash (D-02/T-09-03)
- Export initOutboxTrigger(): void — registers onOutboxDrain(() => scheduleOutboxDrain())
- startOutboxWorker setInterval body: scheduleOutboxDrain() replaces runOutboxDrain().catch()
15 * 1000 interval unchanged (D-08)
- runOutboxDrain body/isDraining guard/finally unchanged (D-02/D-07)
- Fix trigger-wiring tests: add beforeAll(initOutboxTrigger) to wire EventEmitter listener;
fix Test C mock to return empty rows on trailing drain (correct D-07 behaviour)
- 30/30 outboxWorker tests GREEN; tsc --noEmit clean
This commit is contained in:
@@ -13,12 +13,12 @@
|
||||
* They will turn GREEN in Plan 03-03 when the implementation is added.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, beforeAll } 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 } from '../../src/broker/outboxWorker.js';
|
||||
import { runOutboxDrain, assembleRruleString, scheduleOutboxDrain, initOutboxTrigger } from '../../src/broker/outboxWorker.js';
|
||||
import { signalOutboxDrain } from '../../src/lib/outboxTrigger.js';
|
||||
|
||||
// ── Drizzle DB mock ────────────────────────────────────────────────────────
|
||||
@@ -745,6 +745,13 @@ describe('FREQ persistence (D-07 regression)', () => {
|
||||
// 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.
|
||||
// initOutboxTrigger registers the 'drain' listener that connects signalOutboxDrain()
|
||||
// to scheduleOutboxDrain(). Called once (not per-test) to avoid listener accumulation.
|
||||
beforeAll(() => {
|
||||
initOutboxTrigger();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
mockPendingRows = [];
|
||||
@@ -818,6 +825,8 @@ describe('scheduleOutboxDrain — trigger wiring (D-09)', () => {
|
||||
|
||||
// Test C (SC-4 / D-07): two concurrent scheduleOutboxDrain() calls dispatch each row
|
||||
// exactly once — the second call is a no-op via the isDraining guard.
|
||||
// The trailing re-drain (triggered by drainRequested) finds 0 pending rows so
|
||||
// createCalendarEvent is called exactly once total.
|
||||
it('D-07: two concurrent scheduleOutboxDrain() calls invoke createCalendarEvent exactly once', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
@@ -829,14 +838,20 @@ describe('scheduleOutboxDrain — trigger wiring (D-09)', () => {
|
||||
setTimeout(() => resolve(makeResponse(201)), 20),
|
||||
),
|
||||
);
|
||||
mockPendingRows = [makeRow({ id: 1 })];
|
||||
const row = makeRow({ id: 1 });
|
||||
// First pending-rows query returns the row; subsequent queries return empty
|
||||
// (simulates: drain 1 processes and removes the row; trailing drain finds nothing)
|
||||
mockWherePending
|
||||
.mockImplementationOnce(() => Promise.resolve([row]))
|
||||
.mockImplementation(() => Promise.resolve([]));
|
||||
|
||||
// Both calls are synchronous — second must no-op via isDraining guard
|
||||
// Both calls are synchronous — second sees isDraining=true and sets drainRequested=true
|
||||
scheduleOutboxDrain();
|
||||
scheduleOutboxDrain();
|
||||
|
||||
await vi.runAllTimersAsync();
|
||||
|
||||
// Drain 1 dispatched the row once; trailing drain found 0 rows → createCalendarEvent once total
|
||||
expect(createCalendarEvent).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
|
||||
Reference in New Issue
Block a user