import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import ts from 'typescript'; import { compileFunction } from 'node:vm'; const source = fs.readFileSync( new URL('../../resources/js/lib/profile-json.ts', import.meta.url), 'utf8', ); const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022, }, }).outputText; const module = { exports: {} }; compileFunction(output, ['exports'])(module.exports); const { profileJson, ProfileValidationError } = module.exports; async function transport(response, run) { const fetch = globalThis.fetch, document = globalThis.document; let count = 0; globalThis.document = { cookie: 'XSRF-TOKEN=synthetic' }; globalThis.fetch = async () => { count++; return response; }; try { await run(); assert.equal(count, 1); } finally { globalThis.fetch = fetch; globalThis.document = document; } } await test('profile HTML failures retain reauthentication without JSON parser errors or automatic retry', async () => { await transport( new Response('authentication required', { status: 403, headers: { 'X-Portal-Reauthentication': 'required' }, }), async () => { await assert.rejects( profileJson('/synthetic'), (e) => e instanceof ProfileValidationError && e.reauthenticate && Object.keys(e.fields).length === 0, ); }, ); }); await test('profile validation preserves field errors and server message', async () => { await transport( new Response( JSON.stringify({ message: 'Invalid synthetic field', errors: { name: ['Required', 'Too short'] }, }), { status: 422 }, ), async () => { await assert.rejects( profileJson('/synthetic'), (e) => e instanceof ProfileValidationError && e.message === 'Invalid synthetic field' && e.fields.name === 'Required Too short', ); }, ); }); await test('profile rejects invalid successful response and explains expired sessions', async () => { for (const status of [200, 419]) await transport( new Response('unexpected', { status }), async () => { await assert.rejects( profileJson('/synthetic'), (e) => e instanceof ProfileValidationError && (status !== 419 || /expirou/.test(e.message)), ); }, ); }); await test('profile accepts no-content success', async () => { await transport(new Response(null, { status: 204 }), async () => assert.equal(await profileJson('/synthetic'), null), ); }); await test('profile reads ignore malformed CSRF while writes fail before transport with a useful error', async () => { const oldFetch = globalThis.fetch, oldDocument = globalThis.document; let calls = 0; try { globalThis.document = { cookie: 'other=1;XSRF-TOKEN=%broken' }; globalThis.fetch = async (_url, options) => { calls++; assert.equal(options.method, 'GET'); assert.equal(options.headers['X-XSRF-TOKEN'], undefined); assert.equal(options.credentials, 'same-origin'); return new Response('{"ok":true}'); }; assert.deepEqual(await profileJson('/synthetic', {}, 'GET'), { ok: true, }); await assert.rejects( profileJson('/synthetic', {}), (e) => e instanceof ProfileValidationError && /validada/.test(e.message), ); assert.equal(calls, 1); } finally { globalThis.fetch = oldFetch; globalThis.document = oldDocument; } }); await test('profile writes decode CSRF cookies regardless of separator spacing', async () => { const oldFetch = globalThis.fetch, oldDocument = globalThis.document; try { globalThis.document = { cookie: 'other=1;XSRF-TOKEN=synthetic%3D' }; globalThis.fetch = async (_url, options) => { assert.equal(options.headers['X-XSRF-TOKEN'], 'synthetic='); assert.equal(options.credentials, 'same-origin'); assert.equal(options.body, JSON.stringify({ name: 'synthetic' })); return new Response('{"saved":true}'); }; assert.deepEqual( await profileJson('/synthetic', { name: 'synthetic' }), { saved: true }, ); } finally { globalThis.fetch = oldFetch; globalThis.document = oldDocument; } });