Files
finance-app/src/__tests__/integration/helpers.ts
T
siddharthd 1296555f17 test: add unit and integration test suites
- Extract evaluateCondition + rule types into src/lib/rules.ts for testability
- 48 unit tests for evaluateCondition (all fields/operators) and formatCategory
- 21 integration tests for getTransactions filters and getParticipantBalances
- Vitest configs for unit (vitest.config.ts) and integration (vitest.integration.config.ts)
- setup-test-db.sh creates personal_test DB from production schema via pg_dump
- Use vi.doMock + dynamic import pattern to isolate test DB from Prisma singleton
2026-04-01 19:59:29 +11:00

84 lines
2.5 KiB
TypeScript

import { Pool } from "pg";
import { vi } from "vitest";
export function createPool() {
return new Pool({ connectionString: process.env.DATABASE_URL });
}
// Replace the app's Prisma-based queryRaw with a direct pg call so that
// tests don't depend on Prisma's singleton picking up the right DATABASE_URL.
// Must be called BEFORE dynamically importing any module that uses @/lib/db.
// Uses vi.doMock (not vi.mock) so it is NOT hoisted and CAN close over `p`.
export function mockDbWithPool(p: Pool) {
vi.resetModules(); // clear module cache so fresh imports pick up the mock
vi.doMock("@/lib/db", () => ({
queryRaw: async (sql: string, params: unknown[] = []) => {
const result = await p.query(sql, params);
return result.rows;
},
prisma: p,
}));
}
/** Wipe all data tables and restart sequences between tests. */
export async function resetDB(pool: Pool) {
await pool.query(`
TRUNCATE
split_payments,
transaction_splits,
transaction_tags,
transaction_overrides,
rule_apply_runs,
rules,
budgets,
account_owner_mappings,
transactions,
statements,
tags,
participants
RESTART IDENTITY CASCADE
`);
}
/** Seed two participants and return their IDs. */
export async function seedParticipants(pool: Pool, names: [string, string] = ["Alice", "Bob"]) {
const r1 = await pool.query(
`INSERT INTO participants (name) VALUES ($1) RETURNING id`,
[names[0]]
);
const r2 = await pool.query(
`INSERT INTO participants (name) VALUES ($1) RETURNING id`,
[names[1]]
);
return { ownerId: r1.rows[0].id as number, otherId: r2.rows[0].id as number };
}
/** Insert a manual transaction (no statement) and return its id. */
export async function insertTransaction(
pool: Pool,
ownerId: number,
overrides: {
description?: string;
amount?: number;
category?: string;
transaction_type?: string;
transaction_date?: string;
merchant_normalized?: string;
} = {}
): Promise<number> {
const r = await pool.query(
`INSERT INTO transactions
(owner_id, statement_id, transaction_date, description, amount, transaction_type, category, row_index)
VALUES ($1, NULL, $2, $3, $4, $5, $6, 0) RETURNING id`,
[
ownerId,
overrides.transaction_date ?? "2024-06-15",
overrides.description ?? "Test transaction",
overrides.amount ?? 100,
overrides.transaction_type ?? "debit",
overrides.category ?? "other",
]
);
return r.rows[0].id as number;
}