148 lines
5.2 KiB
JavaScript
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"/);
|
|
});
|