/** Tests inbound envelope formatting, timestamps, and sender labels. */ import { describe, expect, it, vi } from "vitest"; import { formatAgentEnvelope, formatAgentEnvelopeTimestamp, formatInboundEnvelope, resolveEnvelopeFormatOptions, } from "./envelope.js"; describe("formatAgentEnvelope", () => { it("includes channel, from, ip, host, and timestamp", () => { const ts = Date.UTC(2025, 0, 2, 3, 4, 5); // 2025-01-02T03:04:05Z const body = formatAgentEnvelope({ channel: "WebChat", from: "user1", host: "mac-mini", ip: "10.0.0.5", timestamp: ts, envelope: { timezone: "utc" }, body: "hello", }); expect(body).toBe("[WebChat user1 mac-mini 10.0.0.5 Thu 2025-01-02T03:04:05Z] hello"); }); it("formats timestamps in local timezone by default", () => { const ts = Date.UTC(2025, 0, 2, 3, 4); const expectedTimestamp = formatAgentEnvelopeTimestamp(ts, { timezone: "local" }); const body = formatAgentEnvelope({ channel: "WebChat", timestamp: ts, body: "hello", }); expect(body).toBe(`[WebChat ${expectedTimestamp}] hello`); }); it("formats timestamps in UTC across date boundaries", () => { for (const [timestamp, timezone, expected] of [ ["2025-01-02T03:04:05Z", "utc", "Thu 2025-01-02T03:04:05Z"], ["2024-12-31T23:59:59Z", "utc", "Tue 2024-12-31T23:59:59Z"], ["2025-01-01T00:00:00Z", "gmt", "Wed 2025-01-01T00:00:00Z"], ["2025-01-03T00:00:00Z", "utc", "Fri 2025-01-03T00:00:00Z"], ] as const) { expect( formatAgentEnvelope({ channel: "WebChat", timestamp: new Date(timestamp), envelope: { timezone }, body: "hello", }), ).toBe(`[WebChat ${expected}] hello`); } }); it("preserves UTC fallback and recovery when Intl is replaced or fails", () => { const render = (timestamp: string) => formatAgentEnvelope({ channel: "WebChat", timestamp: new Date(timestamp), envelope: { timezone: "utc" }, body: "hello", }); expect(render("2025-01-02T03:04:05Z")).toBe("[WebChat Thu 2025-01-02T03:04:05Z] hello"); const OriginalDateTimeFormat = Intl.DateTimeFormat; let constructorFails = true; let newFormatterFails = true; let formatFails = false; const replacement = vi .spyOn(Intl, "DateTimeFormat") .mockImplementation(function (locales, options) { if (constructorFails) { throw new Error("weekday constructor unavailable"); } const formatter = new OriginalDateTimeFormat(locales, options); const nativeFormat = formatter.format.bind(formatter); const failedAtCreation = newFormatterFails; Object.defineProperty(formatter, "format", { value: (date?: Date | number) => { if (failedAtCreation || formatFails) { throw new Error("weekday formatting unavailable"); } return `replacement-${nativeFormat(date)}`; }, }); return formatter; }); try { expect(render("2025-01-02T03:04:05Z")).toBe("[WebChat 2025-01-02T03:04:05Z] hello"); constructorFails = false; expect(render("2025-01-02T03:04:05Z")).toBe("[WebChat 2025-01-02T03:04:05Z] hello"); newFormatterFails = false; expect(render("2025-01-03T00:00:00Z")).toBe( "[WebChat replacement-Fri 2025-01-03T00:00:00Z] hello", ); formatFails = true; expect(render("2025-01-03T00:00:00Z")).toBe("[WebChat 2025-01-03T00:00:00Z] hello"); formatFails = false; expect(render("2025-01-01T00:00:00Z")).toBe( "[WebChat replacement-Wed 2025-01-01T00:00:00Z] hello", ); } finally { replacement.mockRestore(); } expect(render("2025-01-01T00:00:00Z")).toBe("[WebChat Wed 2025-01-01T00:00:00Z] hello"); }); it("formats timestamps in user timezone when configured", () => { const ts = Date.UTC(2025, 0, 2, 3, 4, 5); // 2025-01-02T03:04:05Z (04:04:05 CET) const body = formatAgentEnvelope({ channel: "WebChat", timestamp: ts, envelope: { timezone: "user", userTimezone: "Europe/Vienna" }, body: "hello", }); expect(body).toMatch(/\[WebChat Thu 2025-01-02 04:04:05 [^\]]+\] hello/); }); it("falls back to the host timezone for an invalid configured user timezone", () => { const ts = Date.UTC(2025, 0, 2, 3, 4, 5); const options = resolveEnvelopeFormatOptions({ agents: { defaults: { userTimezone: "Not/A_Timezone" } }, }); expect(options.timezone).toBe("local"); expect(formatAgentEnvelopeTimestamp(ts, options)).toBe( formatAgentEnvelopeTimestamp(ts, { timezone: "local" }), ); }); it("keeps the UTC fallback for an invalid explicit timezone option", () => { const ts = Date.UTC(2025, 0, 2, 3, 4, 5); expect(formatAgentEnvelopeTimestamp(ts, { timezone: "Not/A_Timezone" })).toBe( formatAgentEnvelopeTimestamp(ts, { timezone: "utc" }), ); }); it("omits timestamps when configured", () => { const ts = Date.UTC(2025, 0, 2, 3, 4); const body = formatAgentEnvelope({ channel: "WebChat", timestamp: ts, envelope: { includeTimestamp: false }, body: "hello", }); expect(body).toBe("[WebChat] hello"); }); it("handles missing optional fields", () => { const body = formatAgentEnvelope({ channel: "Telegram", body: "hi" }); expect(body).toBe("[Telegram] hi"); }); it("formats the Unix epoch timestamp", () => { const body = formatAgentEnvelope({ channel: "WebChat", timestamp: 0, envelope: { timezone: "utc" }, body: "hello", }); expect(body).toBe("[WebChat Thu 1970-01-01T00:00:00Z] hello"); }); }); describe("formatInboundEnvelope", () => { it("prefixes sender for non-direct chats", () => { const body = formatInboundEnvelope({ channel: "Discord", from: "Guild #general", body: "hi", chatType: "channel", senderLabel: "Alice", }); expect(body).toBe("[Discord Guild #general] Alice: hi"); }); it("uses sender fields when senderLabel is missing", () => { const body = formatInboundEnvelope({ channel: "Signal", from: "Signal Group id:123", body: "ping", chatType: "group", sender: { name: "Bob", id: "42" }, }); expect(body).toBe("[Signal Signal Group id:123] Bob (42): ping"); }); it("prefixes direct messages with the header sender", () => { const body = formatInboundEnvelope({ channel: "iMessage", from: "+1555", body: "hello", chatType: "direct", senderLabel: "Alice", }); expect(body).toBe("[iMessage +1555] +1555: hello"); }); it("uses display text for direct body prefixes when from includes an id", () => { const body = formatInboundEnvelope({ channel: "Telegram", from: "Alice id:123", body: "hello", chatType: "direct", }); expect(body).toBe("[Telegram Alice id:123] Alice: hello"); }); it("uses a stable direct body prefix when id display text contains a colon", () => { const body = formatInboundEnvelope({ channel: "Telegram", from: "Ops: Alice id:123", body: "/status", chatType: "direct", }); expect(body).toBe("[Telegram Ops: Alice id:123] (sender): /status"); }); it("uses a stable direct body prefix when from is an opaque id label", () => { const body = formatInboundEnvelope({ channel: "LINE", from: "user:U123", body: "hello", chatType: "direct", }); expect(body).toBe("[LINE user:U123] (sender): hello"); }); it("includes elapsed time when previousTimestamp is provided", () => { const now = Date.now(); const twoMinutesAgo = now - 2 * 60 * 1000; const body = formatInboundEnvelope({ channel: "Telegram", from: "Alice", body: "follow-up message", timestamp: now, previousTimestamp: twoMinutesAgo, chatType: "direct", envelope: { includeTimestamp: false }, }); expect(body).toContain("Alice +2m"); expect(body).toContain("follow-up message"); }); it("omits elapsed time when disabled", () => { const now = Date.now(); const body = formatInboundEnvelope({ channel: "Telegram", from: "Alice", body: "follow-up message", timestamp: now, previousTimestamp: now - 2 * 60 * 1000, chatType: "direct", envelope: { includeElapsed: false, includeTimestamp: false }, }); expect(body).toBe("[Telegram Alice] Alice: follow-up message"); }); it("prefixes DM body with (self) when fromMe is true", () => { const body = formatInboundEnvelope({ channel: "WhatsApp", from: "+1555", body: "outbound msg", chatType: "direct", fromMe: true, }); expect(body).toBe("[WhatsApp +1555] (self): outbound msg"); }); it("does not prefix group messages with (self) when fromMe is true", () => { const body = formatInboundEnvelope({ channel: "WhatsApp", from: "Family Chat", body: "hello", chatType: "group", senderLabel: "Alice", fromMe: true, }); expect(body).toBe("[WhatsApp Family Chat] Alice: hello"); }); it("uses fixed envelope options while preserving the user timezone", () => { const options = resolveEnvelopeFormatOptions({ agents: { defaults: { envelopeTimezone: "user", envelopeTimestamp: "off", envelopeElapsed: "off", userTimezone: "Europe/Vienna", }, }, }); expect(options).toEqual({ timezone: "Europe/Vienna", includeTimestamp: true, includeElapsed: true, userTimezone: "Europe/Vienna", }); }); });