Files
aia-website/www/tools/service-portal.test.mjs

237 lines
16 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { randomBytes, pbkdf2Sync, createCipheriv, createHmac } from 'node:crypto';
import { isSuppressed, reachedHalf, suppressionDuration } from '../src/utils/drawer-preference.ts';
import { serviceInquiryCopy } from '../src/data/service-inquiry.ts';
import { decryptPortalStream, encryptPortalStream, validatePortalPayload, portalAlgorithm } from '../src/utils/portal-crypto.mjs';
import { collectResource, resourceReferences, rewriteResources } from './portal-resources.mjs';
import { isFormACompletion } from '../src/utils/jotform-completion.ts';
import { portalLinkPresentation } from '../src/utils/portal-presentation.ts';
import {
PortalDownloadError,
fetchProtectedPortalResource,
portalDownloadErrorMessage,
portalRoleForStreamId,
protectedPortalResource,
recognizableDownloadFilename,
} from '../src/utils/portal-download.mjs';
import { applyRoleMappings, approvedMapping, summarizeMappings } from './portal-production.mjs';
test('completion envelope requires an exact approved origin, active frame window and string Form A ID', () => {
const source = {};
const check = (event, frame, active) => isFormACompletion(event, frame, active,
'https://form.jotform.com/262946078314059?formContext=consultation&language=en');
const data = { action: 'submission-completed', formID: '262946078314059' };
const event = { origin: 'https://form.jotform.com', source, data };
assert.equal(check(event, source, true), true);
assert.equal(check({ ...event, origin: 'https://hipaa-submit.jotform.com' }, source, true), true);
assert.equal(check({ ...event, data: JSON.stringify(data) }, source, true), true);
for (const change of [
{ origin: 'https://form.jotform.com.evil.invalid' }, { origin: 'https://hipaa-submit.jotform.com.evil.invalid' },
{ origin: 'https://other.jotform.com' }, { origin: 'null' }, { origin: 'http://form.jotform.com' },
{ source: {} }, { source: null }, { data: { ...data, formID: 'other' } },
{ data: { ...data, formID: 262946078314059 } }, { data: { ...data, action: 'load' } },
{ data: '{broken' }, { data: [] }, { data: null }, { data: 'x'.repeat(4097) },
]) assert.equal(check({ ...event, ...change }, source, true), false);
assert.equal(check(event, source, false), false);
assert.equal(check(event, null, true), false);
assert.equal(isFormACompletion(event, source, true, undefined), false);
assert.equal(isFormACompletion(event, source, true, 'broken'), false);
});
test('portal presentation translates only explicit button tokens/brand fills and contact protocols', () => {
assert.equal(portalLinkPresentation(['hs-button', 'untrusted'], '', 'https:'), 'portal-resource-button');
assert.equal(portalLinkPresentation([], 'rgb(255, 189, 89)', 'https:'), 'portal-resource-button');
assert.equal(portalLinkPresentation(['arbitrary'], 'url(https://example.invalid)', 'https:'), undefined);
assert.equal(portalLinkPresentation(['unrecognized-legacy'], '', 'https:', 'Operations Monthly Lineup Report - PM'), 'portal-resource-button');
assert.equal(portalLinkPresentation([], '', 'https:', 'Human Resources Daily Lineup Report - PM'), 'portal-resource-button');
assert.equal(portalLinkPresentation([], '', 'https:', 'Billing Daily Lineup Report - PM'), 'portal-resource-button');
assert.equal(portalLinkPresentation([], '', 'https:', 'Generic reports and documents'), undefined);
assert.equal(portalLinkPresentation([], '', 'https:', 'Monthly report'), undefined);
assert.equal(portalLinkPresentation([], '', 'mailto:', 'Operations Monthly Lineup Report - PM'), 'portal-contact-link');
assert.equal(portalLinkPresentation([], '', 'mailto:'), 'portal-contact-link');
assert.equal(portalLinkPresentation([], '', 'tel:'), 'portal-contact-link');
});
test('portal downloads use only the validated role and an in-memory authorization header', async () => {
assert.equal(portalRoleForStreamId('b'), 'staff');
assert.equal(portalRoleForStreamId('a'), 'management');
assert.equal(portalRoleForStreamId('fixture-staff'), undefined);
const origin = 'https://www.azinstitute4autism.com';
const staff = protectedPortalResource('/staff-resources/' + 'a'.repeat(64) + '.pdf', origin, "Staff's Safety & Training: Résumé (2026)!");
const management = protectedPortalResource('/management-resources/' + 'b'.repeat(64) + '.docx', origin, 'Management – Policies / Q&A #1');
assert.deepEqual(staff, {
role: 'staff', filename: 'a'.repeat(64) + '.pdf',
downloadFilename: 'Staffs-Safety-and-Training-Resume-2026.pdf',
url: origin + '/staff-resources/' + 'a'.repeat(64) + '.pdf',
});
assert.equal(management.downloadFilename, 'Management-Policies-Q-and-A-1.docx');
assert.equal(recognizableDownloadFilename('Client Check-In & Check-Out.xlsx', 'c'.repeat(64) + '.xlsx'), 'Client-Check-In-and-Check-Out.xlsx');
assert.equal(recognizableDownloadFilename('../../', 'd'.repeat(64) + '.pdf'), 'AIA-document.pdf');
assert.equal(recognizableDownloadFilename('No server extension', 'e'.repeat(64)), undefined);
assert.equal(protectedPortalResource('/staff-resources/nested/file.pdf', origin), undefined);
assert.equal(protectedPortalResource('/staff-resources/file.pdf?code=forbidden', origin), undefined);
assert.equal(protectedPortalResource('https://other.invalid/staff-resources/file.pdf', origin), undefined);
const code = 'synthetic browser memory only';
let captured;
const result = await fetchProtectedPortalResource(staff, { role: 'staff', code }, async (url, options) => {
captured = { url, options };
return new Response('synthetic document', {
// nginx may expose the hash name; the approved label remains authoritative.
headers: { 'content-disposition': `attachment; filename="${staff.filename}"` },
});
});
assert.equal(captured.url, staff.url);
assert.equal(captured.url.includes(code), false);
assert.equal(captured.options.method, 'GET');
assert.equal(captured.options.credentials, 'omit');
assert.equal(captured.options.cache, 'no-store');
assert.equal(captured.options.redirect, 'error');
assert.equal(captured.options.referrerPolicy, 'no-referrer');
assert.match(captured.options.headers.Authorization, /^Basic /);
assert.equal(result.filename, 'Staffs-Safety-and-Training-Resume-2026.pdf');
assert.equal(await result.blob.text(), 'synthetic document');
for (const [resource, credential] of [
[management, { role: 'staff', code }],
[staff, { role: 'management', code }],
]) {
let called = false;
await assert.rejects(
fetchProtectedPortalResource(resource, credential, async () => { called = true; return new Response(); }),
(error) => error instanceof PortalDownloadError && error.code === 'role',
);
assert.equal(called, false, 'cross-role credentials are never transmitted');
}
await assert.rejects(
fetchProtectedPortalResource(staff, { role: 'staff', code }, async () => new Response('', { status: 401 })),
(error) => error instanceof PortalDownloadError && error.code === 'unauthorized',
);
assert.match(portalDownloadErrorMessage(new PortalDownloadError('unauthorized')), /session could not authorize/);
assert.match(portalDownloadErrorMessage(new PortalDownloadError('role')), /not available from this portal/);
});
test('staff template presentation follows sanitized headings, not original HubSpot classes', async () => {
const renderer = await fs.readFile('src/scripts/employee-portal.ts', 'utf8');
const css = await fs.readFile('src/components/pages/EmployeePortal.astro', 'utf8');
assert.match(renderer, /enhanceStaffPortalPresentation\(output\)/);
assert.match(renderer, /'management templates'/);
assert.match(renderer, /fragment\.querySelectorAll<HTMLAnchorElement>\('a\[href\]'\)/);
assert.match(renderer, /templatesList\?\.classList\.add\('portal-template-grid'\)/);
assert.match(css, /grid-template-columns: repeat\(2, minmax\(0, 1fr\)\)/);
assert.match(css, /@media \(max-width: 767px\)/);
assert.doesNotMatch(css, /ul\.portal-template-grid a\.portal-resource-button/);
assert.match(renderer, /items\.every\(\(item\) => item\.querySelector\('a\.portal-resource-button'\)\)/);
assert.match(renderer, /list\.classList\.add\('portal-resource-list'\)/);
assert.match(css, /\.portal-resource-list > li::marker\)\s*\{\s*content: none;/);
assert.match(css, /\.employee-portal__content:focus\s*\{ outline: none;/);
assert.match(css, /input:focus-visible\s*\{ outline: 3px solid/);
assert.match(css, /:global\(a:focus-visible\)\s*\{ outline: 3px solid/);
});
test('global preference validates expiry, corrupted values and clock anomalies', () => {
const now = 10000000000;
for (const raw of [null, '', 'NaN', '-1', '1.5', 'Infinity', String(now), String(now - 1), String(now + suppressionDuration + 1)]) assert.equal(isSuppressed(raw, now), false);
assert.equal(isSuppressed(String(now + suppressionDuration), now), true);
assert.equal(isSuppressed(String(now + 1), now), true);
});
test('half-scroll handles short, restored, resized and overscrolled pages', () => {
assert.equal(reachedHalf(0, 500, 800), false);
assert.equal(reachedHalf(100, 800, 800), false);
assert.equal(reachedHalf(499, 1800, 800), false);
assert.equal(reachedHalf(500, 1800, 800), true);
assert.equal(reachedHalf(-10, 1800, 800), false);
assert.equal(reachedHalf(500, 2800, 800), false);
});
test('four service configurations are bilingual without adding an Academy route', async () => {
assert.equal(Object.keys(serviceInquiryCopy).length, 4);
for (const variants of Object.values(serviceInquiryCopy)) for (const copy of Object.values(variants)) {
assert.ok(copy.heading && copy.introduction); assert.ok(!copy.introduction.includes('—'));
}
const { formAIframePlacements } = await import('../src/utils/form-integration.ts');
assert.equal(formAIframePlacements.filter(p => p.context === 'consultation').length, 9);
assert.ok(!formAIframePlacements.some((p) => p.route.includes('preparatory')));
});
test('legacy independent crypto fixture and two-view separation round-trip', async () => {
const pass = randomBytes(32).toString('base64'), other = randomBytes(32).toString('base64');
const salt = randomBytes(16), iv = randomBytes(16), keys = pbkdf2Sync(pass, salt, 200000, 64, 'sha256');
const cipher = createCipheriv('aes-256-cbc', keys.subarray(0, 32), iv);
const ct = Buffer.concat([cipher.update('<h2>Synthetic staff</h2>'), cipher.final()]);
const mac = createHmac('sha256', keys.subarray(32)).update(Buffer.concat([iv, ct])).digest();
const stream = { id: 'synthetic-staff', iter: 200000, salt: salt.toString('base64'), iv: iv.toString('base64'), hmac: mac.toString('base64'), ct: ct.toString('base64') };
const second = await encryptPortalStream('synthetic-management', '<h2>Synthetic management</h2>', other);
validatePortalPayload({ alg: portalAlgorithm, streams: [stream, second] });
assert.equal(await decryptPortalStream(stream, pass), '<h2>Synthetic staff</h2>');
assert.equal(await decryptPortalStream(second, pass), null);
assert.equal(await decryptPortalStream(stream, other), null);
assert.equal(await decryptPortalStream(second, other), '<h2>Synthetic management</h2>');
assert.equal(await decryptPortalStream({ ...stream, hmac: randomBytes(32).toString('base64') }, pass), null);
assert.throws(() => validatePortalPayload({ alg: portalAlgorithm, streams: [stream, stream] }));
});
test('resource extraction reconciles duplicate views and rewrites only approved implemented links', () => {
const html = '<h2>Synthetic</h2><a href="/hubfs/example.pdf">Example</a><a href="/hubfs/example.pdf">Again</a><a href="https://unapproved.invalid/form">Other</a>';
const refs = resourceReferences(html, 'synthetic');
assert.equal(refs.length, 2); assert.equal(new Set(refs.map((r) => r.sourceUrl)).size, 1);
const record = { references: refs, rewriteApproved: true, mappingApproved: true, deliveryPath: '/staff-resources/example.pdf', classification: 'STAFF-INTERNAL' };
assert.equal(rewriteResources(html, [record], 'staff').html, html);
const result = rewriteResources(html, [{ ...record, accessPolicyApproved: true }], 'staff');
assert.equal(result.count, 2); assert.ok(result.html.includes('>Again</a>')); assert.ok(result.html.includes('https://unapproved.invalid/form'));
assert.equal(rewriteResources(html, []).html, html);
});
test('role-based resource mapping separates source approval from later server verification', () => {
const record = {
sourceUrl: 'https://www.azinstitute4autism.com/hubfs/guide.pdf', originalPath: '/hubfs/guide.pdf',
localFile: 'a'.repeat(64), references: [{ view: 'staff-stream' }, { view: 'management-stream' }],
};
const roles = { staff: 'staff-stream', management: 'management-stream' };
const summary = summarizeMappings({ resources: [record] }, roles);
assert.equal(summary.shared, 1); assert.equal(summary.ambiguous, 0);
const mapped = applyRoleMappings({ resources: [record] }, roles, { approveShared: true });
assert.equal(mapped.resources[0].installMappings.length, 2);
for (const role of ['staff', 'management']) {
const mapping = approvedMapping(mapped.resources[0], role);
assert.ok(mapping); assert.equal(mapping.destinationInstalled, false);
assert.equal(mapping.destinationAccessVerified, false);
}
});
test('private collector validates binary, reuses hash, rejects errors and unapproved redirects', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-private-test-'));
try {
const resource = { sourceUrl: 'https://www.azinstitute4autism.com/hubfs/synthetic.pdf' };
const good = await collectResource(resource, dir, undefined, async () => new Response('%PDF-1.7\nSynthetic', { headers: { 'content-type': 'application/pdf' } }));
assert.equal(good.downloadStatus, 'downloaded');
const again = await collectResource(good, dir, undefined, () => { throw new Error('must not fetch'); });
assert.equal(again.downloadStatus, 'verified-existing');
await assert.rejects(collectResource(resource, dir, undefined, async () => new Response('<html>Error</html>', { headers: { 'content-type': 'application/pdf' } })));
await assert.rejects(collectResource(resource, dir, undefined, async () => new Response('', { status: 302, headers: { location: 'https://unapproved.invalid/hubfs/file' } })));
} finally { await fs.rm(dir, { recursive: true }); }
});
test('portal input isolates local preview from explicit indexing production payloads', async () => {
const { readPortalInput } = await import('../src/utils/portal-input.mjs');
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-portal-input-'));
try {
const pass = randomBytes(24).toString('base64');
const streams = await Promise.all(['one','two'].map((id) => encryptPortalStream(id, '<p>Synthetic</p>', pass)));
const file = path.join(dir, 'fixture.json'); await fs.writeFile(file, JSON.stringify({ alg: portalAlgorithm, streams }), { mode: 0o600 });
const options = { localApproved: 'true', indexing: 'false', localFile: file };
assert.ok(readPortalInput(options));
// Form modes cannot substitute for the independent private-input approval.
for (const mode of ['live', 'off', 'mock']) {
assert.ok(readPortalInput({ ...options, mode }));
assert.equal(readPortalInput({ indexing: 'false', localFile: file, mode }), undefined);
}
for (const changed of [{ localApproved: undefined }, { localApproved: 'false' }]) assert.equal(readPortalInput({ ...options, ...changed }), undefined);
assert.throws(() => readPortalInput({ ...options, indexing: 'true' }), /publication mode/);
assert.ok(readPortalInput({ productionApproved: 'true', indexing: 'true', productionFile: file }));
assert.throws(() => readPortalInput({ productionApproved: 'true', indexing: 'true' }), /required/);
assert.throws(() => readPortalInput({ localApproved: 'true', productionApproved: 'true', indexing: 'false', localFile: file, productionFile: file }), /either/);
await fs.writeFile(file, '<h1>Not encrypted</h1>');
assert.throws(() => readPortalInput(options), /missing or malformed/);
} finally { await fs.rm(dir, { recursive: true }); }
});