142 lines
5.1 KiB
JavaScript
142 lines
5.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
DossierExportGate,
|
|
dossierExportFailure,
|
|
prepareDossierAttachments,
|
|
} from '../../resources/js/lib/dossier-export-state.ts';
|
|
import {
|
|
createProjectDossierReport,
|
|
generateProjectDossierDocx,
|
|
createProjectDossierPrintHtml,
|
|
generateProjectDossierPackage,
|
|
} from '../../resources/js/lib/project-dossier-report.ts';
|
|
|
|
test('concurrent export is ignored; failed generation unlocks one retry without mutating inputs', async () => {
|
|
const gate = new DossierExportGate();
|
|
const draft = {
|
|
project_title: 'SINTÉTICO QA',
|
|
project_summary: 'Pressupostos declarados; não é resultado fiscal.',
|
|
};
|
|
const saved = JSON.stringify(draft);
|
|
let release;
|
|
let starts = 0;
|
|
const first = gate.run(async () => {
|
|
starts++;
|
|
await new Promise((resolve) => {
|
|
release = resolve;
|
|
});
|
|
throw new Error('Library failure');
|
|
});
|
|
assert.equal(
|
|
await gate.run(async () => {
|
|
starts++;
|
|
}),
|
|
false,
|
|
);
|
|
release();
|
|
await assert.rejects(first, /Library failure/);
|
|
assert.equal(
|
|
await gate.run(async () => {
|
|
starts++;
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(starts, 2);
|
|
assert.equal(JSON.stringify(draft), saved);
|
|
});
|
|
|
|
test('all operations show Portuguese recovery text while retaining technical details in diagnostics', () => {
|
|
for (const operation of ['word', 'package', 'pdf', 'csv', 'attachments']) {
|
|
const error = new Error('English internals / private stack');
|
|
const logs = [];
|
|
const text = dossierExportFailure(operation, error, (...args) =>
|
|
logs.push(args),
|
|
);
|
|
assert.match(text, /Não foi possível/);
|
|
assert.match(text, /foram mantidos.*Tente novamente/);
|
|
assert.doesNotMatch(text, /English internals|private stack/);
|
|
assert.equal(logs.length, 1);
|
|
assert.equal(logs[0][1].cause, error);
|
|
assert.ok(text.includes(logs[0][1].reference));
|
|
}
|
|
});
|
|
|
|
test('attachment read failure and size refusal preserve existing selection; retry deduplicates bytes and keeps captions', async () => {
|
|
const file = new File(['SINTÉTICO QA'], 'qa.txt', { type: 'text/plain' });
|
|
const current = [{ file, include: false, caption: 'Legenda conservada' }];
|
|
const broken = new File(['another'], 'broken.txt');
|
|
broken.arrayBuffer = async () => {
|
|
throw new Error('OS read failed');
|
|
};
|
|
await assert.rejects(
|
|
prepareDossierAttachments(current, [broken]),
|
|
/OS read failed/,
|
|
);
|
|
assert.equal(current.length, 1);
|
|
const tooLarge = new File([new Uint8Array(16 * 1024 * 1024)], 'large.txt');
|
|
const refused = await prepareDossierAttachments(current, [tooLarge]);
|
|
assert.equal(refused.attachments, current);
|
|
assert.match(refused.problem, /15 MB/);
|
|
const result = await prepareDossierAttachments(current, [
|
|
new File(['SINTÉTICO QA'], 'qa.txt'),
|
|
new File(['distinct'], 'qa.txt'),
|
|
]);
|
|
assert.equal(result.duplicateCount, 1);
|
|
assert.equal(result.attachments.length, 2);
|
|
assert.equal(result.attachments[0], current[0]);
|
|
assert.equal(result.attachments[0].caption, 'Legenda conservada');
|
|
assert.equal(result.attachments[0].include, false);
|
|
});
|
|
|
|
test('actual Word PDF and ZIP generators recover after a transient attachment read failure; archive has one original', async () => {
|
|
const model = createProjectDossierReport(
|
|
{ project_title: 'QA recuperação' },
|
|
{ selectedSections: new Set(['summary', 'annexes']) },
|
|
);
|
|
const file = new File(['Texto QA'], 'qa.txt', { type: 'text/plain' });
|
|
const original = file.text.bind(file);
|
|
let fail = false;
|
|
file.text = async () => {
|
|
if (fail) {
|
|
fail = false;
|
|
throw new Error('Transient attachment read failure');
|
|
}
|
|
return original();
|
|
};
|
|
const attachments = [{ file, include: true, caption: '' }];
|
|
for (const generate of [
|
|
() => generateProjectDossierDocx(model, attachments, true),
|
|
() => createProjectDossierPrintHtml(model, attachments, true),
|
|
]) {
|
|
fail = true;
|
|
await assert.rejects(generate, /Transient attachment/);
|
|
assert.ok(await generate());
|
|
assert.equal(attachments.length, 1);
|
|
}
|
|
const word = await generateProjectDossierDocx(model, [], false);
|
|
let broken = true;
|
|
const { DossierExportGate: Gate } =
|
|
await import('../../resources/js/lib/dossier-export-state.ts');
|
|
const gate = new Gate();
|
|
await assert.rejects(
|
|
gate.run(async () => {
|
|
if (broken) throw new Error('ZIP write fault');
|
|
}),
|
|
/ZIP write fault/,
|
|
);
|
|
broken = false;
|
|
let zip;
|
|
await gate.run(async () => {
|
|
zip = await generateProjectDossierPackage(word, attachments, true);
|
|
});
|
|
const { default: JSZip } = await import('jszip');
|
|
const archive = await JSZip.loadAsync(await zip.arrayBuffer());
|
|
assert.deepEqual(
|
|
Object.keys(archive.files).filter(
|
|
(name) => name.startsWith('Anexos/') && !archive.files[name].dir,
|
|
),
|
|
['Anexos/qa.txt'],
|
|
);
|
|
});
|