Files
Investidor-V2/tests/frontend/profile-json.test.mjs
T
2026-11-02 20:31:15 +02:00

141 lines
4.9 KiB
JavaScript

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('<html>authentication required</html>', {
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('<html>unexpected</html>', { 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;
}
});