import { mkdtemp, readFile, readdir, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import sharp from "sharp"; import { reportDataSchema } from "../src/report-schema.js"; import { ReportStore } from "../src/report-store.js"; import { discoverChrome, renderReportHtml, renderReportPng, type ChromeRunner } from "../src/report-renderer.js"; import { sampleReport } from "./report-fixture.js"; const cssPath = path.resolve(import.meta.dirname, "../../../.agents/skills/swads-daily-report/assets/report.css"); const signal = () => new AbortController().signal; const root = () => mkdtemp(path.join(tmpdir(), "swads-report-test-")); describe("normalized report", () => { it("keeps null, multiplier and percentage units and exactly three recommendations", () => { const data = reportDataSchema.parse(sampleReport()); expect(data.summary).toMatchObject({ ctr_percent: null, roas: 6, simple_roi_percent: 500 }); for (const mutate of [(d: ReturnType) => { d.recommendations.pop(); }, (d: ReturnType) => { d.meta.report_date = "2026-02-30"; }, (d: ReturnType) => { d.summary.spend = 0; }, (d: ReturnType) => { d.manual_confirmations = []; }]) { const invalid = sampleReport(); mutate(invalid); expect(reportDataSchema.safeParse(invalid).success).toBe(false); } expect(reportDataSchema.safeParse({ ...data, outputPath: "/tmp/x" }).success).toBe(false); expect(reportDataSchema.safeParse({ ...data, summary: { ...data.summary, roas: "6x" } }).success).toBe(false); }); it("escapes all dynamic HTML and excludes external resources", async () => { const data = sampleReport(); data.meta.account_name = '