Files
aia-website/www/tools/phase10-artifact.test.mjs
T

200 lines
12 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { verifyArtifact } from './phase10-artifact-verify.mjs';
import { createHash } from 'node:crypto';
import { encryptPortalStream, portalAlgorithm } from '../src/utils/portal-crypto.mjs';
async function fixture(mode = 'staging', forms = 'live') {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-phase10-artifact-'));
const contactLandingFrames = [
['contact/index.html', 'https://form.jotform.com/262946078314059?formContext=contact&language=en'],
['es/contact/index.html', 'https://form.jotform.com/262946078314059?formContext=contact&language=es'],
['lp/aba-therapy/index.html', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['lp/aba-insurance/index.html', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['lp/aba-ahcccs/index.html', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['lp/aba-private-pay/index.html', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['lp/early-intervention/index.html', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
];
const consultationFrames = [
['client-consultation/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'],
['es/client-consultation/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'],
['schedule-consultation/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'],
];
const consultationDrawers = [
['aba-therapy/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'],
['autism-evaluations/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'],
['learner-social-club/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=en'],
['es/aba-therapy/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'],
['es/autism-evaluations/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'],
['es/learner-social-club/index.html', 'https://form.jotform.com/262946078314059?formContext=consultation&language=es'],
];
const robots = mode === 'staging' ? 'noindex,nofollow' : 'index,follow';
const pages = [['index.html', robots, '']];
if (mode === 'staging') pages.push(['lp/index.html', robots, '']);
if (forms === 'live') {
pages.push(...[...contactLandingFrames,...consultationFrames].map(([file, frame]) =>
[file, robots, `<section data-jotform-form-a data-form-mode="live"><iframe src="${frame}"></iframe></section>`]));
pages.push(...consultationDrawers.map(([file, frame]) =>
[file, robots, `<section data-jotform-form-a data-form-mode="live" data-jotform-deferred="true"><iframe data-jotform-iframe-url="${frame}"></iframe></section>`]));
}
for (const [file, robots, body] of pages) {
const target = path.join(root, file);
await fs.mkdir(path.dirname(target), { recursive: true });
await fs.writeFile(target, `<!doctype html><html><head><title>${file}</title><meta name="robots" content="${robots}"><link rel="canonical" href="https://www.azinstitute4autism.com/${file}"></head><body><main><h1>${file}</h1>${body}</main></body></html>`);
}
await fs.writeFile(path.join(root, 'sitemap.xml'), mode === 'staging' ? '<urlset></urlset>' : '<urlset><url><loc>https://www.azinstitute4autism.com/</loc></url></urlset>');
return root;
}
test('staging artifact requires noindex, empty sitemap, no hreflang and review index', async () => {
const result = await verifyArtifact(await fixture('staging'), 'staging');
assert.equal(result.passed, true);
assert.equal(result.sitemapUrls, 0);
assert.equal(result.reviewIndex, true);
});
test('indexing artifact requires sitemap and excludes review index', async () => {
const result = await verifyArtifact(await fixture('indexing'), 'indexing');
assert.equal(result.passed, true);
assert.equal(result.sitemapUrls, 1);
assert.equal(result.reviewIndex, false);
});
test('live forms have ten immediate and six deferred placements independently of publication mode', async () => {
for (const mode of ['staging','indexing']) {
const result = await verifyArtifact(await fixture(mode), mode);
assert.equal(result.passed, true, result.errors.join('; '));
assert.equal(result.formAIframes, 10);
assert.equal(result.deferredFormAIframes, 6);
assert.equal(result.reviewIndex, mode === 'staging');
}
});
test('off and mock artifacts contain no real frames independently of publication mode', async () => {
for (const mode of ['staging','indexing']) for (const forms of ['off','mock']) {
const root = await fixture(mode, forms);
const result = await verifyArtifact(root, mode, forms);
assert.equal(result.passed, true);
assert.equal(result.formAIframes + result.deferredFormAIframes, 0);
await fs.appendFile(path.join(root, 'index.html'), '<iframe src="https://form.jotform.com/262946078314059"></iframe>');
assert.equal((await verifyArtifact(root,mode,forms)).passed,false);
}
});
test('indexing artifact rejects an invalid Form A URL', async () => {
const root = await fixture('indexing');
const contact = path.join(root, 'contact/index.html');
const html = await fs.readFile(contact, 'utf8');
await fs.writeFile(contact, html.replace('formContext=contact&language=en', 'formContext=landing&language=en'));
const result = await verifyArtifact(root, 'indexing');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('invalid approved Form A iframe URL')));
});
test('staging artifact rejects active native form actions', async () => {
const root = await fixture('staging');
await fs.appendFile(path.join(root, 'index.html'), '<form action="https://example.invalid/submit"></form>');
const result = await verifyArtifact(root, 'staging');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('native form action')));
});
test('artifact rejects production API origin even when Form A is approved', async () => {
const root = await fixture('staging');
await fs.appendFile(path.join(root, 'index.html'), '<div data-form-mode="live">https://api.azinstitute4autism.com</div>');
const result = await verifyArtifact(root, 'staging');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('unsafe artifact signals')));
});
test('live counters allow only the reviewed API origin and explicit live mode', async () => {
const root = await fixture('indexing');
await fs.appendFile(path.join(root, 'index.html'), '<div data-counter data-counter-mode="live" data-api-base="https://api.azinstitute4autism.com"></div>');
assert.equal((await verifyArtifact(root, 'indexing', 'live', { mode: 'none' }, 'live')).passed, true);
assert.equal((await verifyArtifact(root, 'indexing', 'live', { mode: 'none' }, 'off')).passed, false);
const wrong = await fixture('indexing');
await fs.appendFile(path.join(wrong, 'index.html'), '<div data-counter data-counter-mode="live" data-api-base="https://other.invalid"></div>');
const result = await verifyArtifact(wrong, 'indexing', 'live', { mode: 'none' }, 'live');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('approved mode or API origin')));
});
test('artifact rejects Form A identity outside the exact approved HTML routes', async () => {
const root = await fixture('staging');
await fs.writeFile(path.join(root, 'assets.js'), 'https://form.jotform.com/262946078314059?formContext=contact');
const result = await verifyArtifact(root, 'staging');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('outside approved routes')));
});
test('artifact rejects root-relative URLs with terminal slashes', async () => {
const root = await fixture('staging');
await fs.appendFile(path.join(root, 'index.html'), '<a href="/lp/aba-therapy/">Campaign</a>');
const result = await verifyArtifact(root, 'staging');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('root-relative URL(s) end in a slash')));
});
test('artifact digest is deterministic for unchanged files', async () => {
const root = await fixture('staging');
const first = await verifyArtifact(root, 'staging');
const second = await verifyArtifact(root, 'staging');
assert.equal(first.artifactSha256, second.artifactSha256);
assert.equal(first.artifactFiles, second.artifactFiles);
});
test('release verification rejects private portal resources and encrypted payloads', async () => {
const root = await fixture('staging');
await fs.mkdir(path.join(root, 'private/employee-portal'), { recursive: true });
await fs.writeFile(path.join(root, 'private/employee-portal/manifest.json'), '{}');
await fs.appendFile(path.join(root, 'index.html'), '<script type="application/json" data-portal-payload>{"streams":[]}</script>');
const result = await verifyArtifact(root, 'staging');
assert.equal(result.passed, false);
assert.ok(result.errors.some((error) => error.includes('portal ciphertext')));
assert.ok(result.errors.some((error) => error.includes('private/legacy')));
});
test('approved production portal accepts only the expected two-stream ciphertext on the noindex route', async () => {
const root = await fixture('indexing');
const payload = { alg: portalAlgorithm, streams: [
await encryptPortalStream('staff', '<p>Synthetic staff</p>', 'synthetic-staff-code'),
await encryptPortalStream('management', '<p>Synthetic management</p>', 'synthetic-management-code'),
] };
const portal = path.join(root, 'employee-portal/index.html');
await fs.mkdir(path.dirname(portal), { recursive: true });
await fs.writeFile(portal, '<!doctype html><html><head><title>Portal</title><meta name="robots" content="noindex,follow"><link rel="canonical" href="https://www.azinstitute4autism.com/employee-portal"></head><body><script data-portal-payload type="application/json">' + JSON.stringify(payload) + '</script></body></html>');
const sha256 = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
const result = await verifyArtifact(root, 'indexing', 'live', { mode: 'production', sha256 });
assert.equal(result.passed, true, result.errors.join('; '));
await fs.appendFile(portal, '<!-- changed -->');
assert.equal((await verifyArtifact(root, 'indexing', 'live', { mode: 'production', sha256 })).passed, true);
const corrupted = JSON.parse(JSON.stringify(payload));
corrupted.streams[0].ct = (corrupted.streams[0].ct[0] === 'A' ? 'B' : 'A') + corrupted.streams[0].ct.slice(1);
await fs.writeFile(portal, '<!doctype html><html><head><title>Portal</title><meta name="robots" content="noindex,follow"><link rel="canonical" href="https://www.azinstitute4autism.com/employee-portal"></head><body><script data-portal-payload type="application/json">' + JSON.stringify(corrupted) + '</script></body></html>');
assert.equal((await verifyArtifact(root, 'indexing', 'live', { mode: 'production', sha256 })).passed, false);
});
test('artifact rejects extra routes, wrong origins and prematurely loaded drawers', async () => {
const root = await fixture();
await fs.appendFile(path.join(root,'index.html'), '<iframe src="https://form.jotform.com/262946078314059?formContext=contact&language=en"></iframe>');
assert.equal((await verifyArtifact(root)).passed,false);
const clean = await fixture();
const file = path.join(clean,'aba-therapy/index.html');
await fs.writeFile(file,(await fs.readFile(file,'utf8')).replace('data-jotform-iframe-url=', 'src='));
assert.ok((await verifyArtifact(clean)).errors.some(e => e.includes('before activation')));
const other = await fixture();
await fs.appendFile(path.join(other,'index.html'), '<iframe src="https://unapproved.invalid/form"></iframe>');
assert.ok((await verifyArtifact(other)).errors.some(e => e.includes('unauthorized iframe')));
});
test('obsolete artifact modes are rejected with separate publication/form guidance', async () => {
await assert.rejects(verifyArtifact('/unused','isolated-test'), /--forms=live\|off\|mock/);
await assert.rejects(verifyArtifact('/unused','staging','invalid'), /live, off, or mock/);
await assert.rejects(verifyArtifact('/unused','staging','live',{ mode: 'none' },'invalid'), /counters must be live/);
});