@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$root = dirname(__DIR__).'/packages/';
|
||||
$manifest = json_decode(file_get_contents($root.'runtime-manifest.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
if (($manifest['version'] ?? null) !== 1
|
||||
|| ! is_array($manifest['files'] ?? null)
|
||||
|| ! isset($manifest['files']['budget-control/bootstrap.php'], $manifest['files']['payables/bootstrap.php'])) {
|
||||
fwrite(STDERR, "Manifesto de execução incompleto ou incompatível.\n");
|
||||
exit(1);
|
||||
}
|
||||
$errors = [];
|
||||
foreach ($manifest['files'] as $path => $expected) {
|
||||
if (! preg_match('~^(budget-control|payables)/(bootstrap\.php|README\.md|(src|ui)/[A-Za-z]+\.php)$~D', $path)
|
||||
|| ! is_file($root.$path)
|
||||
|| hash_file('sha256', $root.$path) !== $expected) {
|
||||
$errors[] = $path;
|
||||
}
|
||||
}
|
||||
if ($errors) {
|
||||
fwrite(STDERR, 'Pacotes de execução divergentes: '.implode(', ', $errors)."\n");
|
||||
exit(1);
|
||||
}
|
||||
fwrite(STDOUT, count($manifest['files'])." ficheiros de execução conferidos por SHA-256. Sem dependência da pasta SGF.\n");
|
||||
@@ -3,9 +3,9 @@ import fs from 'node:fs/promises';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
const cookies = new Map();
|
||||
async function request(path, method = 'GET', body) {
|
||||
async function request(path, method = 'GET', body, accept = 'application/json') {
|
||||
const response = await fetch(base + path, {method, redirect: 'manual', headers: {
|
||||
Accept: 'application/json', Cookie: [...cookies].map(([k,v]) => `${k}=${v}`).join('; '),
|
||||
Accept: accept, Cookie: [...cookies].map(([k,v]) => `${k}=${v}`).join('; '),
|
||||
...(body ? {'Content-Type': 'application/x-www-form-urlencoded'} : {}),
|
||||
}, ...(body ? {body: new URLSearchParams(body)} : {})});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
@@ -15,22 +15,28 @@ async function request(path, method = 'GET', body) {
|
||||
return {status: response.status, text: await response.text(), location: response.headers.get('location')};
|
||||
}
|
||||
const token = text => text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
async function enter(role) {
|
||||
async function enter(role, destination = '/dashboard') {
|
||||
const page = await request('/avaliar');
|
||||
assert.equal(page.status, 200);
|
||||
const entered = await request('/avaliar/entrar', 'POST', {role, _token: token(page.text)});
|
||||
assert.equal(entered.status, 302, role);
|
||||
assert.equal(new URL(entered.location).pathname, '/dashboard');
|
||||
assert.equal(new URL(entered.location).pathname, destination);
|
||||
assert.equal((await request('/dashboard')).status, 200);
|
||||
return entered;
|
||||
}
|
||||
let page = await request('/avaliar');
|
||||
const guestReport = await request('/relatorios?status=approved', 'GET', undefined, 'text/html');
|
||||
assert.equal(guestReport.status, 302);
|
||||
assert.equal(new URL(guestReport.location).pathname, '/avaliar');
|
||||
assert.equal(new URL((await request('/login', 'GET', undefined, 'text/html')).location).pathname, '/avaliar');
|
||||
assert.equal((await request('/avaliar/entrar','POST',{role:'admin'})).status,419);
|
||||
assert.equal((await request('/avaliar/entrar','POST',{role:'root',_token:token(page.text)})).status,422);
|
||||
assert.equal((await request('/avaliar/entrar','POST',{role:'admin',user_id:'1',_token:token(page.text)})).status,422);
|
||||
await enter('admin');
|
||||
const reportEntry = await enter('admin', '/relatorios');
|
||||
assert.equal(new URL(reportEntry.location).searchParams.get('status'), 'approved');
|
||||
const modules = ['/dashboard','/empresa','/requisicoes','/planeamento','/fornecedores','/cotacoes','/ordens',
|
||||
'/recepcoes','/stock','/stock/operacoes','/equipamentos','/orcamentos','/regras-investimento','/facturas',
|
||||
'/pagamentos','/obrigacoes-fornecedores','/conflitos','/revisao-compras','/politica-admissao','/politica-recomendacao'];
|
||||
'/pagamentos','/obrigacoes-fornecedores','/conflitos','/revisao-compras','/politica-admissao','/politica-recomendacao','/relatorios','/relatorios/medidas-fornecedores','/administracao/parametrizacao'];
|
||||
for (const path of modules) assert.equal((await request(path)).status,200,path);
|
||||
const roles = ['requester','department_head','procurement','finance','warehouse','admin'];
|
||||
for (const role of roles) await enter(role);
|
||||
@@ -44,5 +50,5 @@ await fs.mkdir('docs/proofs',{recursive:true});
|
||||
await fs.writeFile('docs/proofs/evaluation-access.json',JSON.stringify({checked_at:new Date().toISOString(),base,
|
||||
owner_modules:modules,roles,csrf_enforced:true,arbitrary_role_refused:true,arbitrary_user_refused:true,logout:true,
|
||||
business_commands:0,real_accounts_changed:false,operational_entry_status:operational?.status??'not running',
|
||||
visual_review:false},null,2)+'\n');
|
||||
console.log('Owner entry, 20 module pages, six roles, CSRF, rejected identifiers and logout passed.');
|
||||
visual_review:false,guest_report_redirect:true,intended_report_and_filters_preserved:true},null,2)+'\n');
|
||||
console.log('Owner entry, 23 module pages, six roles, report return, CSRF, rejected identifiers and logout passed.');
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import {
|
||||
procurementNavigation,
|
||||
activeProcurementEntry,
|
||||
} from '../resources/js/lib/procurement-navigation.ts';
|
||||
|
||||
const result = spawnSync(
|
||||
'php',
|
||||
['artisan', 'route:list', '--json', '--except-vendor'],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const routes = JSON.parse(result.stdout);
|
||||
const rows = new Map();
|
||||
for (const role of [
|
||||
'admin',
|
||||
'procurement',
|
||||
'finance',
|
||||
'department_head',
|
||||
'warehouse',
|
||||
'requester',
|
||||
]) {
|
||||
for (const payables of [false, true]) {
|
||||
const groups = procurementNavigation(role, payables);
|
||||
for (const group of groups)
|
||||
for (const entry of group.entries) {
|
||||
const uri = new URL(entry.path, 'http://local').pathname.slice(
|
||||
1,
|
||||
);
|
||||
const route = routes.find(
|
||||
(route) =>
|
||||
route.uri === uri &&
|
||||
route.method.split('|').includes('GET'),
|
||||
);
|
||||
assert.ok(
|
||||
route,
|
||||
`${role}: ${entry.label} has no registered GET route (${uri})`,
|
||||
);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, entry.path),
|
||||
entry.id,
|
||||
`${role}: wrong active menu for ${entry.path}`,
|
||||
);
|
||||
rows.set(`${role}:${entry.id}`, {
|
||||
role,
|
||||
group: group.label,
|
||||
id: entry.id,
|
||||
label: entry.label,
|
||||
path: entry.path,
|
||||
action: route.action,
|
||||
full_page: !!entry.fullPage,
|
||||
});
|
||||
}
|
||||
assert.equal(
|
||||
groups.some((group) =>
|
||||
group.entries.some(
|
||||
(entry) => entry.id === 'procurement_reports',
|
||||
),
|
||||
),
|
||||
['admin', 'procurement', 'finance', 'department_head'].includes(
|
||||
role,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
const configuredFinance = procurementNavigation(['finance'], false).flatMap(
|
||||
(g) => g.entries,
|
||||
);
|
||||
assert.equal(
|
||||
configuredFinance.some((e) => e.id === 'organization_parameters'),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
configuredFinance.some((e) => e.id === 'new_request'),
|
||||
false,
|
||||
);
|
||||
const configuredMulti = procurementNavigation(
|
||||
['finance', 'requester', 'admin'],
|
||||
false,
|
||||
).flatMap((g) => g.entries);
|
||||
assert.equal(
|
||||
configuredMulti.some((e) => e.id === 'organization_parameters'),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
configuredMulti.some((e) => e.id === 'new_request'),
|
||||
true,
|
||||
);
|
||||
const proof = {
|
||||
checked_at: new Date().toISOString(),
|
||||
result: 'passed',
|
||||
entries: rows.size,
|
||||
roles: 6,
|
||||
configurable_permissions_and_multiple_profiles: true,
|
||||
source: 'procurementNavigation compared with actual Laravel GET routes',
|
||||
scope: 'Menu routing, active entry and visibility contract; not a substitute for functional or visual workflow acceptance',
|
||||
rows: [...rows.values()],
|
||||
};
|
||||
fs.writeFileSync(
|
||||
'docs/proofs/menu-code-contract.json',
|
||||
JSON.stringify(proof, null, 2) + '\n',
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
result: proof.result,
|
||||
entries: proof.entries,
|
||||
roles: proof.roles,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,130 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
function client() {
|
||||
const cookies = new Map();
|
||||
let csrf;
|
||||
let version;
|
||||
async function request(path, method = 'GET', data, inertia = true) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: inertia ? 'application/json' : 'text/html',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(inertia
|
||||
? {
|
||||
'X-Inertia': 'true',
|
||||
'X-Inertia-Version': version ?? '',
|
||||
}
|
||||
: {}),
|
||||
...(data
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
i = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, i), pair.slice(i + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
location: response.headers.get('location'),
|
||||
text,
|
||||
page: response.headers
|
||||
.get('content-type')
|
||||
?.includes('application/json')
|
||||
? JSON.parse(text)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
request,
|
||||
async enter(role) {
|
||||
let page = await request('/avaliar', 'GET', undefined, false);
|
||||
assert.equal(page.status, 200);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role }, false))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
page = await request('/avaliar', 'GET', undefined, false);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const dashboard = await request(
|
||||
'/dashboard',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
const encoded = dashboard.text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
assert.ok(encoded, 'Expected Inertia bootstrap data');
|
||||
version = JSON.parse(encoded[1]).version;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const roles = [
|
||||
'admin',
|
||||
'requester',
|
||||
'department_head',
|
||||
'procurement',
|
||||
'finance',
|
||||
'warehouse',
|
||||
];
|
||||
let state;
|
||||
for (const role of roles) {
|
||||
const c = client();
|
||||
await c.enter(role);
|
||||
const response = await c.request('/administracao/parametrizacao');
|
||||
if (role === 'admin') {
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.page.component, 'company/parameters');
|
||||
assert.equal(response.page.props.ready, true);
|
||||
assert.ok(response.page.props.permissions.admin);
|
||||
assert.ok(
|
||||
response.page.props.users.every(
|
||||
(u) => !('password' in u) && !('remember_token' in u),
|
||||
),
|
||||
);
|
||||
state = {
|
||||
active: response.page.props.active,
|
||||
version: response.page.props.version,
|
||||
profiles: response.page.props.profiles.length,
|
||||
};
|
||||
} else assert.equal(response.status, 403, role);
|
||||
assert.equal((await c.request('/logout', 'POST', {}, false)).status, 302);
|
||||
}
|
||||
await fs.writeFile(
|
||||
'docs/proofs/organization-access-http.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
base,
|
||||
path: '/administracao/parametrizacao',
|
||||
roles,
|
||||
owner_access: true,
|
||||
other_legacy_profiles_refused: true,
|
||||
passwords_not_returned: true,
|
||||
configuration_state: state,
|
||||
business_commands: 0,
|
||||
accounts_created: 0,
|
||||
permissions_changed: false,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Administration parameters: owner access, six profiles, no exposed credentials or changed accounts.',
|
||||
);
|
||||
@@ -0,0 +1,222 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
function client() {
|
||||
const cookies = new Map();
|
||||
let csrf;
|
||||
let version;
|
||||
async function request(path, method = 'GET', data, inertia = true) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: inertia ? 'application/json' : 'text/html',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(inertia
|
||||
? {
|
||||
'X-Inertia': 'true',
|
||||
'X-Inertia-Version': version ?? '',
|
||||
}
|
||||
: {}),
|
||||
...(data
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
i = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, i), pair.slice(i + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
location: response.headers.get('location'),
|
||||
text,
|
||||
page: response.headers
|
||||
.get('content-type')
|
||||
?.includes('application/json')
|
||||
? JSON.parse(text)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
request,
|
||||
async enter(role) {
|
||||
let page = await request('/avaliar', 'GET', undefined, false);
|
||||
assert.equal(page.status, 200);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role }, false))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
page = await request('/avaliar', 'GET', undefined, false);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const dashboard = await request(
|
||||
'/dashboard',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
const encoded = dashboard.text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
assert.ok(encoded, 'Expected Inertia bootstrap data');
|
||||
version = JSON.parse(encoded[1]).version;
|
||||
},
|
||||
};
|
||||
}
|
||||
const author = client(),
|
||||
reviewer = client();
|
||||
await author.enter('admin');
|
||||
await reviewer.enter('finance');
|
||||
const index = await author.request('/fornecedores/avaliacoes');
|
||||
assert.equal(index.status, 200);
|
||||
assert.equal(index.page.props.ready, true);
|
||||
const supplier = index.page.props.suppliers[0];
|
||||
assert.ok(supplier, 'Existing QA supplier required');
|
||||
const payload = {
|
||||
title: 'QA — avaliação qualitativa do percurso integrado',
|
||||
nature: 'goods',
|
||||
scope: 'Ensaio explícito na avaliação local; não representa decisão comercial.',
|
||||
period_start: '2026-10-01',
|
||||
period_end: '2026-10-30',
|
||||
order_ids: [],
|
||||
criteria: [
|
||||
{
|
||||
label: 'Registo de evidências do ensaio',
|
||||
result: 'observed',
|
||||
reason: 'Percurso fictício destinado a verificar gravação e revisão.',
|
||||
evidence:
|
||||
'Prova técnica scripts/supplier-evaluation-check.mjs; sem entrega real.',
|
||||
},
|
||||
],
|
||||
conclusion: 'Exemplo fictício para avaliar o novo fluxo.',
|
||||
critical_occurrences: '',
|
||||
critical_evidence: '',
|
||||
};
|
||||
const resume = process.argv
|
||||
.find((a) => /^--resume=\d+$/.test(a))
|
||||
?.split('=')[1];
|
||||
const verify = process.argv
|
||||
.find((a) => /^--verify=\d+$/.test(a))
|
||||
?.split('=')[1];
|
||||
let path = verify ? '/fornecedores/avaliacoes/' + verify : undefined;
|
||||
if (!verify) {
|
||||
if (!resume) {
|
||||
const created = await author.request(
|
||||
'/fornecedores/avaliacoes',
|
||||
'POST',
|
||||
{ supplier_id: supplier.id, operation_key: randomUUID(), payload },
|
||||
);
|
||||
assert.equal(created.status, 302, created.text.slice(0, 300));
|
||||
path = new URL(created.location).pathname;
|
||||
assert.equal(
|
||||
(await author.request(path + '/submit', 'POST', { version: 1 }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await author.request(path + '/approve', 'POST', {
|
||||
version: 2,
|
||||
reason: 'Tentativa de aprovar a própria avaliação.',
|
||||
})
|
||||
).status,
|
||||
403,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(path + '/return', 'POST', {
|
||||
version: 2,
|
||||
reason: 'Acrescentar a identificação explícita do exemplo QA.',
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
payload.conclusion += ' Revisto após devolução independente.';
|
||||
assert.equal(
|
||||
(await author.request(path, 'PUT', { version: 3, payload })).status,
|
||||
303,
|
||||
);
|
||||
} else {
|
||||
path = '/fornecedores/avaliacoes/' + resume;
|
||||
const stored = await author.request(path);
|
||||
assert.equal(stored.page.props.evaluation.payload.title, payload.title);
|
||||
assert.equal(stored.page.props.evaluation.version, 4);
|
||||
assert.equal(stored.page.props.evaluation.status, 'draft');
|
||||
payload.conclusion += ' Revisto após devolução independente.';
|
||||
}
|
||||
assert.equal(
|
||||
(await author.request(path, 'PUT', { version: 3, payload })).status,
|
||||
422,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(path + '/submit', 'POST', { version: 4 })).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(path + '/approve', 'POST', {
|
||||
version: 5,
|
||||
reason: 'Âmbito fictício e referências do ensaio conferidos independentemente.',
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(path, 'PUT', { version: 6, payload })).status,
|
||||
409,
|
||||
);
|
||||
}
|
||||
const report = await author.request(path + '/relatorio');
|
||||
assert.equal(report.status, 200);
|
||||
assert.equal(report.page.component, 'suppliers/evaluation');
|
||||
assert.equal(report.page.props.evaluation.status, 'approved');
|
||||
assert.equal(report.page.props.reportOnly, true);
|
||||
assert.equal(report.page.props.canEdit, false);
|
||||
const revision = await author.request(path + '/rever', 'POST', { version: 6 });
|
||||
assert.equal(revision.status, 302);
|
||||
const child = await author.request(new URL(revision.location).pathname);
|
||||
assert.equal(child.page.props.evaluation.revision, 2);
|
||||
assert.equal(child.page.props.evaluation.status, 'draft');
|
||||
assert.equal(
|
||||
(await author.request(path + '/relatorio')).page.props.evaluation.status,
|
||||
'approved',
|
||||
);
|
||||
const history = report.page.props.history;
|
||||
assert.equal(history.total, 6);
|
||||
for (const c of [author, reviewer])
|
||||
assert.equal((await c.request('/logout', 'POST', {}, false)).status, 302);
|
||||
await fs.writeFile(
|
||||
'docs/proofs/supplier-evaluations-http.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
base,
|
||||
report_path: path + '/relatorio',
|
||||
revision_path: new URL(revision.location).pathname,
|
||||
create_submit_return_correct_approve: true,
|
||||
self_approval_refused: true,
|
||||
stale_write_refused: true,
|
||||
approved_edit_refused: true,
|
||||
prior_approved_revision_preserved: true,
|
||||
history_events: history.total,
|
||||
synthetic_example_only: true,
|
||||
financial_commands: 0,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Supplier evaluation HTTP workflow passed, including independent review, corrections, report and revision.',
|
||||
);
|
||||
@@ -0,0 +1,274 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
function client() {
|
||||
const cookies = new Map();
|
||||
let csrf;
|
||||
let version;
|
||||
async function request(path, method = 'GET', data, inertia = true) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: inertia ? 'application/json' : 'text/html',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(inertia
|
||||
? {
|
||||
'X-Inertia': 'true',
|
||||
'X-Inertia-Version': version ?? '',
|
||||
}
|
||||
: {}),
|
||||
...(data
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
i = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, i), pair.slice(i + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
location: response.headers.get('location'),
|
||||
text,
|
||||
page: response.headers
|
||||
.get('content-type')
|
||||
?.includes('application/json')
|
||||
? JSON.parse(text)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
request,
|
||||
async enter(role) {
|
||||
let page = await request('/avaliar', 'GET', undefined, false);
|
||||
assert.equal(page.status, 200);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role }, false))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
page = await request('/avaliar', 'GET', undefined, false);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const dashboard = await request(
|
||||
'/dashboard',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
const encoded = dashboard.text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
assert.ok(encoded, 'Expected Inertia bootstrap data');
|
||||
version = JSON.parse(encoded[1]).version;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const planner = client(),
|
||||
responsible = client(),
|
||||
reviewer = client();
|
||||
await planner.enter('admin');
|
||||
await responsible.enter('procurement');
|
||||
await reviewer.enter('finance');
|
||||
const path = '/fornecedores/avaliacoes/1';
|
||||
const parent = await planner.request(path);
|
||||
assert.equal(parent.status, 200);
|
||||
assert.equal(parent.page.props.evaluation.status, 'approved');
|
||||
assert.equal(parent.page.props.improvements.ready, true);
|
||||
const original = JSON.stringify(parent.page.props.evaluation);
|
||||
const responsiblePage = await responsible.request(path);
|
||||
const responsibleId = responsiblePage.page.props.auth.user.id;
|
||||
const title = 'QA — medida de melhoria do percurso integrado';
|
||||
const plan = {
|
||||
title,
|
||||
description:
|
||||
'Exemplo fictício: completar a referência documental da prestação avaliada.',
|
||||
responsible_id: responsibleId,
|
||||
due_on: '2026-10-29',
|
||||
operation_key: randomUUID(),
|
||||
};
|
||||
const created = await planner.request(path + '/medidas', 'POST', plan);
|
||||
assert.equal(created.status, 302);
|
||||
assert.equal(
|
||||
(await planner.request(path + '/medidas', 'POST', plan)).status,
|
||||
302,
|
||||
);
|
||||
let page = await planner.request(path);
|
||||
const measure = page.page.props.improvements.records
|
||||
.filter((m) => m.title === title)
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
assert.ok(measure);
|
||||
const url = path + '/medidas/' + measure.id;
|
||||
assert.equal(
|
||||
page.page.props.improvements.records.filter((m) => m.id === measure.id)
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await responsible.request(url + '/submit', 'POST', {
|
||||
version: 1,
|
||||
result: 'Resultado sem evidência.',
|
||||
})
|
||||
).status,
|
||||
422,
|
||||
);
|
||||
let evidence = {
|
||||
result: 'Resultado fictício preparado para conferência.',
|
||||
evidence:
|
||||
'Referência QA scripts/supplier-improvement-check.mjs; sem prestação real.',
|
||||
};
|
||||
assert.equal(
|
||||
(
|
||||
await responsible.request(url + '/submit', 'POST', {
|
||||
version: 1,
|
||||
...evidence,
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await responsible.request(url + '/close', 'POST', {
|
||||
version: 2,
|
||||
reason: 'Tentativa do próprio responsável.',
|
||||
})
|
||||
).status,
|
||||
403,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await planner.request(url + '/close', 'POST', {
|
||||
version: 2,
|
||||
reason: 'Tentativa do autor do plano.',
|
||||
})
|
||||
).status,
|
||||
403,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(url + '/return', 'POST', {
|
||||
version: 2,
|
||||
reason: 'Precisar a referência da evidência do exemplo.',
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
const replanned = {
|
||||
...plan,
|
||||
version: 3,
|
||||
due_on: '2026-11-15',
|
||||
reason: 'Reagendamento fictício documentado após a devolução.',
|
||||
};
|
||||
delete replanned.operation_key;
|
||||
assert.equal(
|
||||
(await planner.request(url + '/plan', 'POST', replanned)).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await planner.request(url + '/plan', 'POST', replanned)).status,
|
||||
422,
|
||||
);
|
||||
evidence = {
|
||||
...evidence,
|
||||
evidence: 'Referência QA completada; exemplo sem documento comercial real.',
|
||||
};
|
||||
assert.equal(
|
||||
(
|
||||
await responsible.request(url + '/progress', 'POST', {
|
||||
version: 4,
|
||||
...evidence,
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await responsible.request(url + '/submit', 'POST', {
|
||||
version: 5,
|
||||
...evidence,
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(url + '/close', 'POST', {
|
||||
version: 6,
|
||||
reason: 'Resultado e evidência do exemplo conferidos independentemente.',
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await planner.request(url + '/plan', 'POST', { ...replanned, version: 7 }))
|
||||
.status,
|
||||
409,
|
||||
);
|
||||
page = await planner.request(path);
|
||||
const closed = page.page.props.improvements.records.find(
|
||||
(m) => m.id === measure.id,
|
||||
);
|
||||
assert.equal(closed.status, 'closed');
|
||||
assert.equal(closed.version, 7);
|
||||
assert.equal(closed.due_on, '2026-11-15');
|
||||
assert.equal(JSON.stringify(page.page.props.evaluation), original);
|
||||
const actions = page.page.props.improvements.history.data.filter(
|
||||
(e) => e.after.id === measure.id,
|
||||
);
|
||||
assert.equal(actions.length, 7);
|
||||
assert.ok(
|
||||
actions
|
||||
.find((e) => e.action === 'supplier_improvement.plan')
|
||||
.after.change_reason.includes('Reagendamento'),
|
||||
);
|
||||
const report = await planner.request(path + '/relatorio');
|
||||
assert.equal(report.status, 200);
|
||||
const printed = report.page.props.improvements.records.find(
|
||||
(m) => m.id === measure.id,
|
||||
);
|
||||
assert.equal(printed.status, 'closed');
|
||||
assert.equal(printed.canPlan, false);
|
||||
assert.equal(printed.canExecute, false);
|
||||
assert.equal(printed.canReview, false);
|
||||
for (const c of [planner, responsible, reviewer])
|
||||
assert.equal((await c.request('/logout', 'POST', {}, false)).status, 302);
|
||||
await fs.writeFile(
|
||||
'docs/proofs/supplier-improvements-http.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
base,
|
||||
evaluation_path: path,
|
||||
measure_id: measure.id,
|
||||
create_retry_idempotent: true,
|
||||
missing_evidence_refused: true,
|
||||
self_closure_refused: true,
|
||||
planner_closure_refused: true,
|
||||
return_replan_correct_submit_close: true,
|
||||
stale_plan_refused: true,
|
||||
closed_edit_refused: true,
|
||||
parent_evaluation_unchanged: true,
|
||||
history_events: actions.length,
|
||||
report_includes_closed_measure: true,
|
||||
synthetic_example_only: true,
|
||||
financial_commands: 0,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Supplier improvement HTTP flow passed, including independent closure and preserved approved evaluation.',
|
||||
);
|
||||
@@ -0,0 +1,222 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
function client() {
|
||||
const cookies = new Map();
|
||||
let csrf;
|
||||
let version;
|
||||
async function request(path, method = 'GET', data, inertia = true) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: inertia ? 'application/json' : 'text/html',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(inertia
|
||||
? {
|
||||
'X-Inertia': 'true',
|
||||
'X-Inertia-Version': version ?? '',
|
||||
}
|
||||
: {}),
|
||||
...(data
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
i = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, i), pair.slice(i + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
location: response.headers.get('location'),
|
||||
text,
|
||||
page: response.headers
|
||||
.get('content-type')
|
||||
?.includes('application/json')
|
||||
? JSON.parse(text)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
request,
|
||||
async enter(role) {
|
||||
let page = await request('/avaliar', 'GET', undefined, false);
|
||||
assert.equal(page.status, 200);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role }, false))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
page = await request('/avaliar', 'GET', undefined, false);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const dashboard = await request(
|
||||
'/dashboard',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
const encoded = dashboard.text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
assert.ok(encoded, 'Expected Inertia bootstrap data');
|
||||
version = JSON.parse(encoded[1]).version;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function csvRows(text) {
|
||||
const rows = [];
|
||||
let row = [],
|
||||
value = '',
|
||||
quoted = false;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
if (c === '"') {
|
||||
if (quoted && text[i + 1] === '"') {
|
||||
value += '"';
|
||||
i++;
|
||||
} else quoted = !quoted;
|
||||
} else if (c === ';' && !quoted) {
|
||||
row.push(value);
|
||||
value = '';
|
||||
} else if (c === '\n' && !quoted) {
|
||||
row.push(value.replace(/\r$/, ''));
|
||||
rows.push(row);
|
||||
row = [];
|
||||
value = '';
|
||||
} else value += c;
|
||||
}
|
||||
if (value || row.length) {
|
||||
row.push(value);
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
const path = '/relatorios/medidas-fornecedores';
|
||||
const readers = ['admin', 'procurement', 'finance', 'department_head'];
|
||||
let example;
|
||||
for (const role of [...readers, 'requester', 'warehouse']) {
|
||||
const c = client();
|
||||
await c.enter(role);
|
||||
const report = await c.request(path);
|
||||
if (readers.includes(role)) {
|
||||
assert.equal(report.status, 200, role);
|
||||
assert.equal(report.page.component, 'reports/supplier-improvements');
|
||||
const csv = await c.request(path + '.csv', 'GET', undefined, false);
|
||||
assert.equal(csv.status, 200, role);
|
||||
const rows = csvRows(csv.text);
|
||||
assert.equal(rows.length, report.page.props.records.total + 3, role);
|
||||
assert.equal(
|
||||
Object.values(report.page.props.counts).reduce((a, b) => a + b, 0),
|
||||
report.page.props.records.total,
|
||||
);
|
||||
if (role === 'admin') {
|
||||
assert.ok(
|
||||
report.page.props.records.total >= 1,
|
||||
'Existing explicit QA improvement required',
|
||||
);
|
||||
const supplier = report.page.props.suppliers[0],
|
||||
responsible = report.page.props.responsibles[0];
|
||||
const filters = new URLSearchParams({
|
||||
supplier_id: String(supplier.id),
|
||||
responsible_id: String(responsible.id),
|
||||
status: 'closed',
|
||||
from: '2026-11-15',
|
||||
to: '2026-11-15',
|
||||
});
|
||||
const filtered = await c.request(path + '?' + filters);
|
||||
assert.equal(filtered.status, 200);
|
||||
assert.ok(filtered.page.props.records.total >= 1);
|
||||
assert.ok(
|
||||
filtered.page.props.records.data.every(
|
||||
(m) =>
|
||||
m.status === 'closed' &&
|
||||
m.due_on === '2026-11-15' &&
|
||||
m.overdue === false,
|
||||
),
|
||||
);
|
||||
const exported = await c.request(
|
||||
path + '.csv?' + filters + '&page=2',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
assert.equal(exported.status, 200);
|
||||
assert.equal(
|
||||
csvRows(exported.text).length,
|
||||
filtered.page.props.records.total + 3,
|
||||
);
|
||||
const overdue = await c.request(path + '?overdue=1&status=closed');
|
||||
assert.equal(overdue.page.props.records.total, 0);
|
||||
const invalid = await c.request(
|
||||
path + '?from=2026-11-15&to=2026-01-01',
|
||||
);
|
||||
assert.equal(invalid.status, 422);
|
||||
const first = report.page.props.records.data[0];
|
||||
assert.equal(
|
||||
(
|
||||
await c.request(
|
||||
'/fornecedores/avaliacoes/' + first.evaluation_id,
|
||||
)
|
||||
).status,
|
||||
200,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await c.request(
|
||||
'/fornecedores/avaliacoes/' +
|
||||
first.evaluation_id +
|
||||
'/relatorio',
|
||||
)
|
||||
).status,
|
||||
200,
|
||||
);
|
||||
example = {
|
||||
total: report.page.props.records.total,
|
||||
filtered_total: filtered.page.props.records.total,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
assert.equal(report.status, 403, role);
|
||||
assert.equal(
|
||||
(await c.request(path + '.csv', 'GET', undefined, false)).status,
|
||||
403,
|
||||
role,
|
||||
);
|
||||
}
|
||||
assert.equal((await c.request('/logout', 'POST', {}, false)).status, 302);
|
||||
}
|
||||
await fs.writeFile(
|
||||
'docs/proofs/supplier-improvement-report-http.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
base,
|
||||
path,
|
||||
readers,
|
||||
denied: ['requester', 'warehouse'],
|
||||
...example,
|
||||
filters_and_csv_match: true,
|
||||
csv_ignores_page: true,
|
||||
closed_not_overdue: true,
|
||||
invalid_range_refused: true,
|
||||
linked_evaluation_and_report: true,
|
||||
business_commands: 0,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Consolidated improvement report: six profiles, filters, counts, CSV and evaluation links passed.',
|
||||
);
|
||||
@@ -0,0 +1,254 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
function client() {
|
||||
const cookies = new Map();
|
||||
let csrf;
|
||||
let version;
|
||||
async function request(path, method = 'GET', data, inertia = true) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: inertia ? 'application/json' : 'text/html',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(inertia
|
||||
? {
|
||||
'X-Inertia': 'true',
|
||||
'X-Inertia-Version': version ?? '',
|
||||
}
|
||||
: {}),
|
||||
...(data
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrf,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
i = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, i), pair.slice(i + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
return {
|
||||
status: response.status,
|
||||
location: response.headers.get('location'),
|
||||
text,
|
||||
page: response.headers
|
||||
.get('content-type')
|
||||
?.includes('application/json')
|
||||
? JSON.parse(text)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
return {
|
||||
request,
|
||||
async enter(role) {
|
||||
let page = await request('/avaliar', 'GET', undefined, false);
|
||||
assert.equal(page.status, 200);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role }, false))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
page = await request('/avaliar', 'GET', undefined, false);
|
||||
csrf = page.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const dashboard = await request(
|
||||
'/dashboard',
|
||||
'GET',
|
||||
undefined,
|
||||
false,
|
||||
);
|
||||
const encoded = dashboard.text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
assert.ok(encoded, 'Expected Inertia bootstrap data');
|
||||
version = JSON.parse(encoded[1]).version;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const author = client(),
|
||||
reviewer = client();
|
||||
await author.enter('admin');
|
||||
await reviewer.enter('finance');
|
||||
const path = '/fornecedores/avaliacoes/1',
|
||||
responseBase = path + '/contraditorios';
|
||||
const before = await author.request(path);
|
||||
assert.equal(before.status, 200);
|
||||
assert.equal(before.page.props.evaluation.status, 'approved');
|
||||
assert.equal(before.page.props.responses.ready, true);
|
||||
const original = JSON.stringify(before.page.props.evaluation);
|
||||
const operation_key = randomUUID();
|
||||
let payload = {
|
||||
title: 'QA — contraditório do percurso integrado',
|
||||
received_on: '',
|
||||
source_reference: '',
|
||||
statement: '',
|
||||
};
|
||||
assert.equal(
|
||||
(await author.request(responseBase, 'POST', { operation_key, payload }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(responseBase, 'POST', { operation_key, payload }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
let page = await author.request(path);
|
||||
const record = page.page.props.responses.records
|
||||
.filter((r) => r.payload.title === payload.title)
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
assert.ok(record);
|
||||
const url = responseBase + '/' + record.id;
|
||||
assert.equal(
|
||||
(await author.request(url + '/submit', 'POST', { version: 1 })).status,
|
||||
422,
|
||||
);
|
||||
payload = {
|
||||
...payload,
|
||||
received_on: '2026-10-29',
|
||||
source_reference:
|
||||
'Documento fictício QA-CTR — ensaio sem comunicação externa.',
|
||||
statement:
|
||||
'Argumentos fictícios do fornecedor destinados a conferir o fluxo e o histórico.',
|
||||
};
|
||||
assert.equal(
|
||||
(await author.request(url + '/save', 'POST', { version: 1, payload }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(url + '/save', 'POST', { version: 1, payload }))
|
||||
.status,
|
||||
422,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(url + '/submit', 'POST', { version: 2 })).status,
|
||||
302,
|
||||
);
|
||||
const decision = {
|
||||
outcome: 'partially_accepted',
|
||||
reason: 'Parte dos argumentos do exemplo está sustentada pelas referências fictícias.',
|
||||
follow_up:
|
||||
'Conferir o âmbito afectado numa nova revisão, sem alterar esta avaliação.',
|
||||
response_reference: 'Minuta fictícia QA-RESPOSTA — não enviada.',
|
||||
};
|
||||
assert.equal(
|
||||
(await author.request(url + '/decide', 'POST', { version: 3, ...decision }))
|
||||
.status,
|
||||
403,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(url + '/return', 'POST', {
|
||||
version: 3,
|
||||
reason: 'Completar a referência do documento recebido para o ensaio.',
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
payload = {
|
||||
...payload,
|
||||
source_reference: payload.source_reference + ' Anexo fictício A.',
|
||||
};
|
||||
assert.equal(
|
||||
(await author.request(url + '/save', 'POST', { version: 4, payload }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(url + '/submit', 'POST', { version: 5 })).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(url + '/decide', 'POST', {
|
||||
version: 6,
|
||||
...decision,
|
||||
follow_up: '',
|
||||
})
|
||||
).status,
|
||||
422,
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await reviewer.request(url + '/decide', 'POST', {
|
||||
version: 6,
|
||||
...decision,
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
assert.equal(
|
||||
(await author.request(url + '/save', 'POST', { version: 7, payload }))
|
||||
.status,
|
||||
409,
|
||||
);
|
||||
page = await author.request(path);
|
||||
assert.equal(JSON.stringify(page.page.props.evaluation), original);
|
||||
const final = page.page.props.responses.records.find((r) => r.id === record.id);
|
||||
assert.equal(final.status, 'decided');
|
||||
assert.equal(final.version, 7);
|
||||
assert.equal(final.decision.outcome, 'partially_accepted');
|
||||
const events = page.page.props.responses.history.data.filter(
|
||||
(e) => e.after.id === record.id,
|
||||
);
|
||||
assert.equal(events.length, 7);
|
||||
assert.equal(
|
||||
events.find((e) => e.action === 'supplier_response.create').after.payload
|
||||
.source_reference,
|
||||
'',
|
||||
);
|
||||
assert.ok(
|
||||
events
|
||||
.find((e) => e.action === 'supplier_response.return')
|
||||
.after.review_reason.includes('Completar'),
|
||||
);
|
||||
const report = await author.request(path + '/relatorio');
|
||||
assert.equal(report.status, 200);
|
||||
const printed = report.page.props.responses.records.find(
|
||||
(r) => r.id === record.id,
|
||||
);
|
||||
assert.equal(printed.status, 'decided');
|
||||
assert.equal(printed.canEdit, false);
|
||||
assert.equal(printed.canReview, false);
|
||||
for (const c of [author, reviewer])
|
||||
assert.equal((await c.request('/logout', 'POST', {}, false)).status, 302);
|
||||
await fs.writeFile(
|
||||
'docs/proofs/supplier-responses-http.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
base: 'http://127.0.0.1:8005',
|
||||
evaluation_path: path,
|
||||
response_id: record.id,
|
||||
idempotent_creation: true,
|
||||
incomplete_submit_refused: true,
|
||||
stale_save_refused: true,
|
||||
self_decision_refused: true,
|
||||
return_correction_independent_conclusion: true,
|
||||
accepted_follow_up_required: true,
|
||||
concluded_edit_refused: true,
|
||||
parent_evaluation_unchanged: true,
|
||||
history_events: events.length,
|
||||
individual_report: true,
|
||||
synthetic_example_only: true,
|
||||
external_messages_sent: 0,
|
||||
financial_commands: 0,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Supplier response HTTP flow passed: independent conclusion, preserved statements, history and report.',
|
||||
);
|
||||
Reference in New Issue
Block a user