import { strict as assert } from 'node:assert'; import { readFile, readdir, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { afterEach, beforeEach, describe, it } from 'node:test'; import { MANIFEST_FILE } from '../src/storage/manifest.ts'; import { sitePaths } from '../src/storage/sets.ts'; import { hasFatal, validateSite } from '../src/storage/validate.ts'; import { SECTIONS, makeStore, pngBytes, tempDir } from './support/harness.ts'; let dataDir: string; beforeEach(async () => { dataDir = await tempDir('validate'); }); afterEach(async () => { await rm(dataDir, { recursive: true, force: true }); }); function validate() { return validateSite({ dataDir, sections: { has: (slug) => (SECTIONS as readonly string[]).includes(slug) } }); } describe('validateSite', () => { it('passes an empty Site and a healthy published set', async () => { const { store } = makeStore(dataDir); assert.deepEqual(await validate(), []); const set = await store.create('bar', { label: 'Bar Menu' }); await store.addPage(set.id, { data: pngBytes('one'), originalName: 'a.jpg' }); await store.publish(set.id); assert.deepEqual(await validate(), []); }); it('warns about a broken Draft but fails on a broken published set', async () => { const { store } = makeStore(dataDir); const draft = await store.create('bar', { label: 'Bar Menu' }); const page = await store.addPage(draft.id, { data: pngBytes('one'), originalName: 'a.jpg' }); const names = await readdir(draft.dir); const thumbnail = names.find((name) => name.startsWith('thumb-'))!; await rm(path.join(draft.dir, thumbnail)); const draftProblems = await validate(); assert.equal(draftProblems.length, 1); assert.equal(draftProblems[0]?.level, 'warning'); assert.equal(hasFatal(draftProblems), false); await store.publish(draft.id); const publishedProblems = await validate(); assert.equal(publishedProblems[0]?.level, 'fatal'); assert.equal(hasFatal(publishedProblems), true); assert.match(publishedProblems[0]?.message ?? '', new RegExp(page.id)); }); it('fails when a manifest disagrees with its own directory', async () => { const { store } = makeStore(dataDir); const set = await store.create('bar', { label: 'Bar Menu' }); const file = path.join(set.dir, MANIFEST_FILE); const manifest = JSON.parse(await readFile(file, 'utf8')) as Record; manifest['section'] = 'food'; await writeFile(file, JSON.stringify(manifest)); const problems = await validate(); assert.equal(hasFatal(problems), true); assert.ok( problems.some((problem) => problem.message.includes('does not match its directory')), ); }); it('fails on a manifest that cannot be parsed', async () => { const { store } = makeStore(dataDir); const set = await store.create('bar', { label: 'Bar Menu' }); await writeFile(path.join(set.dir, MANIFEST_FILE), '{ not json'); const problems = await validate(); assert.equal(hasFatal(problems), true); assert.ok(problems.some((problem) => problem.message.includes('not valid JSON'))); }); it('fails on a published set with no Pages', async () => { const { store } = makeStore(dataDir); const set = await store.create('bar', { label: 'Bar Menu' }); await store.addPage(set.id, { data: pngBytes('one'), originalName: 'a.jpg' }); const published = await store.publish(set.id); const manifestPath = path.join(published.dir, MANIFEST_FILE); const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as Record; manifest['pages'] = []; await writeFile(manifestPath, JSON.stringify(manifest)); const problems = await validate(); assert.equal(hasFatal(problems), true); assert.ok(problems.some((problem) => problem.message.includes('no Pages'))); }); it('reports the data directory layout it inspected', async () => { const paths = sitePaths(dataDir); assert.equal(paths.drafts, path.join(dataDir, 'drafts')); assert.equal(paths.published, path.join(dataDir, 'published')); assert.equal(paths.originals, path.join(dataDir, 'originals')); }); });