import { describe, expect, it } from "vitest"; import { buildWall } from "../src/ops-summary.js"; import type { OpsSummary } from "@app/shared-types"; function baseSummary(overrides: Partial = {}): OpsSummary { return { app: { app: "cibello", generated_at: new Date().toISOString() }, ekonomi: { mrr: 0, intakt_24h: 0, intakt_7d: 0, valuta: "SEK" }, anvandare: { aktiva_nu: 0, dau: 0, mau: 0, nya_24h: 0, nya_7d: 0 }, prenumerationer: { trial_aktiva: 0, trial_konverterade_24h: 0, trial_konverterade_7d: 0, konverteringsgrad_30d: null, betalande: 0, avslutade_24h: 0, }, butik: { butik: null, all_fields_phase_2: null }, feedback: { oppna: 0, nya_24h: 0, senaste: null }, ai_scan: { scans_24h: 0, scans_7d: 0, lyckandegrad_24h: null, latens_p50_ms: null, latens_p95_ms: null, felfrekvens_24h: null, senaste_fel: null, kostnad_usd_mikrocent_manad: 0, kostnad_usd_mikrocent_24h: null, kostnad_usd_mikrocent_24h_uppskattad: false, budget_andel: null, as_of: new Date().toISOString(), }, aktivering: { scan_inom_24h_andel: null, lagad_maltid_inom_7d_andel: null, hushall_andra_medlem_aktiv_andel: null, retention_d1: null, retention_d7: null, retention_d30: null, }, engagemang: { lagade_maltider_24h: 0, lagade_maltider_7d: 0, rek_ctr_7d: null, puffar_skickade_24h: 0, puffar_atgardsgrad_7d: null, }, betalning: { failed_nu: 0, grace_period_nu: 0, trials_utgar_48h: 0, aterbetalningar_7d: null, chargebacks_7d: null, }, jobb: { vantande: 0, aktiva: 0, misslyckade_24h: 0, aldsta_vantande_sek: null, workers_ok: true, }, sakerhet: { allergen_invariant_brott: 0, overifierade_visade: 0, food_safety_lint_avvisade_7d: null, senaste_kontroll: null, }, wall: { boards: [] }, as_of: new Date().toISOString(), ...overrides, }; } /** * Wall-testet verifierar att buildWall är härlett uteslutande ur summary-värden. * Inga hårdkodade tal får förekomma. */ describe("buildWall", () => { it("formaterar MRR i öre till kr med mellanslag", () => { const summary = baseSummary({ ekonomi: { ...baseSummary().ekonomi, mrr: 1234567 } }); const tile = buildWall(summary).boards[0]!.tiles.find((t) => t.label === "MRR")!; expect(tile.value).toBe("12 346 kr"); expect(tile.tone).toBe("ok"); }); it("MRR null blir — med warn", () => { const summary = baseSummary({ ekonomi: { ...baseSummary().ekonomi, mrr: null } }); const tile = buildWall(summary).boards[0]!.tiles.find((t) => t.label === "MRR")!; expect(tile.value).toBe("—"); expect(tile.tone).toBe("warn"); }); it("aktiva nu och betalande visas som heltal", () => { const summary = baseSummary({ anvandare: { ...baseSummary().anvandare, aktiva_nu: 42 }, prenumerationer: { ...baseSummary().prenumerationer, betalande: 7 }, }); const tiles = buildWall(summary).boards[0]!.tiles; expect(tiles.find((t) => t.label === "Aktiva nu")!.value).toBe("42"); expect(tiles.find((t) => t.label === "Betalande")!.value).toBe("7"); }); it("trial-konvertering blir procent; null blir —/warn", () => { const withValue = baseSummary({ prenumerationer: { ...baseSummary().prenumerationer, konverteringsgrad_30d: 0.3149 }, }); const tileValue = buildWall(withValue).boards[0]!.tiles.find((t) => t.label === "Trial-konv")!; expect(tileValue.value).toBe("31 %"); expect(tileValue.tone).toBe("ok"); const withNull = baseSummary(); const tileNull = buildWall(withNull).boards[0]!.tiles.find((t) => t.label === "Trial-konv")!; expect(tileNull.value).toBe("—"); expect(tileNull.tone).toBe("warn"); }); it("workers ok → OK; nere → NERE/warn", () => { const ok = buildWall(baseSummary()).boards[0]!.tiles.find((t) => t.label === "Workers")!; expect(ok.value).toBe("OK"); expect(ok.tone).toBe("ok"); const nere = buildWall( baseSummary({ jobb: { ...baseSummary().jobb, workers_ok: false } }), ).boards[0]!.tiles.find((t) => t.label === "Workers")!; expect(nere.value).toBe("NERE"); expect(nere.tone).toBe("warn"); }); it("allergen-brott 0 är ok; >0 varnar", () => { const zero = buildWall(baseSummary()).boards[0]!.tiles.find( (t) => t.label === "Allergen-brott", )!; expect(zero.value).toBe("0"); expect(zero.tone).toBe("ok"); const brott = buildWall( baseSummary({ sakerhet: { ...baseSummary().sakerhet, allergen_invariant_brott: 3 } }), ).boards[0]!.tiles.find((t) => t.label === "Allergen-brott")!; expect(brott.value).toBe("3"); expect(brott.tone).toBe("warn"); }); it("alla sju tiles finns i rätt ordning", () => { const tiles = buildWall(baseSummary()).boards[0]!.tiles; expect(tiles.map((t) => t.label)).toEqual([ "MRR", "Aktiva nu", "Betalande", "Trial-konv", "AI-budget", "Workers", "Allergen-brott", ]); }); it("ändrat summary-värde ändrar tile (inga hårdkodade tal)", () => { const first = buildWall( baseSummary({ anvandare: { ...baseSummary().anvandare, aktiva_nu: 1 } }), ); const second = buildWall( baseSummary({ anvandare: { ...baseSummary().anvandare, aktiva_nu: 2 } }), ); expect(first.boards[0]!.tiles.find((t) => t.label === "Aktiva nu")!.value).toBe("1"); expect(second.boards[0]!.tiles.find((t) => t.label === "Aktiva nu")!.value).toBe("2"); }); });