test(f4): harden multi-act immutability gates

This commit is contained in:
2026-09-08 09:58:12 -03:00
parent 7174d379e5
commit afb6feb4c1
@@ -8,6 +8,8 @@ const read = (relative: string) => fs.readFileSync(path.join(root, relative), 'u
const closing = read('src/inspection-closing/inspection-closing.service.ts');
const lifecycle = read('src/inspection-visits/inspection-visit-lifecycle.service.ts');
const acts = read('src/inspection-acts/inspection-acts.service.ts');
const findings = read('src/inspection-findings/inspection-findings.service.ts');
const actEntity = read('src/database/entities/inspection-act.entity.ts');
test('F4 keeps finalized acts irreversible', () => {
@@ -31,17 +33,52 @@ test('F4 act locking is independent from inspection-wide verification completion
assert.match(prepareBody, /status='LOCKED'/);
});
test('F4 inspection close is the aggregate gate for sealed acts and verification results', () => {
test('F4 blocks act header and inventory mutations once the act leaves draft', () => {
const updateBody = acts.slice(acts.indexOf(' async update('), acts.indexOf(' async cancel('));
const cancelBody = acts.slice(acts.indexOf(' async cancel('), acts.indexOf(' async getById('));
assert.match(updateBody, /this\.assertActDraft\(act\)/);
assert.match(cancelBody, /this\.assertActDraft\(act\)/);
});
test('F4 blocks finding creation and editing once the act is locked', () => {
const editableGuard = findings.slice(
findings.indexOf(' private assertActEditable('),
findings.indexOf(' private assertFindingOpen('),
);
assert.match(editableGuard, /act\.status !== InspectionActStatus\.DRAFT/);
assert.match(editableGuard, /visit\.status !== InspectionVisitStatus\.IN_PROGRESS/);
assert.match(editableGuard, /INSPECTION_FINDING_ACT_NOT_EDITABLE/);
const createBody = findings.slice(findings.indexOf(' async create('), findings.indexOf(' async update('));
const updateBody = findings.slice(findings.indexOf(' async update('), findings.indexOf(' async updateFollowUp('));
assert.match(createBody, /this\.assertActEditable\(act, visit\)/);
assert.match(updateBody, /this\.assertActEditable\(act, visit\)/);
});
test('F4 sealing requires inspector signature and exactly one resolved company manifestation', () => {
const closeBody = closing.slice(closing.indexOf(' async close('), closing.indexOf(' async signatureContent('));
assert.match(closeBody, /INSPECTION_ACT_INSPECTOR_SIGNATURE_REQUIRED/);
assert.match(closeBody, /companyOutcomes\.length !== 1/);
assert.match(closeBody, /INSPECTION_ACT_COMPANY_OUTCOME_REQUIRED/);
assert.match(closeBody, /InspectionActSignatureStatus\.ABSENT/);
assert.match(closeBody, /INSPECTION_ACT_COMPANY_MANIFESTATION_PENDING/);
assert.match(closeBody, /status='SEALED'/);
});
test('F4 inspection close is the aggregate gate for every non-cancelled act and verification result', () => {
const closeBody = lifecycle.slice(lifecycle.indexOf(' async close('), lifecycle.indexOf(' async cancel('));
assert.match(closeBody, /status<>'CANCELLED'/);
assert.match(closeBody, /act\.status !== 'SEALED'/);
assert.match(closeBody, /WHERE visit_id=\$1 AND status<>'CANCELLED'/);
assert.match(closeBody, /acts\.length === 0/);
assert.match(closeBody, /INSPECTION_REQUIRES_ACT/);
assert.match(closeBody, /acts\.filter\(\(act\) => act\.status !== 'SEALED'\)/);
assert.match(closeBody, /INSPECTION_ACTS_NOT_SEALED/);
assert.match(closeBody, /pendingActs: pending\.map/);
assert.match(closeBody, /assertVerificationResultsComplete\(manager, id\)/);
assert.match(closeBody, /InspectionVisitStatus\.CLOSED/);
});
test('F4 prevents cancelling an inspection that already has a sealed act', () => {
test('F4 prevents cancelling an inspection that already has any sealed act', () => {
const cancelBody = lifecycle.slice(lifecycle.indexOf(' async cancel('), lifecycle.indexOf(' private async lockVisit('));
assert.match(cancelBody, /status='SEALED'/);
assert.match(cancelBody, /WHERE visit_id=\$1 AND status='SEALED'/);
assert.match(cancelBody, /INSPECTION_WITH_SEALED_ACT_CANNOT_CANCEL/);
});