UNPKG

@accounter/server

Version:
480 lines (424 loc) • 17.2 kB
/** * Tests for fixture loader insertion logic * * Validates ordered insertion, savepoint rollback, and error handling. */ import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import type { Pool } from 'pg'; import { insertFixture } from './fixture-loader.js'; import { withTestTransaction } from './test-transaction.js'; import { connectTestDb, closeTestDb } from './db-connection.js'; import { qualifyTable } from './test-db-config.js'; import type { Fixture } from './fixture-types.js'; import { createBusiness, createTaxCategory, createFinancialAccount, createCharge, createTransaction, createDocument, } from '../factories/index.js'; import { makeUUID } from '../../demo-fixtures/helpers/deterministic-uuid.js'; describe('Fixture Loader', () => { let pool: Pool; beforeAll(async () => { pool = await connectTestDb(); }); afterAll(async () => { await closeTestDb(); }); describe('insertFixture', () => { it('should insert a complete minimal fixture successfully', () => withTestTransaction(pool, async client => { const businessId = makeUUID('business', 'test-business-1'); const taxCategoryId = makeUUID('tax-category', 'test-tax-cat-1'); const chargeId = makeUUID('charge', 'test-charge-1'); const fixture: Fixture = { businesses: { businesses: [ createBusiness({ id: businessId, name: 'Test Business', ownerId: businessId, }), ], }, taxCategories: { taxCategories: [ createTaxCategory({ id: taxCategoryId, name: 'Test Tax Category 1', ownerId: businessId, }), ], }, charges: { charges: [ createCharge( { owner_id: businessId, tax_category_id: taxCategoryId, }, { id: chargeId }, ), ], }, }; const idMap = await insertFixture(client, fixture); // Set context for RLS to allow reading back the inserted data await client.query("SELECT set_config('app.current_business_id', $1, true)", [businessId]); // Verify ID mapping expect(idMap.size).toBeGreaterThan(0); expect(idMap.get(businessId)).toBe(businessId); expect(idMap.get(taxCategoryId)).toBe(taxCategoryId); expect(idMap.get(chargeId)).toBe(chargeId); // Verify data inserted const businessResult = await client.query( `SELECT * FROM ${qualifyTable('businesses')} WHERE id = $1`, [businessId], ); expect(businessResult.rows).toHaveLength(1); expect(businessResult.rows[0].id).toBe(businessId); expect(businessResult.rows[0].hebrew_name).toBeNull(); const taxCatResult = await client.query( `SELECT * FROM ${qualifyTable('tax_categories')} WHERE id = $1`, [taxCategoryId], ); expect(taxCatResult.rows).toHaveLength(1); expect(taxCatResult.rows[0].id).toBe(taxCategoryId); expect(taxCatResult.rows[0].hashavshevet_name).toBeNull(); const chargeResult = await client.query( `SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [chargeId], ); expect(chargeResult.rows).toHaveLength(1); expect(chargeResult.rows[0].owner_id).toBe(businessId); })); it('should insert fixture with transactions and documents', () => withTestTransaction(pool, async client => { const supplierId = makeUUID('business', 'supplier-1'); const customerId = makeUUID('business', 'customer-1'); const taxCategoryId = makeUUID('tax-category', 'tax-cat-1'); const accountNumber = 'ACC-12345'; const chargeId = makeUUID('charge', 'charge-1'); const transactionId = makeUUID('transaction', 'tx-1'); const documentId = makeUUID('document', 'doc-1'); const ownerId = makeUUID('user', 'admin-user'); const fixture: Fixture = { businesses: { businesses: [ createBusiness({ id: ownerId, name: 'Owner', ownerId }), createBusiness({ id: supplierId, name: 'Supplier Ltd', ownerId }), createBusiness({ id: customerId, name: 'Customer Inc', ownerId }), ], }, taxCategories: { taxCategories: [createTaxCategory({ id: taxCategoryId, name: 'Tax Category 1', ownerId })], }, accounts: { accounts: [ createFinancialAccount({ accountNumber: accountNumber, type: 'BANK_ACCOUNT', ownerId: customerId, }), ], }, charges: { charges: [ createCharge( { owner_id: customerId, tax_category_id: taxCategoryId }, { id: chargeId }, ), ], }, transactions: { transactions: [ createTransaction( { charge_id: chargeId, business_id: supplierId, amount: '-500.00', currency: 'ILS', event_date: '2024-01-15', owner_id: ownerId, }, { id: transactionId, account_id: accountNumber, // Will be resolved to UUID by loader }, ), ], }, documents: { documents: [ createDocument( { charge_id: chargeId, creditor_id: supplierId, debtor_id: customerId, type: 'INVOICE', total_amount: 500.0, currency_code: 'ILS', date: '2024-01-15', owner_id: ownerId, }, { id: documentId }, ), ], }, }; const idMap = await insertFixture(client, fixture); // Set context for RLS to allow reading back the inserted data await client.query("SELECT set_config('app.current_business_id', $1, true)", [customerId]); // Verify all IDs mapped expect(idMap.get(supplierId)).toBe(supplierId); expect(idMap.get(customerId)).toBe(customerId); expect(idMap.get(chargeId)).toBe(chargeId); expect(idMap.get(transactionId)).toBe(transactionId); expect(idMap.get(documentId)).toBe(documentId); // Verify transaction inserted with correct references const txResult = await client.query( `SELECT * FROM ${qualifyTable('transactions')} WHERE id = $1`, [transactionId], ); expect(txResult.rows).toHaveLength(1); expect(txResult.rows[0].charge_id).toBe(chargeId); expect(txResult.rows[0].business_id).toBe(supplierId); expect(txResult.rows[0].amount).toBe('-500.00'); // Verify document inserted with correct references const docResult = await client.query( `SELECT * FROM ${qualifyTable('documents')} WHERE id = $1`, [documentId], ); expect(docResult.rows).toHaveLength(1); expect(docResult.rows[0].charge_id).toBe(chargeId); expect(docResult.rows[0].creditor_id).toBe(supplierId); expect(docResult.rows[0].debtor_id).toBe(customerId); })); it('should handle empty fixture sections gracefully', () => withTestTransaction(pool, async client => { const fixture: Fixture = { businesses: { businesses: [] }, taxCategories: { taxCategories: [] }, }; const idMap = await insertFixture(client, fixture); expect(idMap.size).toBe(0); })); it('should handle fixture with only some sections populated', () => withTestTransaction(pool, async client => { const businessId = makeUUID('business', 'lonely-business'); const fixture: Fixture = { businesses: { businesses: [createBusiness({ id: businessId, name: 'Lonely Business', ownerId: businessId })], }, // No tax categories, accounts, charges, etc. }; await client.query("SELECT set_config('app.current_business_id', $1, true)", [businessId]); const idMap = await insertFixture(client, fixture); expect(idMap.get(businessId)).toBe(businessId); const result = await client.query( `SELECT * FROM ${qualifyTable('businesses')} WHERE id = $1`, [businessId], ); expect(result.rows).toHaveLength(1); })); it('should throw validation error before insertion for invalid FK references', () => withTestTransaction(pool, async client => { const businessId = makeUUID('business', 'test-biz'); const invalidChargeId = makeUUID('charge', 'invalid-charge'); const fixture: Fixture = { businesses: { businesses: [createBusiness({ id: businessId, name: 'Test Biz' })], }, charges: { charges: [ createCharge( { owner_id: 'non-existent-owner', // Invalid FK reference tax_category_id: makeUUID('tax-category', 'tax-cat'), }, { id: invalidChargeId }, ), ], }, }; // Validation should catch this before insertion await expect(insertFixture(client, fixture)).rejects.toThrow(/validation/i); })); it('should throw validation error with details for invalid fixture', () => withTestTransaction(pool, async client => { const fixture: Fixture = { charges: { charges: [ createCharge({ owner_id: 'non-existent-owner', tax_category_id: makeUUID('tax-category', 'tax-cat'), }), ], }, }; try { await insertFixture(client, fixture); expect.fail('Should have thrown validation error'); } catch (error) { expect(error).toBeInstanceOf(Error); const validationError = error as Error; expect(validationError.message).toMatch(/validation/i); expect(validationError.message).toMatch(/non-existent/i); } })); it('should validate fixture before insertion', () => withTestTransaction(pool, async client => { const chargeId = makeUUID('charge', 'orphan-charge'); const fixture: Fixture = { // Missing businesses and tax categories charges: { charges: [ createCharge( { owner_id: makeUUID('business', 'missing-owner'), tax_category_id: makeUUID('tax-category', 'missing-tax'), }, { id: chargeId }, ), ], }, }; await expect(insertFixture(client, fixture)).rejects.toThrow(/validation/i); })); it('should insert multiple entities in correct order', () => withTestTransaction(pool, async client => { const biz1 = makeUUID('business', 'biz-1'); const biz2 = makeUUID('business', 'biz-2'); const tax1 = makeUUID('tax-category', 'tax-1'); const tax2 = makeUUID('tax-category', 'tax-2'); const charge1 = makeUUID('charge', 'charge-1'); const charge2 = makeUUID('charge', 'charge-2'); const fixture: Fixture = { businesses: { businesses: [ createBusiness({ id: biz1, name: 'Business 1', ownerId: biz1 }), createBusiness({ id: biz2, name: 'Business 2', ownerId: biz1 }), ], }, taxCategories: { taxCategories: [ createTaxCategory({ id: tax1, name: 'Tax 1', ownerId: biz1 }), createTaxCategory({ id: tax2, name: 'Tax 2', ownerId: biz2 }), ], }, charges: { charges: [ createCharge({ owner_id: biz1, tax_category_id: tax1 }, { id: charge1 }), createCharge({ owner_id: biz2, tax_category_id: tax2 }, { id: charge2 }), ], }, }; const idMap = await insertFixture(client, fixture); await client.query("SELECT set_config('app.current_business_id', $1, false)", [biz1]); // Force set context to allow reading back the inserted data expect(idMap.size).toBe(6); // 2 businesses + 2 tax cats + 2 charges // Verify all charges reference correct businesses and tax categories const charge1Result = await client.query( `SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [charge1], ); expect(charge1Result.rows[0].owner_id).toBe(biz1); expect(charge1Result.rows[0].tax_category_id).toBe(tax1); await client.query("SELECT set_config('app.current_business_id', $1, false)", [biz2]); const charge2Result = await client.query( `SELECT * FROM ${qualifyTable('charges')} WHERE id = $1`, [charge2], ); expect(charge2Result.rows[0].owner_id).toBe(biz2); expect(charge2Result.rows[0].tax_category_id).toBe(tax2); })); it('should handle ON CONFLICT for idempotent insertion', () => withTestTransaction(pool, async client => { const businessId = makeUUID('business', 'duplicate-biz'); const fixture: Fixture = { businesses: { businesses: [createBusiness({ id: businessId, name: 'Original Name', ownerId: businessId })], }, }; // Insert once await insertFixture(client, fixture); // Insert again (should not error due to ON CONFLICT DO NOTHING) const fixtureModified: Fixture = { businesses: { businesses: [createBusiness({ id: businessId, name: 'Modified Name', ownerId: businessId })], }, }; const idMap = await insertFixture(client, fixtureModified); expect(idMap.get(businessId)).toBe(businessId); // Original data should be preserved (ON CONFLICT DO NOTHING) const result = await client.query( `SELECT * FROM ${qualifyTable('financial_entities')} WHERE id = $1`, [businessId], ); expect(result.rows[0].name).toBe('Original Name'); })); it('should insert transactions with generated source_id', () => withTestTransaction(pool, async client => { const businessId = makeUUID('business', 'biz-tx'); const taxCatId = makeUUID('tax-category', 'tax-tx'); const chargeId = makeUUID('charge', 'charge-tx'); const accountNumber = 'ACC-TX-001'; const transactionId = makeUUID('transaction', 'tx-with-source'); const fixture: Fixture = { businesses: { businesses: [createBusiness({ id: businessId, name: 'Biz Tx', ownerId: businessId })], }, taxCategories: { taxCategories: [createTaxCategory({ id: taxCatId, name: 'Tax Tx', ownerId: businessId })], }, accounts: { accounts: [ createFinancialAccount({ accountNumber: accountNumber, type: 'BANK_ACCOUNT', ownerId: businessId, }), ], }, charges: { charges: [createCharge({ owner_id: businessId, tax_category_id: taxCatId }, { id: chargeId })], }, transactions: { transactions: [ createTransaction( { charge_id: chargeId, business_id: businessId, amount: '100.00', currency: 'USD', event_date: '2024-02-01', owner_id: businessId, }, { id: transactionId, account_id: accountNumber, // Will be resolved to UUID by loader source_id: undefined, // Will be auto-generated }, ), ], }, }; await insertFixture(client, fixture); // Verify transaction has a source_id const txResult = await client.query( `SELECT * FROM ${qualifyTable('transactions')} WHERE id = $1`, [transactionId], ); expect(txResult.rows).toHaveLength(1); expect(txResult.rows[0].source_id).toBeDefined(); // Verify source_id references transactions_raw_list const sourceResult = await client.query( `SELECT * FROM ${qualifyTable('transactions_raw_list')} WHERE id = $1`, [txResult.rows[0].source_id], ); expect(sourceResult.rows).toHaveLength(1); })); }); });