@accounter/server
Version:
Accounter GraphQL server
101 lines (100 loc) • 4.08 kB
TypeScript
/**
* Fixture insertion logic with ordered insertion and rollback support
*
* Inserts fixture data into the database in the correct order to satisfy
* foreign key constraints, with savepoint-based rollback for detailed error messages.
*
* @see packages/server/src/__tests__/helpers/fixture-types.ts for fixture structure
* @see packages/server/src/__tests__/helpers/fixture-validation.ts for validation logic
*/
import type { Client, PoolClient } from 'pg';
import type { Fixture } from './fixture-types.js';
/**
* Custom error for fixture insertion failures
*
* Provides context about which section of the fixture failed to insert
* and includes the underlying database error.
*/
export declare class FixtureInsertionError extends Error {
readonly section: string;
readonly originalError: Error;
readonly context?: Record<string, unknown> | undefined;
constructor(section: string, originalError: Error, context?: Record<string, unknown> | undefined);
toJSON(): {
name: string;
message: string;
section: string;
originalError: {
name: string;
message: string;
};
context: Record<string, unknown> | undefined;
};
}
/**
* ID mapping result from fixture insertion
*
* Maps logical IDs from the fixture to actual database-generated IDs.
* Currently, factories generate UUIDs directly, so this is mostly a pass-through,
* but the infrastructure is in place for future auto-generated IDs.
*/
export type FixtureIdMapping = Map<string, string>;
/**
* Insert a complete fixture into the database
*
* Performs ordered insertion of all fixture entities with savepoint-based
* error handling for clear diagnostics. Insertion order ensures foreign key
* constraints are satisfied:
*
* 1. Businesses (financial entities)
* 2. Tax categories (financial entities)
* 3. Financial accounts (references businesses)
* 4. Charges (references businesses and tax categories)
* 5. Transactions (references charges, accounts, businesses)
* 6. Documents (references charges and businesses)
*
* Each section is wrapped in a SAVEPOINT so that partial failures can be
* rolled back to a known state with clear error context.
*
* **Transaction Handling:**
* - Must be called within an existing transaction (client has active BEGIN)
* - Creates savepoints for each section (SAVEPOINT section_name)
* - On section error, rolls back to savepoint (ROLLBACK TO SAVEPOINT section_name)
* - Does NOT commit or rollback the outer transaction
*
* **Validation:**
* - Validates fixture before any insertion (assertValidFixture)
* - Checks referential integrity (charge IDs, business IDs, etc.)
* - Validates required fields
*
* @param client - PostgreSQL client (PoolClient or standalone Client) within an active transaction
* @param fixture - Complete fixture to insert
* @returns Promise resolving to ID mapping (fixture ID → database ID)
* @throws {Error} If fixture validation fails (via assertValidFixture)
* @throws {FixtureInsertionError} If any insertion section fails
*
* @remarks
* Type Safety: Accepts both PoolClient and Client to support both test transactions
* (pool.connect()) and standalone connections (new pg.Client()) used in seed scripts.
*
* @example
* ```typescript
* import { withTestTransaction } from './test-transaction.js';
* import { insertFixture } from './fixture-loader.js';
* import { expenseScenarioA } from '../fixtures/expenses/expense-scenario-a.js';
*
* it('should insert expense scenario', () =>
* withTestTransaction(pool, async (client) => {
* const idMap = await insertFixture(client, expenseScenarioA);
*
* // Verify insertion
* const result = await client.query(
* 'SELECT * FROM accounter_schema.charges WHERE id = $1',
* [expenseScenarioA.charges!.charges[0].id!]
* );
* expect(result.rows).toHaveLength(1);
* })
* );
* ```
*/
export declare function insertFixture(client: PoolClient | Client, fixture: Fixture, adminBusinessId?: string): Promise<FixtureIdMapping>;