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