223 lines
7.9 KiB
JavaScript
223 lines
7.9 KiB
JavaScript
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.',
|
|
);
|