import { randomUUID } from "node:crypto"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ resolveSessionAgentId: vi.fn(() => "agent-from-key"), resolveSessionDeliveryTarget: vi.fn(() => ({ channel: "whatsapp", to: "+15550001", accountId: "acct-1", threadId: "thread-1", })), normalizeMessageChannel: vi.fn((channel: string) => channel), isDeliverableMessageChannel: vi.fn(() => true), deliverOutboundPayloads: vi.fn(async () => []), enqueueSystemEvent: vi.fn(), })); vi.mock("../agents/agent-scope.js", () => ({ resolveSessionAgentId: mocks.resolveSessionAgentId, })); vi.mock("../utils/message-channel.js", () => ({ normalizeMessageChannel: mocks.normalizeMessageChannel, isDeliverableMessageChannel: mocks.isDeliverableMessageChannel, })); vi.mock("./outbound/targets.js", () => ({ resolveSessionDeliveryTarget: mocks.resolveSessionDeliveryTarget, })); vi.mock("./outbound/deliver.js", () => ({ deliverOutboundPayloads: mocks.deliverOutboundPayloads, })); vi.mock("./system-events.js", () => ({ enqueueSystemEvent: mocks.enqueueSystemEvent, })); const { deliverSessionMaintenanceWarning } = await import("./session-maintenance-warning.js"); function createParams( overrides: Partial[0]> = {}, ): Parameters[0] { const sessionKey = overrides.sessionKey ?? `agent:${randomUUID()}:main`; return { cfg: {}, sessionKey, entry: {} as never, warning: { activeSessionKey: sessionKey, pruneAfterMs: 1_000, maxEntries: 100, wouldPrune: true, wouldCap: false, ...(overrides.warning as object), } as never, ...overrides, }; } describe("deliverSessionMaintenanceWarning", () => { let prevVitest: string | undefined; let prevNodeEnv: string | undefined; beforeEach(() => { prevVitest = process.env.VITEST; prevNodeEnv = process.env.NODE_ENV; delete process.env.VITEST; process.env.NODE_ENV = "development"; mocks.resolveSessionAgentId.mockClear(); mocks.resolveSessionDeliveryTarget.mockClear(); mocks.normalizeMessageChannel.mockClear(); mocks.isDeliverableMessageChannel.mockClear(); mocks.deliverOutboundPayloads.mockClear(); mocks.enqueueSystemEvent.mockClear(); }); afterEach(() => { if (prevVitest === undefined) { delete process.env.VITEST; } else { process.env.VITEST = prevVitest; } if (prevNodeEnv === undefined) { delete process.env.NODE_ENV; } else { process.env.NODE_ENV = prevNodeEnv; } }); it("forwards session context to outbound delivery", async () => { const params = createParams({ sessionKey: "agent:main:main" }); await deliverSessionMaintenanceWarning(params); expect(mocks.deliverOutboundPayloads).toHaveBeenCalledWith( expect.objectContaining({ channel: "whatsapp", to: "+15550001", session: { key: "agent:main:main", agentId: "agent-from-key" }, }), ); expect(mocks.enqueueSystemEvent).not.toHaveBeenCalled(); }); it("suppresses duplicate warning contexts for the same session", async () => { const params = createParams(); await deliverSessionMaintenanceWarning(params); await deliverSessionMaintenanceWarning(params); expect(mocks.deliverOutboundPayloads).toHaveBeenCalledTimes(1); }); it("falls back to a system event when the last target is not deliverable", async () => { mocks.resolveSessionDeliveryTarget.mockReturnValueOnce({ channel: "debug", to: "+15550001", accountId: "acct-1", threadId: "thread-1", }); mocks.isDeliverableMessageChannel.mockReturnValueOnce(false); await deliverSessionMaintenanceWarning( createParams({ warning: { pruneAfterMs: 3_600_000, maxEntries: 10, wouldPrune: false, wouldCap: true, } as never, }), ); expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled(); expect(mocks.enqueueSystemEvent).toHaveBeenCalledWith( expect.stringContaining("most recent 10 sessions"), expect.objectContaining({ sessionKey: expect.stringContaining("agent:") }), ); }); it("skips warning delivery in test mode", async () => { process.env.NODE_ENV = "test"; await deliverSessionMaintenanceWarning(createParams()); expect(mocks.resolveSessionDeliveryTarget).not.toHaveBeenCalled(); expect(mocks.deliverOutboundPayloads).not.toHaveBeenCalled(); expect(mocks.enqueueSystemEvent).not.toHaveBeenCalled(); }); it("enqueues a system event when outbound delivery fails", async () => { mocks.deliverOutboundPayloads.mockRejectedValueOnce(new Error("boom")); await deliverSessionMaintenanceWarning(createParams()); expect(mocks.enqueueSystemEvent).toHaveBeenCalledWith( expect.stringContaining("older than 1 second"), expect.objectContaining({ sessionKey: expect.stringContaining("agent:") }), ); }); });