Incognito-Wiki/tests/pdf-archive.test.mjs

115 lines
4.6 KiB
JavaScript

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\/\)/);
}
});