From 3e574553e5f86682457520f6d702e3440852ded9 Mon Sep 17 00:00:00 2001 From: Param Harrison Date: Fri, 9 Oct 2026 01:07:04 +0300 Subject: [PATCH 1/2] factory: claim #62 by runner-55570-1791497224125 From e9bf299a20c122ce89dad90cacf003632e019d05 Mon Sep 17 00:00:00 2001 From: Param Harrison Date: Fri, 9 Oct 2026 01:09:58 +0300 Subject: [PATCH 2/2] factory: build #62 --- src/csv.ts | 12 ++++-------- tests/csv.test.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/csv.test.ts diff --git a/src/csv.ts b/src/csv.ts index bbb9f3a..dad968b 100644 --- a/src/csv.ts +++ b/src/csv.ts @@ -1,4 +1,5 @@ import type { Database } from "bun:sqlite"; +import { formatCents } from "./money/cents.ts"; interface ExpenseRow { createdAt: string; @@ -26,19 +27,14 @@ function fetchExpenseRows(db: Database, groupId: string): ExpenseRow[] { } /** - * Build the CSV export for a group's expenses. - * - * SEEDED DEFECT (issue #3): the amount column prints the raw integer cents - * (e.g. "1000") instead of a formatted amount ("10.00"). Baseline tests - * only check the header row and the row count, not the amount column's - * formatting, so this ships undetected. The fix should reuse - * `formatCents` from src/money (see .claude/skills/handling-money). + * Build the CSV export for a group's expenses. Amounts stay integer cents + * until this output boundary, where `formatCents` renders them as "10.00". */ export function exportCsv(db: Database, groupId: string): string { const rows = fetchExpenseRows(db, groupId); const header = "date,payer,description,amount\n"; const lines = rows.map( - (r) => `${r.createdAt},${csvEscape(r.payerName)},${csvEscape(r.description)},${r.amountCents}`, + (r) => `${r.createdAt},${csvEscape(r.payerName)},${csvEscape(r.description)},${formatCents(r.amountCents)}`, ); return header + lines.join("\n") + (lines.length > 0 ? "\n" : ""); } diff --git a/tests/csv.test.ts b/tests/csv.test.ts new file mode 100644 index 0000000..356d24c --- /dev/null +++ b/tests/csv.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, test } from "bun:test"; +import { Database } from "bun:sqlite"; +import { initSchema } from "../src/db.ts"; +import { seedDemoData } from "../src/seed.ts"; +import { exportCsv } from "../src/csv.ts"; +import { formatCents } from "../src/money/index.ts"; + +function setupDb(): Database { + const db = new Database(":memory:"); + initSchema(db); + seedDemoData(db); + return db; +} + +function storedCents(db: Database, groupId: string): number[] { + const rows = db + .query("SELECT amount_cents as amountCents FROM expenses WHERE group_id = ? ORDER BY created_at ASC") + .all(groupId) as { amountCents: number }[]; + return rows.map((r) => r.amountCents); +} + +describe("exportCsv", () => { + test("exportCsv formats the amount column with formatCents", () => { + const db = setupDb(); + const lines = exportCsv(db, "flat-4b").trimEnd().split("\n").slice(1); + const amounts = lines.map((line) => line.split(",").at(-1)); + const cents = storedCents(db, "flat-4b"); + + expect(amounts).toEqual(cents.map(formatCents)); + for (const amount of amounts) { + expect(amount).toMatch(/^-?\d+\.\d{2}$/); + } + expect(amounts[0]).toBe("45.00"); + }); + + test("exportCsv keeps the header and one row per expense", () => { + const db = setupDb(); + const csv = exportCsv(db, "flat-4b"); + const lines = csv.trimEnd().split("\n"); + + expect(lines[0]).toBe("date,payer,description,amount"); + expect(lines.length - 1).toBe(storedCents(db, "flat-4b").length); + expect(csv.endsWith("\n")).toBe(true); + }); +});