From bac00ec73d4f362b1b202a9ad4fa2fb960b18ae0 Mon Sep 17 00:00:00 2001 From: Justice Date: Sun, 28 Jun 2026 11:10:58 +0100 Subject: [PATCH 1/2] fix(daemon): fix #112 daemon alert delivery --- tests/daemon/loop.test.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/daemon/loop.test.ts b/tests/daemon/loop.test.ts index f68dfd13..905c18f4 100644 --- a/tests/daemon/loop.test.ts +++ b/tests/daemon/loop.test.ts @@ -532,4 +532,36 @@ describe("daemon loop", () => { expect(onCycle).toHaveBeenNthCalledWith(2, result2, undefined); }); }); + // ========================================================================= + // 10. ALERT DISPATCH + // ========================================================================= + describe("Alert dispatch", () => { + it("triggers alert dispatch during daemon cycle", async () => { + mockRunMonitorCycle.mockResolvedValue(makeCycleResult()); + + await startDaemon(db, "testnet", { intervalMs: 5000 }); + + expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(1); + expect(mockDeliverPendingAlerts).toHaveBeenCalledWith(db, "testnet"); + + await vi.advanceTimersByTimeAsync(5000); + expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(2); + expect(mockDeliverPendingAlerts).toHaveBeenLastCalledWith(db, "testnet"); + }); + + it("survives and logs if alert dispatch throws unexpectedly", async () => { + mockRunMonitorCycle.mockResolvedValue(makeCycleResult()); + mockDeliverPendingAlerts.mockRejectedValueOnce(new Error("Dispatcher exploded")); + + // startDaemon should not throw, and the cycle should continue + await expect(startDaemon(db, "testnet", { intervalMs: 5000 })).resolves.not.toThrow(); + + expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(1); + + // The next interval should still trigger, showing the daemon didn't crash + await vi.advanceTimersByTimeAsync(5000); + expect(mockRunMonitorCycle).toHaveBeenCalledTimes(2); + expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(2); + }); + }); }); From ae10ec39ca82fd6e545075185e53aa344ea51028 Mon Sep 17 00:00:00 2001 From: Justice Date: Sun, 28 Jun 2026 12:27:24 +0100 Subject: [PATCH 2/2] fix(daemon): cover delivery logging in tests --- tests/daemon/loop.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/daemon/loop.test.ts b/tests/daemon/loop.test.ts index 905c18f4..b04d9efc 100644 --- a/tests/daemon/loop.test.ts +++ b/tests/daemon/loop.test.ts @@ -9,6 +9,18 @@ const mockRunMonitorCycle = vi.fn(); const mockDeliverPendingAlerts = vi.fn(); const mockVacuumDatabase = vi.fn(); const mockAggregateDailyCostSnapshots = vi.fn(); +const mockDaemonLogger = vi.hoisted(() => { + const logger = { + debug: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + fatal: vi.fn(), + child: vi.fn(), + }; + logger.child.mockReturnValue(logger); + return logger; +}); vi.mock("../../src/core/monitor.js", () => ({ runMonitorCycle: (...args: unknown[]) => mockRunMonitorCycle(...args), @@ -34,6 +46,10 @@ vi.mock("../../src/db/repositories.js", async (importOriginal) => { }; }); +vi.mock("../../src/logging/index.js", () => ({ + getLogger: () => mockDaemonLogger, +})); + import { startDaemon, stopDaemon } from "../../src/daemon/loop.js"; // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -538,11 +554,20 @@ describe("daemon loop", () => { describe("Alert dispatch", () => { it("triggers alert dispatch during daemon cycle", async () => { mockRunMonitorCycle.mockResolvedValue(makeCycleResult()); + mockDeliverPendingAlerts.mockResolvedValue({ + attempted: 2, + delivered: 2, + failed: 0, + errors: [], + }); await startDaemon(db, "testnet", { intervalMs: 5000 }); expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(1); expect(mockDeliverPendingAlerts).toHaveBeenCalledWith(db, "testnet"); + expect(mockDaemonLogger.info).toHaveBeenCalledWith( + "Delivery — attempted: 2, delivered: 2, failed: 0", + ); await vi.advanceTimersByTimeAsync(5000); expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(2); @@ -557,6 +582,10 @@ describe("daemon loop", () => { await expect(startDaemon(db, "testnet", { intervalMs: 5000 })).resolves.not.toThrow(); expect(mockDeliverPendingAlerts).toHaveBeenCalledTimes(1); + expect(mockDaemonLogger.error).toHaveBeenCalledWith( + "deliverPendingAlerts threw unexpectedly", + expect.any(Error), + ); // The next interval should still trigger, showing the daemon didn't crash await vi.advanceTimersByTimeAsync(5000);