import fs from 'node:fs/promises';
import path from 'node:path';
import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { fileURLToPath } from 'node:url';
const root = path.dirname(fileURLToPath(import.meta.url));
const dir = process.argv[2] === 'live' ? '/home/agent/services/license/public' : path.join(root, 'preview');
const manifest = JSON.parse(await fs.readFile(path.join(dir, 'docs/manifest.json'), 'utf8'));
let links = 0;
for (const page of manifest.pages) {
const html = await fs.readFile(path.join(dir, 'docs', page.output), 'utf8');
assert.match(html, /
]*>[^<]+<\/script>|href="javascript:|onerror=/i);
for (const match of html.matchAll(/(?:href|src)="(\/docs\/[^"#]+)(?:#[^"]*)?"/g)) { await fs.access(path.join(dir, match[1])); links++; }
}
console.log(JSON.stringify({ pages:manifest.pages.length, validPublicLinks:links, commits:manifest.commits, drafts:manifest.drafts }));
for (const image of manifest.images || []) {
const data = await fs.readFile(path.join(dir, 'docs', image.output));
assert.equal(createHash('sha256').update(data).digest('hex'), image.sha256);
if (process.argv[2] === 'live') {
const response = await fetch(`https://license.enelix.ch/docs/${image.output}`);
assert.equal(response.status, 200); assert.match(response.headers.get('content-type'), /image\/png/);
assert.equal(createHash('sha256').update(Buffer.from(await response.arrayBuffer())).digest('hex'), image.sha256);
}
}
if (process.argv[2] === 'live') {
for (const route of ['/docs/index.html', '/docs/erste-schritte.html', '/docs/installation.html', '/docs/faq.html', '/docs/preise.html', '/docs/ems/Manager.html', '/docs/utils/Shelly-Modul.html', '/api/catalog', '/api/session', '/']) {
const response = await fetch(`https://license.enelix.ch${route}`, { signal:AbortSignal.timeout(15000) });
assert.equal(response.status, 200, route);
const body = await response.text();
if (route === '/api/session') assert.equal(JSON.parse(body).authenticated, false);
if (route.startsWith('/docs/')) { assert.match(body, /Enelix Dokumentation/); assert.equal(response.headers.get('cache-control'), 'no-store'); }
if (route === '/') assert.match(body, /public-doc-links/);
console.log(JSON.stringify({ anonymous:route, status:response.status }));
}
} else {
const before = createHash('sha256').update(await fs.readFile(path.join(dir, 'docs/index.html'))).digest('hex');
const failureState = await fs.mkdtemp(path.join(root, 'failure-test-'));
try {
const mirror = path.join(failureState, 'ems.git'); await fs.mkdir(mirror);
execFileSync('git', ['init', '--bare', '--quiet'], { cwd:mirror });
execFileSync('git', ['remote', 'add', 'origin', 'https://invalid.example/documentation.git'], { cwd:mirror });
const result = spawnSync(process.execPath, [path.join(root, 'sync.mjs')], { encoding:'utf8', env:{ ...process.env, DOCS_PUBLIC_DIR:dir, DOCS_STATE_DIR:failureState, DOCS_ALLOW_DRAFTS:'true' } });
assert.equal(result.status, 1);
const after = createHash('sha256').update(await fs.readFile(path.join(dir, 'docs/index.html'))).digest('hex');
assert.equal(after, before);
const status = JSON.parse(await fs.readFile(path.join(dir, 'docs-status.json'), 'utf8'));
assert.equal(status.ok, false); assert.ok(status.lastSuccess);
console.log('Failure regression passed: last-good pages retained, failure visible, lock released.');
await assert.rejects(fs.access(path.join(failureState, 'sync.lock')));
} finally { await fs.rm(failureState, { recursive:true, force:true }); }
}