import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import test from 'node:test'; import { verifyPdfArchive } from '../scripts/verify-pdf-archive.mjs'; async function withArchiveFixture(run) { const root = await mkdtemp(join(tmpdir(), 'incognito-pdf-archive-')); try { const relativePath = 'public/media/legacy-dokuwiki/study/course/exam.pdf'; const absolutePath = join(root, relativePath); const bytes = Buffer.from('%PDF-1.4\nfixture\n%%EOF\n'); await mkdir(join(absolutePath, '..'), { recursive: true }); await writeFile(absolutePath, bytes); const sha256 = createHash('sha256').update(bytes).digest('hex'); const inventoryPath = join(root, 'inventory.tsv'); const row = [ 'study:course:exam.pdf', '/media/legacy-dokuwiki/study/course/exam.pdf', relativePath, 'https://msvincognito.nl/wiki/_media/study/course/exam.pdf', String(bytes.length), sha256, ]; await writeFile(inventoryPath, [ 'media_id\tpublic_url\tlocal_path\tsource_url\tbytes\tsha256', row.join('\t'), '', ].join('\n')); await run({ root, inventoryPath, absolutePath, bytes, row }); } finally { await rm(root, { recursive: true, force: true }); } } test('verifies a complete byte-identical public PDF archive', async () => { await withArchiveFixture(async ({ root, inventoryPath, bytes }) => { const result = await verifyPdfArchive({ inventoryPath, projectRoot: root, expectedCount: 1 }); assert.deepEqual(result, { fileCount: 1, totalBytes: bytes.length, uniqueHashes: 1 }); }); }); test('rejects a missing inventoried public PDF', async () => { await withArchiveFixture(async ({ root, inventoryPath, absolutePath }) => { await rm(absolutePath); await assert.rejects( verifyPdfArchive({ inventoryPath, projectRoot: root, expectedCount: 1 }), /missing public PDF/, ); }); }); test('rejects a PDF whose published bytes do not match the inventory', async () => { await withArchiveFixture(async ({ root, inventoryPath, absolutePath }) => { await writeFile(absolutePath, Buffer.from('%PDF-1.4\nchanged\n%%EOF\n')); await assert.rejects( verifyPdfArchive({ inventoryPath, projectRoot: root, expectedCount: 1 }), /byte size mismatch|SHA-256 mismatch/, ); }); }); test('rejects an inventoried file without a PDF signature', async () => { await withArchiveFixture(async ({ root, inventoryPath, absolutePath, row }) => { const bytes = Buffer.from('not a PDF'); await writeFile(absolutePath, bytes); row[4] = String(bytes.length); row[5] = createHash('sha256').update(bytes).digest('hex'); await writeFile(inventoryPath, [ 'media_id\tpublic_url\tlocal_path\tsource_url\tbytes\tsha256', row.join('\t'), '', ].join('\n')); await assert.rejects( verifyPdfArchive({ inventoryPath, projectRoot: root, expectedCount: 1 }), /invalid PDF signature/, ); }); }); test('the reader-facing archive links every inventoried PDF exactly once', async () => { const inventory = (await readFile('docs/pdf-inventory.tsv', 'utf8')).trim().split(/\r?\n/).slice(1); const publicUrls = inventory.map((row) => row.split('\t')[1]); const archive = await readFile('src/content/docs/previous-exams-and-documents.md', 'utf8'); const pdfLinks = [...archive.matchAll(/\]\((\/media\/legacy-dokuwiki\/[^)]+\.pdf)\)/g)] .map((match) => match[1]); assert.equal(pdfLinks.length, 332); assert.deepEqual(new Set(pdfLinks), new Set(publicUrls)); for (const url of publicUrls) { assert.equal(pdfLinks.filter((candidate) => candidate === url).length, 1, `${url} must appear once`); } }); test('the PDF archive is registered in supplemental content and the sidebar', async () => { const supplemental = JSON.parse(await readFile('docs/supplemental-content.json', 'utf8')); assert.deepEqual(supplemental.find(({ category }) => category === 'pdf-archive'), { destination: 'src/content/docs/previous-exams-and-documents.md', source: 'docs/pdf-inventory.tsv', category: 'pdf-archive', }); const sidebar = await readFile('src/config/sidebar.mjs', 'utf8'); assert.match(sidebar, /slug: 'previous-exams-and-documents'/); for (const overview of [ 'src/content/docs/data-science-and-ai/index.md', 'src/content/docs/master-ai/index.md', 'src/content/docs/master-dsdm/index.md', ]) { assert.match(await readFile(overview, 'utf8'), /\[Previous exams and documents\]\(\/previous-exams-and-documents\/\)/); } });