import { strict as assert } from 'node:assert'; import { readdir, readFile } from 'node:fs/promises'; import path from 'node:path'; import { describe, it } from 'node:test'; import { parseSiteConfig } from '../src/config.ts'; const root = path.join(import.meta.dirname, '..'); /** The inventory decides which Sites exist; the playbook names the vars file. */ async function inventory(): Promise<{ name: string; varFile: string }[]> { const text = await readFile(path.join(root, 'ansible', 'hosts'), 'utf8'); const inMenus = text.split(/^\[/m).find((block) => block.startsWith('menus]')) ?? ''; return inMenus .split('\n') .slice(1) .map((line) => line.trim()) .filter((line) => line !== '' && !line.startsWith('#')) .map((line) => { const [name, ...vars] = line.split(/\s+/); const varFile = vars.find((entry) => entry.startsWith('var_file=')) ?? ''; return { name: name!, varFile: varFile.replace('var_file=', '') }; }); } describe('every Site in the inventory is complete', () => { it('has a config file, a vars file, and a config that parses', async () => { const sites = await inventory(); assert.ok(sites.length >= 3, `expected the three Sites, found ${sites.length}`); for (const { name, varFile } of sites) { assert.notEqual(varFile, '', `${name} names no var_file`); const configPath = path.join(root, 'config', `${name}.site.json`); const raw = JSON.parse(await readFile(configPath, 'utf8')); const config = parseSiteConfig(raw); assert.equal( config.site.startsWith('menu.'), true, `${name}: site should be a menu hostname`, ); assert.ok(config.sections.length > 0, `${name} seeds no categories`); assert.match( config.theme.steel, /^#[0-9a-f]{6}$/i, `${name}: the theme should carry real colours`, ); const varsRaw = await readFile(path.join(root, 'ansible', varFile), 'utf8'); // Each vars file only needs to say where the Site runs; the templating is // quoted, which is what a first deploy got wrong. assert.match(varsRaw, /^title: /m, `${name}: ${varFile} has no title`); assert.match(varsRaw, /^host: 'menu\.\{\{ title \}\}'/m, `${name}: host is not quoted`); assert.match(varsRaw, /^host_directory: /m, `${name}: no host_directory`); } }); it('leaves no Site config without an inventory entry', async () => { const sites = new Set((await inventory()).map((site) => site.name)); const files = await readdir(path.join(root, 'config')); for (const file of files.filter((entry) => entry.endsWith('.site.json'))) { const name = file.replace('.site.json', ''); assert.ok(sites.has(name), `config/${file} belongs to no Site in the inventory`); } }); });