import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { existsSync, readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { resolve } from 'node:path'; import test from 'node:test'; import { buildInspectionActPdf } from '../../src/inspection-reports/inspection-act-pdf-builder'; import { buildInspectionReportWord } from '../../src/inspection-reports/inspection-report-word-builder'; import sharp from 'sharp'; import { renderableInspectionImage } from '../../src/inspection-reports/inspection-document-images'; const digest = (data: Buffer) => createHash('sha256').update(data).digest('hex'); const photo = readFileSync(resolve(process.cwd(), 'assets/logo-mendoza.png')); const locked = { act: { code: 'ACT-TEST-01', occurredAt: '2026-09-14T18:30:00Z', summary: 'Se inspeccionó un tanque.', inspection: { code: 'INSP-TEST-01' } }, responsible: { fullName: 'Responsable de prueba', documentNumber: '12345678' }, inventories: [{ id: 'asset-1', code: 'CAM-TEST-01', name: 'Tanque de prueba' }], findings: [{ id: 'finding-1', assetId: 'asset-1', code: 'ACT-TEST-H01', title: 'Pérdida', description: 'Se constató una pérdida visible.', severity: 3 }], }; const sealed = { lockedSnapshot: locked, finalSha256: 'a'.repeat(64), lockedSha256: 'b'.repeat(64), signatures: [], seal: { serverSealedAt: '2026-09-15T18:30:00Z' } }; const evidence = { id: 'photo-1', findingId: 'finding-1', sha256: digest(photo), buffer: photo }; test('F6.9 consolidated Acta is a hashed PDF 1.4 with actual evidence embedded', async () => { const without = await buildInspectionActPdf(sealed); const withEvidence = await buildInspectionActPdf(sealed, [evidence]); assert.ok(withEvidence.buffer.subarray(0, 8).equals(Buffer.from('%PDF-1.4'))); assert.equal(withEvidence.sha256, digest(withEvidence.buffer)); assert.ok(withEvidence.buffer.length > without.buffer.length + 3_000); assert.notEqual(withEvidence.sha256, without.sha256); }); test('F6.9 technical Informe embeds evidence and its institutional source hash', () => { const input = { code: 'INF-TEST-01', title: 'Informe de prueba', generatedAt: new Date('2026-09-15'), frozenSha256: 'c'.repeat(64), frozenSnapshot: { sealedAct: sealed, source: { actCode: locked.act.code } } }; const plain = buildInspectionReportWord(input); const withEvidence = buildInspectionReportWord({ ...input, photos: [evidence] }); assert.equal(withEvidence.sha256, digest(withEvidence.buffer)); assert.ok(withEvidence.buffer.subarray(0, 4).equals(Buffer.from('PK\x03\x04'))); assert.ok(withEvidence.buffer.includes(Buffer.from('word/media/photo-1.png'))); assert.ok(withEvidence.buffer.includes(photo)); assert.ok(withEvidence.buffer.includes(Buffer.from('ACT-TEST-H01'))); assert.ok(withEvidence.buffer.includes(Buffer.from('SHA-256 de la fuente del Informe'))); assert.ok(withEvidence.buffer.length > plain.buffer.length + photo.length); assert.ok(!withEvidence.buffer.includes(Buffer.from('[Completar'))); }); test('F6.9 keeps prior sealed PDF and prior company responses available for audit', () => { const pdf = readFileSync(resolve(process.cwd(), 'src/inspection-reports/inspection-act-pdf.service.ts'), 'utf8'); const controller = readFileSync(resolve(process.cwd(), 'src/act-administration/act-administration.controller.ts'), 'utf8'); assert.match(pdf, /inspection_act_consolidated_pdf_artifacts/); const word = readFileSync(resolve(process.cwd(), 'src/inspection-reports/inspection-report-word.service.ts'), 'utf8'); assert.match(word, /inspection_report_consolidated_word_artifacts/); assert.match(pdf, /ON CONFLICT \(act_id\) DO NOTHING/); assert.match(controller, /responseContent\(responseId\)/); assert.doesNotMatch(controller, /@Post\('responses'\)/); }); test('F6.9 renders WebP field photographs in both documents without changing the source hash', async () => { const original = await sharp(photo).webp().toBuffer(); const originalSha = digest(original); const display = await renderableInspectionImage(original); assert.ok(display.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))); assert.equal(digest(original), originalSha); const pdf = await buildInspectionActPdf(sealed, [{ ...evidence, sha256: originalSha, buffer: display }]); assert.ok(pdf.buffer.subarray(0, 8).equals(Buffer.from('%PDF-1.4'))); const word = buildInspectionReportWord({ code: 'INF-WEBP', title: 'Informe', generatedAt: new Date(), frozenSha256: 'c'.repeat(64), frozenSnapshot: { sealedAct: sealed }, photos: [{ ...evidence, sha256: originalSha, buffer: display }] }); assert.ok(word.buffer.includes(display)); assert.ok(word.buffer.includes(Buffer.from(originalSha))); }); test('F6.9 built CommonJS runtime renders signed PDF and WebP evidence', async () => { const builtPdf = resolve(process.cwd(), 'dist/inspection-reports/inspection-act-pdf-builder.js'); const builtImage = resolve(process.cwd(), 'dist/inspection-reports/inspection-document-images.js'); if (!existsSync(builtPdf) || !existsSync(builtImage)) return; const requireCjs = createRequire(resolve(process.cwd(), 'package.json')); const runtimePdf = requireCjs(builtPdf) as { buildInspectionActPdf: typeof buildInspectionActPdf }; const runtimeImage = requireCjs(builtImage) as { renderableInspectionImage: typeof renderableInspectionImage }; const webp = await sharp(photo).webp().toBuffer(); const rendered = await runtimeImage.renderableInspectionImage(webp); const pdf = await runtimePdf.buildInspectionActPdf(sealed, [{ ...evidence, buffer: rendered, sha256: digest(webp) }]); assert.ok(pdf.buffer.subarray(0, 8).equals(Buffer.from('%PDF-1.4'))); });