From 6bbf2d486c67de55e2d8ee8f09c1e6ecfb3796d9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 19:02:54 +0000 Subject: [PATCH] test: expand presence and maintenance warning coverage --- src/infra/session-maintenance-warning.test.ts | 93 ++++++++++++++++--- src/infra/system-presence.test.ts | 36 +++++++ 2 files changed, 117 insertions(+), 12 deletions(-) diff --git a/src/infra/session-maintenance-warning.test.ts b/src/infra/session-maintenance-warning.test.ts index f0e9590c572..f4c2e0757a1 100644 --- a/src/infra/session-maintenance-warning.test.ts +++ b/src/infra/session-maintenance-warning.test.ts @@ -1,3 +1,4 @@ +import { randomUUID } from "node:crypto"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ @@ -37,6 +38,26 @@ vi.mock("./system-events.js", () => ({ 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; @@ -68,18 +89,9 @@ describe("deliverSessionMaintenanceWarning", () => { }); it("forwards session context to outbound delivery", async () => { - await deliverSessionMaintenanceWarning({ - cfg: {}, - sessionKey: "agent:main:main", - entry: {} as never, - warning: { - activeSessionKey: "agent:main:main", - pruneAfterMs: 1_000, - maxEntries: 100, - wouldPrune: true, - wouldCap: false, - } as never, - }); + const params = createParams({ sessionKey: "agent:main:main" }); + + await deliverSessionMaintenanceWarning(params); expect(mocks.deliverOutboundPayloads).toHaveBeenCalledWith( expect.objectContaining({ @@ -90,4 +102,61 @@ describe("deliverSessionMaintenanceWarning", () => { ); 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:") }), + ); + }); }); diff --git a/src/infra/system-presence.test.ts b/src/infra/system-presence.test.ts index 10929115605..02369a18355 100644 --- a/src/infra/system-presence.test.ts +++ b/src/infra/system-presence.test.ts @@ -61,6 +61,42 @@ describe("system-presence", () => { expect(entry?.scopes).toEqual(expect.arrayContaining(["operator.admin", "system.run"])); }); + it("parses node presence text and normalizes the update key", () => { + const update = updateSystemPresence({ + text: "Node: Relay-Host (10.0.0.9) · app 2.1.0 · last input 7s ago · mode ui · reason beacon", + instanceId: " Mixed-Case-Node ", + }); + + expect(update.key).toBe("mixed-case-node"); + expect(update.changedKeys).toEqual(["host", "ip", "version", "mode", "reason"]); + expect(update.next).toMatchObject({ + host: "Relay-Host", + ip: "10.0.0.9", + version: "2.1.0", + lastInputSeconds: 7, + mode: "ui", + reason: "beacon", + text: "Node: Relay-Host (10.0.0.9) · app 2.1.0 · last input 7s ago · mode ui · reason beacon", + }); + }); + + it("drops blank role and scope entries while keeping fallback text", () => { + const deviceId = randomUUID(); + + upsertPresence(deviceId, { + deviceId, + host: "relay-host", + mode: "operator", + roles: [" operator ", "", " "], + scopes: ["operator.admin", "", " "], + }); + + const entry = listSystemPresence().find((candidate) => candidate.deviceId === deviceId); + expect(entry?.roles).toEqual(["operator"]); + expect(entry?.scopes).toEqual(["operator.admin"]); + expect(entry?.text).toBe("Node: relay-host · mode operator"); + }); + it("prunes stale non-self entries after TTL", () => { vi.useFakeTimers(); vi.setSystemTime(Date.now());