Files
Investidor-V2/tests/frontend/catalogue-request-errors.test.mjs
2026-11-02 20:31:15 +02:00

148 lines
5.2 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import fs from 'node:fs';
import { compileFunction } from 'node:vm';
import { createRequire } from 'node:module';
import ts from 'typescript';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
const require = createRequire(import.meta.url);
function source(name) {
return fs.readFileSync(
new URL('../../resources/js/pages/investment/' + name, import.meta.url),
'utf8',
);
}
function compile(source) {
const output = ts.transpileModule(source, {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2022,
jsx: ts.JsxEmit.ReactJSX,
},
}).outputText;
const module = { exports: {} };
compileFunction(output, ['require', 'module', 'exports'])(
require,
module,
module.exports,
);
return module.exports;
}
const shared = ts.createSourceFile(
'shared.tsx',
source('shared.tsx'),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX,
);
const apiNode = shared.statements.find(
(node) => ts.isFunctionDeclaration(node) && node.name?.text === 'api',
);
const { api } = compile(apiNode.getText(shared));
await test('API preserves JSON validation messages and handles empty and non-JSON responses', async () => {
const originalFetch = globalThis.fetch;
try {
globalThis.fetch = async () =>
new Response(JSON.stringify({ message: 'Campo obrigatorio' }), {
status: 422,
});
await assert.rejects(api('/synthetic'), /Campo obrigatorio/);
globalThis.fetch = async () =>
new Response('<html>proxy error</html>', { status: 502 });
await assert.rejects(api('/synthetic'), /concluir a opera/);
globalThis.fetch = async () =>
new Response('<html>expired</html>', { status: 419 });
await assert.rejects(api('/synthetic'), /sess.*expirou/);
globalThis.fetch = async () => new Response('', { status: 200 });
await assert.rejects(api('/synthetic'), /concluir a opera/);
globalThis.fetch = async () => new Response(null, { status: 204 });
assert.equal(await api('/synthetic'), null);
} finally {
globalThis.fetch = originalFetch;
}
});
await test('public GET ignores malformed CSRF cookie; writes preserve CSRF and multipart transport', async () => {
const originalFetch = globalThis.fetch;
const originalDocument = Object.getOwnPropertyDescriptor(
globalThis,
'document',
);
let request;
try {
Object.defineProperty(globalThis, 'document', {
value: { cookie: 'XSRF-TOKEN=%broken' },
configurable: true,
});
globalThis.fetch = async (url, options) => {
request = options;
return new Response('{"ok":true}');
};
assert.deepEqual(await api('/synthetic'), { ok: true });
await assert.rejects(api('/synthetic', {}), /validada/);
globalThis.document.cookie = 'XSRF-TOKEN=synthetic%3D';
const body = new FormData();
body.set('name', 'synthetic');
await api('/synthetic', body);
assert.equal(request.method, 'POST');
assert.equal(request.credentials, 'same-origin');
assert.equal(request.headers['X-XSRF-TOKEN'], 'synthetic=');
assert.equal(request.headers['Content-Type'], undefined);
assert.equal(request.body, body);
} finally {
globalThis.fetch = originalFetch;
if (originalDocument)
Object.defineProperty(globalThis, 'document', originalDocument);
else delete globalThis.document;
}
});
await test('catalogue pagination exposes applied links only after a successful search', () => {
const file = ts.createSourceFile(
'catalogue.tsx',
source('catalogue.tsx'),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX,
);
let nav;
function visit(node) {
if (
ts.isJsxElement(node) &&
node.openingElement.tagName.getText(file) === 'nav' &&
node.getText(file).includes('results.prev_page_url')
)
nav = node;
ts.forEachChild(node, visit);
}
visit(file);
assert.ok(nav);
const { Pagination } = compile(
`const Link='a'; export function Pagination({searchLoading,searchError,results,view}) {return (${nav.getText(file)});}`,
);
const props = {
view: 'list',
results: {
prev_page_url: '/previous?page=1',
next_page_url: '/previous?page=3',
},
};
for (const state of [
{ searchLoading: true, searchError: '' },
{ searchLoading: false, searchError: 'failure' },
]) {
const html = renderToStaticMarkup(
React.createElement(Pagination, { ...props, ...state }),
);
assert.doesNotMatch(html, /href=/);
}
const html = renderToStaticMarkup(
React.createElement(Pagination, {
...props,
searchLoading: false,
searchError: '',
}),
);
assert.match(html, /href="\/previous\?page=1"/);
assert.match(html, /href="\/previous\?page=3"/);
});