import { describe, expect, it, beforeAll, afterAll, vi } from "vitest"; import IORedis from "ioredis"; import BRAND from "../../../brand.config.json" with { type: "json" }; import { pushOpsSummaryToEoc } from "../src/processors/eoc-push.js"; const redis = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", { maxRetriesPerRequest: null, }); describe("pushOpsSummaryToEoc", () => { beforeAll(async () => { await redis.set( "ops:summary:cache", JSON.stringify({ app: { app: "cibello", generated_at: new Date().toISOString() }, as_of: new Date().toISOString() }), "EX", 60, ); }); afterAll(async () => { await redis.del("ops:summary:cache"); await redis.quit(); }); it("maps feedback.created_at to skapad in the POST body", async () => { process.env.EOC_URL = "https://eoc.example.com"; process.env.EOC_PUSH_TOKEN = "tok"; const ts = "2026-08-11T10:00:00.000Z"; await redis.set( "ops:summary:cache", JSON.stringify({ as_of: ts, app: { app: "cibello" }, feedback: { oppna: 1, senaste: [{ typ: "bug", rubrik: "exempel", status: "oppen", created_at: ts }], }, }), "EX", 60, ); const originalFetch = globalThis.fetch; const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204 })); globalThis.fetch = fetchMock as unknown as typeof fetch; try { const result = await pushOpsSummaryToEoc(redis); expect(result.ok).toBe(true); expect(fetchMock).toHaveBeenCalledTimes(1); const init = fetchMock.mock.calls[0][1] as RequestInit; const body = JSON.parse(init.body as string); expect(body.feedback.senaste[0].skapad).toBe(ts); expect(body.feedback.senaste[0]).not.toHaveProperty("created_at"); expect(body.as_of).toBe(ts); } finally { globalThis.fetch = originalFetch; } }); it("returns disabled result when EOC_URL is missing", async () => { delete process.env.EOC_URL; process.env.EOC_PUSH_TOKEN = "tok"; const result = await pushOpsSummaryToEoc(redis); expect(result.ok).toBe(false); expect(result.error).toMatch(/EOC_URL or EOC_PUSH_TOKEN not configured/); }); it("returns disabled result when EOC_PUSH_TOKEN is missing", async () => { process.env.EOC_URL = "https://eoc.example.com"; delete process.env.EOC_PUSH_TOKEN; const result = await pushOpsSummaryToEoc(redis); expect(result.ok).toBe(false); expect(result.error).toMatch(/EOC_URL or EOC_PUSH_TOKEN not configured/); }); it("returns cache-miss when cache is empty", async () => { process.env.EOC_URL = "https://eoc.example.com"; process.env.EOC_PUSH_TOKEN = "tok"; await redis.del("ops:summary:cache"); const result = await pushOpsSummaryToEoc(redis); expect(result.ok).toBe(false); expect(result.error).toMatch(/cache miss/); }); it("transforms wall titles and tones for EOC", async () => { process.env.EOC_URL = "https://eoc.example.com"; process.env.EOC_PUSH_TOKEN = "tok"; await redis.set( "ops:summary:cache", JSON.stringify({ as_of: new Date().toISOString(), app: { app: "cibello" }, jobb: { workers_ok: false }, sakerhet: { allergen_invariant_brott: 1 }, wall: { boards: [ { title: "{brand} Overview", tiles: [ { label: "MRR", value: "0 kr", tone: "ok" }, { label: "Workers", value: "NERE", tone: "warn" }, { label: "Allergen-brott", value: "1", tone: "warn" }, ], }, ], }, }), "EX", 60, ); const originalFetch = globalThis.fetch; const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204 })); globalThis.fetch = fetchMock as unknown as typeof fetch; try { const result = await pushOpsSummaryToEoc(redis); expect(result.ok).toBe(true); const init = fetchMock.mock.calls[0][1] as RequestInit; const body = JSON.parse(init.body as string); const board = body.wall.boards[0]; expect(board.title).toBe(`${BRAND.name} Overview`); expect(board.title).not.toMatch(/\{brand\}/); const tones = board.tiles.map((t: { tone?: string }) => t.tone); for (const tone of tones) { expect(["good", "warn", "crit", ""]).toContain(tone); } expect(board.tiles.find((t: { label: string }) => t.label === "MRR")?.tone).toBe("good"); expect(board.tiles.find((t: { label: string }) => t.label === "Workers")?.tone).toBe("crit"); expect(board.tiles.find((t: { label: string }) => t.label === "Allergen-brott")?.tone).toBe("crit"); } finally { globalThis.fetch = originalFetch; } }); it("does not throw when payload mapping fails", async () => { process.env.EOC_URL = "https://eoc.example.com"; process.env.EOC_PUSH_TOKEN = "tok"; await redis.set( "ops:summary:cache", JSON.stringify({ as_of: new Date().toISOString(), app: { app: "cibello" }, wall: { boards: [{ title: 123, tiles: "nej" }] }, }), "EX", 60, ); await expect(pushOpsSummaryToEoc(redis)).resolves.toEqual({ ok: false, status: null, error: expect.stringMatching(/payload mapping failed/), }); }); });