mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-10 11:47:54 +02:00
177 lines
6.2 KiB
JavaScript
177 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const { EventEmitter } = require('events');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
const {
|
|
assertLoopbackUrl,
|
|
exportPdf,
|
|
isCompletePdf,
|
|
pdfFileName,
|
|
resolveChromiumExecutable
|
|
} = require('../../scripts/lib/plan-canvas/pdf');
|
|
|
|
async function test(name, fn) {
|
|
try {
|
|
await fn();
|
|
console.log(` ✓ ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` ${error.stack || error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function fakeChild(onSpawn) {
|
|
const child = new EventEmitter();
|
|
child.stderr = new EventEmitter();
|
|
child.exitCode = null;
|
|
child.signalCode = null;
|
|
child.kill = signal => {
|
|
child.signalCode = signal;
|
|
setImmediate(() => {
|
|
child.exitCode = 0;
|
|
child.emit('close', 0, signal);
|
|
});
|
|
return true;
|
|
};
|
|
onSpawn(child);
|
|
return child;
|
|
}
|
|
|
|
async function main() {
|
|
console.log('\n=== Testing Plan Canvas PDF export ===\n');
|
|
let results = [];
|
|
const record = async (name, fn) => {
|
|
results = [...results, await test(name, fn)];
|
|
};
|
|
|
|
await record('builds safe, useful PDF filenames', () => {
|
|
assert.strictEqual(pdfFileName('/tmp/feature-fleet-2.2.plan.md'), 'feature-fleet-2.2.pdf');
|
|
assert.strictEqual(pdfFileName('/tmp/release-preview.html'), 'release-preview.pdf');
|
|
assert.strictEqual(pdfFileName('/tmp/bad:name?.md'), 'bad-name-.pdf');
|
|
assert.strictEqual(pdfFileName(''), 'plan.pdf');
|
|
});
|
|
|
|
await record('restricts the renderer to loopback artifact URLs', () => {
|
|
assert.strictEqual(
|
|
assertLoopbackUrl('http://127.0.0.1:4518/artifact/abc/?pdf=1'),
|
|
'http://127.0.0.1:4518/artifact/abc/?pdf=1'
|
|
);
|
|
assert.throws(() => assertLoopbackUrl('https://127.0.0.1/artifact/abc'), { code: 'PDF_EXPORT_INVALID_URL' });
|
|
assert.throws(() => assertLoopbackUrl('http://example.com/artifact/abc'), { code: 'PDF_EXPORT_INVALID_URL' });
|
|
});
|
|
|
|
await record('honors an explicit Chromium executable override', () => {
|
|
const fsImpl = {
|
|
accessSync(file) { assert.strictEqual(file, '/opt/test/chrome'); },
|
|
statSync() { return { isFile: () => true }; }
|
|
};
|
|
assert.strictEqual(
|
|
resolveChromiumExecutable({
|
|
env: { ECC_PLAN_CANVAS_CHROME_PATH: '/opt/test/chrome', PATH: '' },
|
|
platform: 'linux',
|
|
fsImpl
|
|
}),
|
|
'/opt/test/chrome'
|
|
);
|
|
});
|
|
|
|
await record('fails actionably when no local PDF renderer is installed', async () => {
|
|
await assert.rejects(
|
|
exportPdf({
|
|
url: 'http://127.0.0.1:4517/artifact/abc123/?pdf=1',
|
|
artifactFile: '/workspace/launch.plan.md',
|
|
env: { PATH: '' },
|
|
platform: 'linux'
|
|
}),
|
|
error => error.code === 'PDF_BROWSER_NOT_FOUND' && error.message.includes('ECC_PLAN_CANVAS_CHROME_PATH')
|
|
);
|
|
});
|
|
|
|
await record('recognizes only complete PDF output', () => {
|
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-canvas-pdf-complete-'));
|
|
const file = path.join(tmp, 'artifact.pdf');
|
|
fs.writeFileSync(file, '%PDF-1.4\npartial');
|
|
assert.strictEqual(isCompletePdf(file), false);
|
|
fs.appendFileSync(file, '\n%%EOF\n');
|
|
assert.strictEqual(isCompletePdf(file), true);
|
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
});
|
|
|
|
await record('validates PDF metadata from the opened file handle', () => {
|
|
const content = Buffer.from('%PDF-1.4\nlocal plan\n%%EOF\n');
|
|
const fsImpl = {
|
|
openSync(file, flags) {
|
|
assert.strictEqual(file, '/private/export.pdf');
|
|
assert.strictEqual(flags, 'r');
|
|
return 41;
|
|
},
|
|
fstatSync(fd) {
|
|
assert.strictEqual(fd, 41);
|
|
return { isFile: () => true, size: content.length };
|
|
},
|
|
readSync(fd, target, offset, length, position) {
|
|
assert.strictEqual(fd, 41);
|
|
return content.copy(target, offset, position, position + length);
|
|
},
|
|
closeSync(fd) { assert.strictEqual(fd, 41); },
|
|
statSync() { throw new Error('path metadata must not be checked before opening'); }
|
|
};
|
|
assert.strictEqual(isCompletePdf('/private/export.pdf', fsImpl), true);
|
|
});
|
|
|
|
await record('renders, isolates network access, terminates its browser, and removes temporary state', async () => {
|
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'plan-canvas-pdf-test-'));
|
|
let spawned = null;
|
|
let child = null;
|
|
const spawnImpl = (command, args, options) => {
|
|
spawned = { command, args, options };
|
|
const outputFile = args.find(arg => arg.startsWith('--print-to-pdf=')).slice('--print-to-pdf='.length);
|
|
child = fakeChild(() => {
|
|
setTimeout(() => fs.writeFileSync(outputFile, '%PDF-1.4\nlocal plan\n%%EOF\n'), 20);
|
|
});
|
|
return child;
|
|
};
|
|
|
|
const result = await exportPdf({
|
|
url: 'http://localhost:4517/artifact/abc123/?pdf=1',
|
|
artifactFile: '/workspace/launch.plan.md',
|
|
executable: '/opt/test/chrome',
|
|
timeoutMs: 1000,
|
|
spawnImpl,
|
|
osImpl: { tmpdir: () => tempRoot }
|
|
});
|
|
|
|
assert.strictEqual(result.filename, 'launch.pdf');
|
|
assert.ok(result.buffer.subarray(0, 5).equals(Buffer.from('%PDF-')));
|
|
assert.strictEqual(spawned.command, '/opt/test/chrome');
|
|
assert.strictEqual(spawned.options.shell, false);
|
|
assert.ok(spawned.args.includes('--headless=new'));
|
|
assert.ok(spawned.args.includes('--disable-background-networking'));
|
|
assert.ok(spawned.args.includes('--disable-quic'));
|
|
assert.ok(spawned.args.some(arg => /^--proxy-server=http:\/\/127\.0\.0\.1:\d+$/.test(arg)));
|
|
assert.ok(spawned.args.includes('--proxy-bypass-list=<-loopback>;http://localhost:4517'));
|
|
assert.ok(spawned.args.includes('--host-resolver-rules=MAP * ~NOTFOUND, EXCLUDE localhost'));
|
|
assert.ok(spawned.args.includes('http://localhost:4517/artifact/abc123/?pdf=1'));
|
|
assert.strictEqual(child.signalCode, 'SIGTERM');
|
|
assert.deepStrictEqual(fs.readdirSync(tempRoot), []);
|
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
const passed = results.filter(Boolean).length;
|
|
const failed = results.length - passed;
|
|
console.log('\n========================================');
|
|
console.log(`Passed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
console.log('========================================');
|
|
process.exit(failed ? 1 : 0);
|
|
}
|
|
|
|
main();
|