Simpl-Resultat/src/utils/sqlFilters.test.ts
le king fu 4a93c60ea7 feat(reports): plumb accountIds[] account filter into 7 report services
Give every report service an optional accountIds?: number[] filter that
narrows results to a subset of import sources (transactions.source_id),
matched via a parameterized IN (...) clause — one bound placeholder per
id, never a joined string (CWE-89). New shared inPlaceholders() helper
(src/utils/sqlFilters.ts) generates the placeholder list so the pattern
lives in exactly one place.

New accountIds param: getCompareMonthOverMonth, getCompareYearOverYear,
getBudgetVsActualData, getCartesSnapshot (forwarded to its
getCompareMonthOverMonth + getBudgetVsActualData sub-calls, so the
Cartes dashboard's top-movers and budget-adherence cards respect an
active filter instead of silently ignoring it).

Signature change scalar -> plural: getMonthlyTrends, getCategoryOverTime,
getExpensesByCategory (sourceId?: number -> accountIds?: number[]). No
scalar production caller of these three passed sourceId today, so only
the test call-sites needed updating to the array shape.

Omitted/empty accountIds adds no clause at all, so every service stays
byte-identical to its pre-#273 SQL/results — pinned by a regression test
per service (new budgetService.test.ts / dashboardService.test.ts files,
extended reportService.test.ts / reportService.cartes.test.ts).

This is backend plumbing only: no report page exposes an account filter
control yet (follow-up issues #274-#276 add the shared <FilterPanel> and
wire it into Trends/Compare/Budget).

Resolves #273

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:15:37 -04:00

27 lines
856 B
TypeScript

import { describe, it, expect } from "vitest";
import { inPlaceholders } from "./sqlFilters";
describe("inPlaceholders", () => {
it("returns null for undefined ids (no filter)", () => {
expect(inPlaceholders(undefined, 1)).toBeNull();
});
it("returns null for an empty array (no filter)", () => {
expect(inPlaceholders([], 1)).toBeNull();
});
it("builds a single placeholder for one id", () => {
expect(inPlaceholders([7], 1)).toBe("$1");
});
it("builds one placeholder per id, starting at startIndex", () => {
expect(inPlaceholders([3, 7, 12], 4)).toBe("$4, $5, $6");
});
it("never joins the raw id values into the string (CWE-89 guard)", () => {
const result = inPlaceholders([3, 7], 1);
expect(result).not.toContain("3");
expect(result).not.toContain("7");
expect(result).toBe("$1, $2");
});
});