355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
import { compileFunction } from 'node:vm';
|
|
import ts from 'typescript';
|
|
import React from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
const require = createRequire(import.meta.url),
|
|
module = { exports: {} };
|
|
const output = ts.transpileModule(
|
|
fs.readFileSync(
|
|
new URL(
|
|
'../../resources/js/components/opportunity-technical-incidents.tsx',
|
|
import.meta.url,
|
|
),
|
|
'utf8',
|
|
),
|
|
{
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2022,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
},
|
|
},
|
|
).outputText;
|
|
compileFunction(output, ['require', 'module', 'exports'])(
|
|
(name) =>
|
|
name === '@/lib/technical-incident-labels'
|
|
? load('resources/js/lib/technical-incident-labels.ts')
|
|
: require(name),
|
|
module,
|
|
module.exports,
|
|
);
|
|
const render = (incidents) =>
|
|
renderToStaticMarkup(
|
|
React.createElement(module.exports.default, { incidents }),
|
|
);
|
|
const base = {
|
|
reference: 'SYNTHETIC-reference',
|
|
operation: 'submit',
|
|
version: 4,
|
|
occurred_at: '2026-11-03',
|
|
error_code: 'database_failure',
|
|
};
|
|
test('no incident produces no empty form or duplicate question', () =>
|
|
assert.equal(render([]), ''));
|
|
test('the reader identifies the attempt and support scope without a business retry or decision', () => {
|
|
const html = render([base]);
|
|
assert.match(html, /Submissão para validação/);
|
|
assert.match(html, /versão observada 4/);
|
|
assert.match(html, /SYNTHETIC-reference/);
|
|
assert.match(html, /Consulte o estado actual antes de repetir/);
|
|
assert.match(html, /Suporte técnico consulta os incidentes/);
|
|
assert.doesNotMatch(html, /<button|<form|href=/);
|
|
});
|
|
|
|
function load(path, mocks = {}) {
|
|
const result = { exports: {} };
|
|
const code = ts.transpileModule(
|
|
fs.readFileSync(new URL('../../' + path, import.meta.url), 'utf8'),
|
|
{
|
|
compilerOptions: {
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2022,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
},
|
|
},
|
|
).outputText;
|
|
compileFunction(code, ['require', 'module', 'exports'])(
|
|
(name) =>
|
|
mocks[name] ??
|
|
(name === '@/components/technical-incident-notice'
|
|
? { default: () => null }
|
|
: undefined) ??
|
|
(name.startsWith('@/')
|
|
? load('resources/js/' + name.slice(2) + '.ts')
|
|
: require(name)),
|
|
result,
|
|
result.exports,
|
|
);
|
|
return result.exports;
|
|
}
|
|
test('the actual profile navigation exposes the support queue only through the server capability', () => {
|
|
const { profileLinks } = load(
|
|
'resources/js/components/profile-navigation.tsx',
|
|
{ '@inertiajs/react': { Link: 'a', usePage: () => ({ props: {} }) } },
|
|
);
|
|
const access = {
|
|
mine: null,
|
|
create: null,
|
|
request: '/perfil/promotor',
|
|
review: null,
|
|
administration: null,
|
|
};
|
|
assert.equal(
|
|
profileLinks(access).some(
|
|
(link) => link.href === '/suporte-tecnico/incidentes',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
profileLinks({
|
|
...access,
|
|
technicalSupport: '/suporte-tecnico/incidentes',
|
|
}).find((link) => link.href === '/suporte-tecnico/incidentes').title,
|
|
'Suporte técnico',
|
|
);
|
|
});
|
|
test('the actual queue presents minimal metadata and an honest empty state without private process links', () => {
|
|
const Queue = load('resources/js/pages/profiles/technical-support.tsx', {
|
|
'@inertiajs/react': {
|
|
Link: ({ href, children, ...rest }) =>
|
|
React.createElement('a', { href, ...rest }, children),
|
|
},
|
|
'../management/shared': {
|
|
Management: ({ children }) =>
|
|
React.createElement('main', null, children),
|
|
Pagination: () => null,
|
|
},
|
|
}).default;
|
|
const page = (data) =>
|
|
renderToStaticMarkup(
|
|
React.createElement(Queue, {
|
|
incidents: {
|
|
data,
|
|
total: data.length,
|
|
current_page: 1,
|
|
last_page: 1,
|
|
prev_page_url: null,
|
|
next_page_url: null,
|
|
},
|
|
}),
|
|
);
|
|
assert.match(page([]), /Não existem incidentes disponíveis neste âmbito/);
|
|
const html = page([base]);
|
|
assert.match(html, /SYNTHETIC-reference/);
|
|
assert.match(html, /não concede acesso aos processos ou documentos/);
|
|
assert.doesNotMatch(html, /minhas-oportunidades|<form|<button/);
|
|
});
|
|
test('the support queue exposes the authorised treatment link and preserves its state and page', () => {
|
|
const Queue = load('resources/js/pages/profiles/technical-support.tsx', {
|
|
'@inertiajs/react': {
|
|
Link: ({ href, children, ...rest }) =>
|
|
React.createElement('a', { href, ...rest }, children),
|
|
},
|
|
'../management/shared': {
|
|
Management: ({ children }) =>
|
|
React.createElement('main', null, children),
|
|
Pagination: () => null,
|
|
},
|
|
}).default;
|
|
const html = renderToStaticMarkup(
|
|
React.createElement(Queue, {
|
|
filters: { status: 'resolved' },
|
|
incidents: {
|
|
data: [
|
|
{
|
|
...base,
|
|
details_url: '/suporte-tecnico/incidentes/one',
|
|
handling: {
|
|
status: 'resolved',
|
|
assignee_name: 'SYNTHETIC support',
|
|
},
|
|
},
|
|
],
|
|
total: 1,
|
|
current_page: 3,
|
|
last_page: 3,
|
|
prev_page_url: null,
|
|
next_page_url: null,
|
|
},
|
|
}),
|
|
);
|
|
assert.ok(
|
|
html.includes(
|
|
'href="/suporte-tecnico/incidentes/one?page=3&status=resolved"',
|
|
),
|
|
);
|
|
assert.match(html, /Estado técnico: Resolvido/);
|
|
assert.match(html, /aria-current="page"/);
|
|
assert.doesNotMatch(html, /minhas-oportunidades/);
|
|
});
|
|
test('the existing JSON transport preserves the incident reference and never repeats a failed write', async () => {
|
|
const transport = load('resources/js/lib/profile-json.ts');
|
|
const oldFetch = globalThis.fetch,
|
|
oldDocument = globalThis.document;
|
|
let calls = 0;
|
|
globalThis.document = { cookie: 'XSRF-TOKEN=SYNTHETIC' };
|
|
globalThis.fetch = async () => {
|
|
calls++;
|
|
return {
|
|
ok: false,
|
|
status: 503,
|
|
headers: { get: () => null },
|
|
json: async () => ({
|
|
message: 'SYNTHETIC incident reference: one',
|
|
reference: 'one',
|
|
incident_recorded: true,
|
|
}),
|
|
};
|
|
};
|
|
try {
|
|
await assert.rejects(
|
|
transport.profileJson('/minhas-oportunidades/1/workflow', {
|
|
action: 'submit',
|
|
}),
|
|
/SYNTHETIC incident reference: one/,
|
|
);
|
|
assert.equal(calls, 1);
|
|
} finally {
|
|
globalThis.fetch = oldFetch;
|
|
globalThis.document = oldDocument;
|
|
}
|
|
});
|
|
test('publication is distinct and untrusted reference text remains escaped', () => {
|
|
const html = render([
|
|
{
|
|
...base,
|
|
operation: 'publish',
|
|
error_code: 'workflow_unavailable',
|
|
reference: '<img src=x onerror=alert(1)>',
|
|
},
|
|
]);
|
|
assert.match(html, /Publicação/);
|
|
assert.match(html, /Falha técnica/);
|
|
assert.doesNotMatch(html, /<img/);
|
|
assert.match(html, /<img/);
|
|
});
|
|
|
|
for (const [operation, label] of [
|
|
['upload_document', 'Carregamento de documento'],
|
|
['upload_image', 'Carregamento de imagem'],
|
|
['prepare_image', 'Preparação de imagens WebP'],
|
|
]) {
|
|
test('the reader distinguishes ' + operation + ' from publication', () => {
|
|
const html = render([{ ...base, operation }]);
|
|
assert.ok(html.includes(label));
|
|
assert.doesNotMatch(html, />Publicação/);
|
|
});
|
|
}
|
|
|
|
test('the support link exposes only a valid count alongside the server capability', () => {
|
|
const { profileLinks } = load(
|
|
'resources/js/components/profile-navigation.tsx',
|
|
{ '@inertiajs/react': { Link: 'a', usePage: () => ({ props: {} }) } },
|
|
);
|
|
const access = {
|
|
request: '/perfil/promotor',
|
|
technicalSupport: '/suporte-tecnico/incidentes',
|
|
};
|
|
assert.match(
|
|
profileLinks({ ...access, technicalSupportUnread: 3 }).find(
|
|
(link) => link.href === access.technicalSupport,
|
|
).title,
|
|
/3 por consultar/,
|
|
);
|
|
for (const count of [null, -1, 0, 1.2, Infinity, '4'])
|
|
assert.equal(
|
|
profileLinks({ ...access, technicalSupportUnread: count }).find(
|
|
(link) => link.href === access.technicalSupport,
|
|
).title,
|
|
'Suporte técnico',
|
|
);
|
|
assert.equal(
|
|
profileLinks({
|
|
request: access.request,
|
|
technicalSupportUnread: 3,
|
|
}).some((link) => link.href === access.technicalSupport),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('an unknown technical operation is not labelled as a publication', () => {
|
|
const html = render([{ ...base, operation: 'unknown_operation' }]);
|
|
assert.match(html, /Operação técnica não identificada/);
|
|
assert.doesNotMatch(html, />Publicação/);
|
|
});
|
|
|
|
for (const [code, label] of [
|
|
['scan_dispatch_unconfirmed', 'Encaminhamento da análise sem confirmação.'],
|
|
['scan_inconclusive', 'Análise de segurança inconclusiva.'],
|
|
['scan_worker_failed', 'Falha na execução da análise de segurança.'],
|
|
[
|
|
'scan_cleanup_unconfirmed',
|
|
'Limpeza do ficheiro temporário por confirmar.',
|
|
],
|
|
]) {
|
|
test(
|
|
'the scan reader distinguishes ' +
|
|
code +
|
|
' from a failed resource save',
|
|
() => {
|
|
const html = render([
|
|
{ ...base, operation: 'scan_document', error_code: code },
|
|
]);
|
|
assert.ok(html.includes(label));
|
|
assert.match(html, /Análise de segurança de documento/);
|
|
assert.match(html, /O recurso já foi registado/);
|
|
assert.doesNotMatch(html, />Publicação/);
|
|
},
|
|
);
|
|
}
|
|
test('the scan reader never renders an unrecognized internal error code', () => {
|
|
const html = render([
|
|
{
|
|
...base,
|
|
operation: 'scan_document',
|
|
error_code: 'PRIVATE INTERNAL TOKEN',
|
|
},
|
|
]);
|
|
assert.doesNotMatch(html, /PRIVATE INTERNAL/);
|
|
assert.match(html, /Falha técnica durante a operação/);
|
|
});
|
|
|
|
test('unread and technical status filters preserve each other and the detail return context', () => {
|
|
const Queue = load('resources/js/pages/profiles/technical-support.tsx', {
|
|
'@inertiajs/react': {
|
|
Link: ({ children, ...rest }) =>
|
|
React.createElement('a', rest, children),
|
|
},
|
|
'../management/shared': {
|
|
Management: ({ children }) =>
|
|
React.createElement('main', null, children),
|
|
Pagination: () => null,
|
|
},
|
|
}).default;
|
|
const html = renderToStaticMarkup(
|
|
React.createElement(Queue, {
|
|
filters: { status: 'resolved', unread: true },
|
|
incidents: {
|
|
data: [
|
|
{ ...base, details_url: '/suporte-tecnico/incidentes/one' },
|
|
],
|
|
current_page: 2,
|
|
total: 1,
|
|
last_page: 2,
|
|
},
|
|
}),
|
|
);
|
|
assert.ok(
|
|
html.includes(
|
|
'href="/suporte-tecnico/incidentes?status=open&unread=1"',
|
|
),
|
|
);
|
|
assert.ok(
|
|
html.includes('href="/suporte-tecnico/incidentes?status=resolved"'),
|
|
);
|
|
assert.ok(
|
|
html.includes(
|
|
'href="/suporte-tecnico/incidentes/one?page=2&status=resolved&unread=1"',
|
|
),
|
|
);
|
|
});
|