import { describe, expect, it } from 'vitest'; import { ensureClientBillingSettingsRow, updateClientBillingSettings } from '../billingClients/billingSettings'; import { ensureDefaultContractForClientIfBillingConfigured } from '../billingClients/defaultContract'; type Row = Record; type TableState = Record; type FakeKnexOptions = { forceEmptyDefaultContractLookups?: number; }; class FakeQueryBuilder { private readonly state: TableState; private readonly tableName: string; private readonly options: FakeKnexOptions; private filters: Record[] = []; private selectedColumns: string[] | null = null; private firstOnly = false; constructor(state: TableState, tableName: string, options: FakeKnexOptions) { this.state = state; this.tableName = tableName; this.options = options; } where(criteria: Record): this { this.filters.push(criteria); return this; } select(...columns: string[]): this { this.selectedColumns = columns; return this; } forUpdate(): this { return this; } first(...columns: string[]): this { if (columns.length > 0) { this.selectedColumns = columns; } this.firstOnly = true; return this; } async insert(payload: Row): Promise<{ returning: (columns: string | string[]) => Promise }> { const table = this.state[this.tableName] ?? (this.state[this.tableName] = []); if ( this.tableName === 'contracts' && payload.is_system_managed_default === true && payload.owner_client_id ) { const duplicate = table.find( (row) => row.tenant === payload.tenant && row.owner_client_id === payload.owner_client_id && row.is_system_managed_default === true ); if (duplicate) { const duplicateError = Object.assign(new Error('duplicate key value violates unique constraint'), { code: '23505', }); throw duplicateError; } } const row = { ...payload }; table.push(row); return { returning: async (columns: string | string[]) => { const selected = Array.isArray(columns) ? columns : [columns]; if (selected.includes('*')) { return [{ ...row }]; } return [selected.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})]; }, }; } async update(patch: Row): Promise { const table = this.state[this.tableName] ?? []; const rows = this.filterRows(table); for (const row of rows) { Object.assign(row, patch); } return rows.length; } async del(): Promise { const table = this.state[this.tableName] ?? []; const matching = this.filterRows(table); this.state[this.tableName] = table.filter((row) => !matching.includes(row)); return matching.length; } then(resolve: (value: any) => any, reject?: (reason: any) => any): Promise { if (this.firstOnly) { const firstRow = this.getRows()[0]; return Promise.resolve(firstRow).then(resolve, reject); } return Promise.resolve(this.getRows()).then(resolve, reject); } private getRows(): Row[] { const table = this.state[this.tableName] ?? []; if ( this.tableName === 'contracts' && this.options.forceEmptyDefaultContractLookups && this.options.forceEmptyDefaultContractLookups > 0 && this.filters.some((criteria) => criteria.is_system_managed_default === true) ) { this.options.forceEmptyDefaultContractLookups -= 1; return []; } const rows = this.filterRows(table); return rows.map((row) => this.projectRow(row)); } private filterRows(rows: Row[]): Row[] { if (this.filters.length === 0) { return [...rows]; } return rows.filter((row) => this.filters.every((criteria) => Object.entries(criteria).every(([key, value]) => row[key] === value) ) ); } private projectRow(row: Row): Row { if (!this.selectedColumns || this.selectedColumns.length === 0 || this.selectedColumns.includes('*')) { return { ...row }; } return this.selectedColumns.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}); } } const createFakeKnex = (initialState: TableState, options: FakeKnexOptions = {}) => { const state: TableState = Object.fromEntries( Object.entries(initialState).map(([table, rows]) => [table, rows.map((row) => ({ ...row }))]) ); const knex = ((tableName: string) => new FakeQueryBuilder(state, tableName, options)) as any; knex.fn = { now: () => '2026-03-21T00:00:00.000Z', }; knex.transaction = async (handler: (trx: any) => Promise) => handler(knex); knex.__state = state; return knex; }; describe('default contract ensure on billing settings ensure', () => { it('T001: creates exactly one system-managed default contract and assignment and stays idempotent on repeated ensure', async () => { const knex = createFakeKnex({ clients: [ { tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'usd', }, ], default_billing_settings: [ { tenant: 'tenant-1', zero_dollar_invoice_handling: 'normal', suppress_zero_dollar_invoices: false, credit_expiration_days: 365, credit_expiration_notification_days: [30, 7, 1], enable_credit_expiration: true, }, ], client_billing_settings: [], contracts: [], client_contracts: [], }); const first = await ensureClientBillingSettingsRow(knex, { tenant: 'tenant-1', clientId: 'client-1', }); const second = await ensureClientBillingSettingsRow(knex, { tenant: 'tenant-1', clientId: 'client-1', }); expect(first.created).toBe(true); expect(second.created).toBe(false); const state = knex.__state as TableState; expect(state.client_billing_settings).toHaveLength(1); const defaultContracts = state.contracts.filter( (row) => row.tenant === 'tenant-1' && row.owner_client_id === 'client-1' && row.is_system_managed_default === true ); expect(defaultContracts).toHaveLength(1); expect(defaultContracts[0]).toMatchObject({ contract_name: 'System-managed default contract', status: 'active', is_active: true, is_template: false, currency_code: 'USD', }); const contractId = defaultContracts[0].contract_id; const assignments = state.client_contracts.filter( (row) => row.tenant === 'tenant-1' && row.client_id === 'client-1' && row.contract_id === contractId ); expect(assignments).toHaveLength(1); expect(assignments[0].is_active).toBe(true); }); it('T002: parallel ensure calls for the same tenant+client do not create duplicate default contracts', async () => { const knex = createFakeKnex( { clients: [ { tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'USD', }, ], default_billing_settings: [ { tenant: 'tenant-1', zero_dollar_invoice_handling: 'normal', suppress_zero_dollar_invoices: false, credit_expiration_days: 365, credit_expiration_notification_days: [30, 7, 1], enable_credit_expiration: true, }, ], client_billing_settings: [], contracts: [], client_contracts: [], }, { // Force both parallel calls to miss the initial lookup, so both attempt insert. // The second insert must be rejected/retried via unique-violation path. forceEmptyDefaultContractLookups: 2, } ); await Promise.all([ ensureClientBillingSettingsRow(knex, { tenant: 'tenant-1', clientId: 'client-1' }), ensureClientBillingSettingsRow(knex, { tenant: 'tenant-1', clientId: 'client-1' }), ]); const state = knex.__state as TableState; const defaultContracts = state.contracts.filter( (row) => row.tenant === 'tenant-1' && row.owner_client_id === 'client-1' && row.is_system_managed_default === true ); expect(defaultContracts).toHaveLength(1); const assignments = state.client_contracts.filter( (row) => row.tenant === 'tenant-1' && row.client_id === 'client-1' && row.contract_id === defaultContracts[0].contract_id ); expect(assignments).toHaveLength(1); }); it('fallback ensure hook is a no-op when billing settings do not exist for the client', async () => { const knex = createFakeKnex({ clients: [{ tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'USD' }], default_billing_settings: [], client_billing_settings: [], contracts: [], client_contracts: [], }); const result = await ensureDefaultContractForClientIfBillingConfigured(knex, { tenant: 'tenant-1', clientId: 'client-1', }); expect(result.ensured).toBe(false); const state = knex.__state as TableState; expect(state.contracts).toHaveLength(0); }); it('fallback ensure hook provisions the default contract when billing settings already exist', async () => { const knex = createFakeKnex({ clients: [{ tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'USD' }], default_billing_settings: [], client_billing_settings: [{ tenant: 'tenant-1', client_id: 'client-1' }], contracts: [], client_contracts: [], }); const result = await ensureDefaultContractForClientIfBillingConfigured(knex, { tenant: 'tenant-1', clientId: 'client-1', }); expect(result.ensured).toBe(true); const state = knex.__state as TableState; expect(state.contracts.filter((row) => row.is_system_managed_default === true)).toHaveLength(1); expect(state.client_contracts).toHaveLength(1); }); it('normalizes legacy system-managed default contract naming to canonical convention during ensure', async () => { const knex = createFakeKnex({ clients: [{ tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'USD' }], default_billing_settings: [], client_billing_settings: [{ tenant: 'tenant-1', client_id: 'client-1' }], contracts: [ { tenant: 'tenant-1', contract_id: 'contract-legacy', owner_client_id: 'client-1', is_system_managed_default: true, is_template: false, contract_name: 'Default contract', contract_description: 'legacy description', }, ], client_contracts: [], }); const result = await ensureDefaultContractForClientIfBillingConfigured(knex, { tenant: 'tenant-1', clientId: 'client-1', }); expect(result.ensured).toBe(true); const state = knex.__state as TableState; const contract = state.contracts.find((row) => row.contract_id === 'contract-legacy'); expect(contract).toBeDefined(); expect(contract?.contract_name).toBe('System-managed default contract'); expect(contract?.contract_description).toBe('Created automatically for uncontracted work'); expect(state.client_contracts).toHaveLength(1); }); it('T014: null billing-settings override deletes settings without recreating default contract artifacts', async () => { const knex = createFakeKnex({ clients: [{ tenant: 'tenant-1', client_id: 'client-1', default_currency_code: 'USD' }], default_billing_settings: [], client_billing_settings: [{ tenant: 'tenant-1', client_id: 'client-1' }], contracts: [], client_contracts: [], }); const trxLikeKnex = Object.assign(knex, { commit: () => undefined, rollback: () => undefined, }); await updateClientBillingSettings(trxLikeKnex as any, 'tenant-1', 'client-1', null); const state = knex.__state as TableState; expect(state.client_billing_settings).toHaveLength(0); expect(state.contracts).toHaveLength(0); expect(state.client_contracts).toHaveLength(0); }); });