/// import { afterEach, describe, expect, test } from "bun:test"; import { EXPENSE_CATEGORIES as domainExpenseCategories, addCalendarDays, calendarDateFromInstant, formatElapsedSeconds as formatDomainElapsedSeconds, getEffectiveInvoiceStatus, } from "@beenvoice/domain"; import { fetchAuthCapabilities } from "../lib/auth-capabilities"; import { EXPENSE_CATEGORIES as appExpenseCategories } from "../lib/expense-categories"; import { getInvoiceStatus } from "../lib/invoice-status"; import { generateInvoiceNumber as generateMobileInvoiceNumber } from "../lib/invoice-number"; import { formatElapsedSeconds as formatAppElapsedSeconds } from "../lib/time-clock"; import { generateInvoiceNumber as generateWebInvoiceNumber } from "../../web/src/lib/draft-invoice"; import { safeCallbackPath } from "../../web/src/lib/safe-callback-url"; import { normalizeOptionalId } from "../../web/src/lib/time-clock"; const originalFetch = globalThis.fetch; afterEach(() => { globalThis.fetch = originalFetch; }); describe("auth contract smoke checks", () => { test("mobile reads the server capabilities payload", async () => { globalThis.fetch = (async (input) => { expect(String(input)).toBe("https://beenvoice.app/api/auth/capabilities"); return Response.json({ authentik: true, signupsDisabled: true }); }) as typeof fetch; await expect( fetchAuthCapabilities("https://beenvoice.app/"), ).resolves.toEqual({ authentik: true, signupsDisabled: true, }); }); test("web auth callbacks stay on the same origin", () => { expect(safeCallbackPath("/dashboard/invoices?status=sent")).toBe( "/dashboard/invoices?status=sent", ); expect(safeCallbackPath("https://attacker.example")).toBe("/dashboard"); expect(safeCallbackPath("//attacker.example")).toBe("/dashboard"); }); }); describe("invoice parity", () => { test("web and mobile use the device-local date in invoice numbers", () => { const lateLocalEvening = new Date(2026, 7, 16, 23, 30, 0, 123); expect(generateMobileInvoiceNumber(lateLocalEvening)).toStartWith( "INV-20260816-", ); expect(generateWebInvoiceNumber(lateLocalEvening)).toStartWith( "INV-20260816-", ); }); test("web and mobile agree on draft, paid, sent, and overdue states", () => { const timeZone = "America/New_York"; const today = calendarDateFromInstant(new Date(), timeZone); const yesterday = addCalendarDays(today, -1); const fixtures = [ { stored: "draft" as const, dueDate: yesterday, expected: "draft" as const, }, { stored: "paid" as const, dueDate: yesterday, expected: "paid" as const, }, { stored: "sent" as const, dueDate: today, expected: "sent" as const }, { stored: "sent" as const, dueDate: yesterday, expected: "overdue" as const, }, ]; for (const fixture of fixtures) { expect( getEffectiveInvoiceStatus(fixture.stored, fixture.dueDate, timeZone), ).toBe(fixture.expected); expect( getInvoiceStatus({ status: fixture.stored, dueDate: fixture.dueDate, createdBy: { timeZone }, }), ).toBe(fixture.expected); } }); }); describe("timer parity", () => { test("elapsed time formatting is identical", () => { for (const seconds of [0, 59, 60, 3_661, 86_399]) { expect(formatAppElapsedSeconds(seconds)).toBe( formatDomainElapsedSeconds(seconds), ); } }); test("server normalizes optional client identifiers", () => { expect(normalizeOptionalId(undefined)).toBeNull(); expect(normalizeOptionalId(" ")).toBeNull(); expect(normalizeOptionalId(" client-1 ")).toBe("client-1"); }); }); describe("expense parity", () => { test("web and mobile expose the same category vocabulary", () => { expect([...appExpenseCategories]).toEqual([...domainExpenseCategories]); }); });