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>
27 lines
856 B
TypeScript
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");
|
|
});
|
|
});
|