PSA/packages/billing/tests/contractLineDelete.cascade.test.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

86 lines
2.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@alga-psa/db', () => ({
requireTenantId: vi.fn(async () => 'tenant-1')
}));
import ContractLine from '../src/models/contractLine';
type Op = { table: string; type: 'count' | 'delete'; where: Record<string, unknown> };
function makeKnex(rows: Record<string, number>) {
const ops: Op[] = [];
function builder(table: string) {
const state: { where: Record<string, unknown> } = { where: {} };
const b: any = {};
b.where = (filters: Record<string, unknown>) => {
state.where = { ...state.where, ...filters };
return b;
};
b.count = (_col: string) => {
ops.push({ table, type: 'count', where: state.where });
return {
first: async () => ({ count: String(rows[table] ?? 0) })
};
};
b.delete = async () => {
ops.push({ table, type: 'delete', where: state.where });
return rows[table] === undefined ? 1 : rows[table];
};
return b;
}
const knex: any = (table: string) => builder(table);
return { knex, ops };
}
describe('ContractLine.delete cascade', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('deletes contract_line_service_configuration and contract_line_services before contract_lines', async () => {
const { knex, ops } = makeKnex({
client_contract_lines: 0,
contract_lines: 1
});
await ContractLine.delete(knex, 'cl-1');
const deletes = ops.filter((op) => op.type === 'delete').map((op) => op.table);
expect(deletes).toEqual([
'contract_line_service_configuration',
'contract_line_services',
'contract_lines'
]);
const configDelete = ops.find((op) => op.table === 'contract_line_service_configuration' && op.type === 'delete');
expect(configDelete?.where).toEqual({ contract_line_id: 'cl-1', tenant: 'tenant-1' });
const servicesDelete = ops.find((op) => op.table === 'contract_line_services' && op.type === 'delete');
expect(servicesDelete?.where).toEqual({ contract_line_id: 'cl-1', tenant: 'tenant-1' });
});
it('refuses to delete when contract line is in use by a client', async () => {
const { knex, ops } = makeKnex({
client_contract_lines: 1,
contract_lines: 1
});
await expect(ContractLine.delete(knex, 'cl-2')).rejects.toThrow(/in use by clients/i);
expect(ops.some((op) => op.type === 'delete')).toBe(false);
});
it('throws when contract line does not exist', async () => {
const { knex } = makeKnex({
client_contract_lines: 0,
contract_lines: 0
});
await expect(ContractLine.delete(knex, 'cl-missing')).rejects.toThrow(/not found/i);
});
});