Permitir consulta pública dos ecrãs reais com dados privados protegidos
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -3,57 +3,176 @@ 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, accept = 'application/json') {
|
||||
const response = await fetch(base + path, {method, redirect: 'manual', headers: {
|
||||
Accept: accept, Cookie: [...cookies].map(([k,v]) => `${k}=${v}`).join('; '),
|
||||
...(body ? {'Content-Type': 'application/x-www-form-urlencoded'} : {}),
|
||||
}, ...(body ? {body: new URLSearchParams(body)} : {})});
|
||||
async function request(
|
||||
path,
|
||||
method = 'GET',
|
||||
body,
|
||||
accept = 'application/json',
|
||||
) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
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()) {
|
||||
const pair = cookie.split(';', 1)[0], index = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0,index),pair.slice(index+1));
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
index = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, index), pair.slice(index + 1));
|
||||
}
|
||||
return {status: response.status, text: await response.text(), location: response.headers.get('location')};
|
||||
return {
|
||||
status: response.status,
|
||||
text: await response.text(),
|
||||
location: response.headers.get('location'),
|
||||
};
|
||||
}
|
||||
const token = text => text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
const token = (text) => text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
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)});
|
||||
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, 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');
|
||||
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);
|
||||
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,
|
||||
);
|
||||
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','/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'];
|
||||
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',
|
||||
'/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);
|
||||
page = await request('/avaliar');
|
||||
assert.equal((await request('/logout','POST',{_token:token(page.text)})).status,204);
|
||||
assert.equal((await request('/empresa')).status,401);
|
||||
assert.equal((await request('/dashboard')).status,200);
|
||||
assert.equal((await request('/empresa')).status,200);
|
||||
assert.equal((await request('/administracao/parametrizacao')).status,200);
|
||||
assert.equal(
|
||||
(await request('/logout', 'POST', { _token: token(page.text) })).status,
|
||||
204,
|
||||
);
|
||||
assert.equal((await request('/empresa')).status, 401);
|
||||
assert.equal((await request('/dashboard')).status, 200);
|
||||
assert.equal((await request('/empresa')).status, 200);
|
||||
assert.equal((await request('/administracao/parametrizacao')).status, 200);
|
||||
page = await request('/avaliar');
|
||||
assert.equal((await request('/logout','POST',{_token:token(page.text)})).status,204);
|
||||
assert.equal((await request('/empresa')).status,401);
|
||||
const operational = await fetch('http://127.0.0.1:8015/avaliar', {redirect:'manual'}).catch(() => null);
|
||||
if (operational) assert.equal(operational.status,404,'Evaluation entry must not exist on the normal installation');
|
||||
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,dashboard_auto_entry:true,other_routes_require_session:true,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.');
|
||||
assert.equal(
|
||||
(await request('/logout', 'POST', { _token: token(page.text) })).status,
|
||||
204,
|
||||
);
|
||||
assert.equal((await request('/empresa')).status, 401);
|
||||
const operational = await fetch('http://127.0.0.1:8015/avaliar', {
|
||||
redirect: 'manual',
|
||||
}).catch(() => null);
|
||||
if (operational)
|
||||
assert.equal(
|
||||
operational.status,
|
||||
404,
|
||||
'Evaluation entry must not exist on the normal installation',
|
||||
);
|
||||
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,
|
||||
dashboard_auto_entry: true,
|
||||
other_routes_require_session: true,
|
||||
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.',
|
||||
);
|
||||
|
||||
@@ -1,39 +1,89 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
const cookies = new Map(), base = 'http://127.0.0.1:8005';
|
||||
const cookies = new Map(),
|
||||
base = 'http://127.0.0.1:8005';
|
||||
async function request(path, method = 'GET', body) {
|
||||
const r = await fetch(base + path, {method, redirect:'manual', headers:{Accept:'application/json',
|
||||
Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),
|
||||
...(body?{'Content-Type':'application/x-www-form-urlencoded'}:{})},
|
||||
...(body?{body:new URLSearchParams(body)}:{})});
|
||||
for(const c of r.headers.getSetCookie()) {const p=c.split(';',1)[0],i=p.indexOf('=');cookies.set(p.slice(0,i),p.slice(i+1));}
|
||||
return {status:r.status,text:await r.text()};
|
||||
const r = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(body
|
||||
? { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
: {}),
|
||||
},
|
||||
...(body ? { body: new URLSearchParams(body) } : {}),
|
||||
});
|
||||
for (const c of r.headers.getSetCookie()) {
|
||||
const p = c.split(';', 1)[0],
|
||||
i = p.indexOf('=');
|
||||
cookies.set(p.slice(0, i), p.slice(i + 1));
|
||||
}
|
||||
return { status: r.status, text: await r.text() };
|
||||
}
|
||||
const landing=await request('/avaliar');
|
||||
const csrf=landing.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal((await request('/avaliar/entrar','POST',{role:'admin',_token:csrf})).status,302);
|
||||
const before=JSON.parse((await request('/api/supplier-payables')).text);
|
||||
const landing = await request('/avaliar');
|
||||
const csrf = landing.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal(
|
||||
(await request('/avaliar/entrar', 'POST', { role: 'admin', _token: csrf }))
|
||||
.status,
|
||||
302,
|
||||
);
|
||||
const before = JSON.parse((await request('/api/supplier-payables')).text);
|
||||
assert(before.documents.length);
|
||||
for(const doc of before.documents) {
|
||||
const filtered=await request('/api/supplier-payables?invoice='+doc.local_id);
|
||||
assert.equal(filtered.status,200);
|
||||
const data=JSON.parse(filtered.text);
|
||||
assert.deepEqual(data.documents,[doc]);
|
||||
const page=await request('/obrigacoes-fornecedores?invoice='+doc.local_id);
|
||||
assert.equal(page.status,200);
|
||||
for (const doc of before.documents) {
|
||||
const filtered = await request(
|
||||
'/api/supplier-payables?invoice=' + doc.local_id,
|
||||
);
|
||||
assert.equal(filtered.status, 200);
|
||||
const data = JSON.parse(filtered.text);
|
||||
assert.deepEqual(data.documents, [doc]);
|
||||
const page = await request(
|
||||
'/obrigacoes-fornecedores?invoice=' + doc.local_id,
|
||||
);
|
||||
assert.equal(page.status, 200);
|
||||
assert(page.text.includes('href="/facturas"'));
|
||||
assert(page.text.includes('Voltar às facturas'));
|
||||
assert(page.text.includes(doc.number));
|
||||
}
|
||||
for(const query of ['0','-1','abc','1.5']) {
|
||||
assert.equal((await request('/api/supplier-payables?invoice='+query)).status,422);
|
||||
assert.equal((await request('/obrigacoes-fornecedores?invoice='+query)).status,422);
|
||||
for (const query of ['0', '-1', 'abc', '1.5']) {
|
||||
assert.equal(
|
||||
(await request('/api/supplier-payables?invoice=' + query)).status,
|
||||
422,
|
||||
);
|
||||
assert.equal(
|
||||
(await request('/obrigacoes-fornecedores?invoice=' + query)).status,
|
||||
422,
|
||||
);
|
||||
}
|
||||
assert.equal((await request('/api/supplier-payables?invoice=2147483647')).status,404);
|
||||
const after=JSON.parse((await request('/api/supplier-payables')).text);
|
||||
assert.deepEqual(after,before,'Read-only navigation must preserve all documents and histories');
|
||||
await fs.writeFile('docs/proofs/invoice-payable-link.json',JSON.stringify({checked_at:new Date().toISOString(),
|
||||
documents_checked:before.documents.length,per_invoice_entry:true,return_to_invoices:true,
|
||||
invalid_identifiers_refused:true,unknown_invoice_refused:true,full_listing_unchanged:true,business_commands:0,
|
||||
visual_review:false},null,2)+'\n');
|
||||
console.log('Per-invoice payable navigation, invalid/unknown identifiers and unchanged history passed.');
|
||||
assert.equal(
|
||||
(await request('/api/supplier-payables?invoice=2147483647')).status,
|
||||
404,
|
||||
);
|
||||
const after = JSON.parse((await request('/api/supplier-payables')).text);
|
||||
assert.deepEqual(
|
||||
after,
|
||||
before,
|
||||
'Read-only navigation must preserve all documents and histories',
|
||||
);
|
||||
await fs.writeFile(
|
||||
'docs/proofs/invoice-payable-link.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
documents_checked: before.documents.length,
|
||||
per_invoice_entry: true,
|
||||
return_to_invoices: true,
|
||||
invalid_identifiers_refused: true,
|
||||
unknown_invoice_refused: true,
|
||||
full_listing_unchanged: true,
|
||||
business_commands: 0,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'Per-invoice payable navigation, invalid/unknown identifiers and unchanged history passed.',
|
||||
);
|
||||
|
||||
+44
-10
@@ -81,17 +81,51 @@ const configuredFinance = procurementNavigation(['finance'], false).flatMap(
|
||||
(g) => g.entries,
|
||||
);
|
||||
const separate = procurementNavigation('admin', true);
|
||||
assert.ok(separate.find((group) => group.id === 'purchasing').entries.every((entry) => !entry.path.startsWith('/pessoas')));
|
||||
assert.ok(separate.find((group) => group.id === 'people').entries.every((entry) => entry.path.startsWith('/pessoas')));
|
||||
for (const permission of ['people_service_prepare','people_service_review','people_staff_prepare','people_staff_review']) {
|
||||
const entries = procurementNavigation([permission], false).flatMap((group) => group.entries);
|
||||
assert.equal(entries.some((entry) => entry.id === 'people_hiring'), true);
|
||||
assert.equal(entries.some((entry) => entry.id === 'company' || entry.id === 'invoices' || entry.id === 'new_request'), false);
|
||||
assert.ok(
|
||||
separate
|
||||
.find((group) => group.id === 'purchasing')
|
||||
.entries.every((entry) => !entry.path.startsWith('/pessoas')),
|
||||
);
|
||||
assert.ok(
|
||||
separate
|
||||
.find((group) => group.id === 'people')
|
||||
.entries.every((entry) => entry.path.startsWith('/pessoas')),
|
||||
);
|
||||
for (const permission of [
|
||||
'people_service_prepare',
|
||||
'people_service_review',
|
||||
'people_staff_prepare',
|
||||
'people_staff_review',
|
||||
]) {
|
||||
const entries = procurementNavigation([permission], false).flatMap(
|
||||
(group) => group.entries,
|
||||
);
|
||||
assert.equal(
|
||||
entries.some((entry) => entry.id === 'people_hiring'),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
entries.some(
|
||||
(entry) =>
|
||||
entry.id === 'company' ||
|
||||
entry.id === 'invoices' ||
|
||||
entry.id === 'new_request',
|
||||
),
|
||||
false,
|
||||
);
|
||||
}
|
||||
for (const permission of ['people_registry_read','people_registry_manage']) {
|
||||
const entries = procurementNavigation([permission],false).flatMap((group)=>group.entries);
|
||||
assert.equal(entries.some((entry)=>entry.id==='people_registry'),true);
|
||||
assert.equal(entries.some((entry)=>entry.id==='people_hiring'),false);
|
||||
for (const permission of ['people_registry_read', 'people_registry_manage']) {
|
||||
const entries = procurementNavigation([permission], false).flatMap(
|
||||
(group) => group.entries,
|
||||
);
|
||||
assert.equal(
|
||||
entries.some((entry) => entry.id === 'people_registry'),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
entries.some((entry) => entry.id === 'people_hiring'),
|
||||
false,
|
||||
);
|
||||
}
|
||||
assert.equal(
|
||||
configuredFinance.some((e) => e.id === 'organization_parameters'),
|
||||
|
||||
+218
-62
@@ -1,110 +1,266 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import {procurementNavigation, activeProcurementEntry} from '../resources/js/lib/procurement-navigation.ts';
|
||||
import {
|
||||
procurementNavigation,
|
||||
activeProcurementEntry,
|
||||
} from '../resources/js/lib/procurement-navigation.ts';
|
||||
|
||||
const base = 'http://127.0.0.1:8005';
|
||||
const cookies = new Map();
|
||||
let version = '';
|
||||
async function request(path, {method = 'GET', body, inertia = false} = {}) {
|
||||
const response = await fetch(base + path, {method, redirect: 'manual', headers: {
|
||||
Accept: 'application/json', Cookie: [...cookies].map(([k,v]) => `${k}=${v}`).join('; '),
|
||||
...(body ? {'Content-Type': 'application/x-www-form-urlencoded'} : {}),
|
||||
...(inertia ? {'X-Inertia': 'true', 'X-Inertia-Version': version} : {}),
|
||||
}, ...(body ? {body: new URLSearchParams(body)} : {})});
|
||||
async function request(path, { method = 'GET', body, inertia = false } = {}) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
redirect: 'manual',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Cookie: [...cookies].map(([k, v]) => `${k}=${v}`).join('; '),
|
||||
...(body
|
||||
? { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
: {}),
|
||||
...(inertia
|
||||
? { 'X-Inertia': 'true', 'X-Inertia-Version': version }
|
||||
: {}),
|
||||
},
|
||||
...(body ? { body: new URLSearchParams(body) } : {}),
|
||||
});
|
||||
for (const cookie of response.headers.getSetCookie()) {
|
||||
const pair = cookie.split(';', 1)[0], index = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0,index),pair.slice(index+1));
|
||||
const pair = cookie.split(';', 1)[0],
|
||||
index = pair.indexOf('=');
|
||||
cookies.set(pair.slice(0, index), pair.slice(index + 1));
|
||||
}
|
||||
const text = await response.text();
|
||||
let data;
|
||||
try {data = JSON.parse(text);} catch {}
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {}
|
||||
if (!data) {
|
||||
const page = text.match(/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/);
|
||||
const page = text.match(
|
||||
/<script data-page="app" type="application\/json">([\s\S]*?)<\/script>/,
|
||||
);
|
||||
if (page) data = JSON.parse(page[1]);
|
||||
}
|
||||
if (data?.version) version = data.version;
|
||||
return {status:response.status, text, data};
|
||||
return { status: response.status, text, data };
|
||||
}
|
||||
async function enter(role) {
|
||||
const landing = await request('/avaliar');
|
||||
assert.equal(landing.status, 200);
|
||||
const csrf = landing.text.match(/name="_token" value="([^"]+)"/)[1];
|
||||
assert.equal((await request('/avaliar/entrar', {method:'POST',body:{role,_token:csrf}})).status,302);
|
||||
assert.equal(
|
||||
(
|
||||
await request('/avaliar/entrar', {
|
||||
method: 'POST',
|
||||
body: { role, _token: csrf },
|
||||
})
|
||||
).status,
|
||||
302,
|
||||
);
|
||||
}
|
||||
await enter('admin');
|
||||
await request('/dashboard');
|
||||
const baseline = (await request('/api/supplier-payables')).data;
|
||||
const results = [];
|
||||
for (const role of ['admin','procurement','finance','department_head','warehouse','requester']) {
|
||||
for (const role of [
|
||||
'admin',
|
||||
'procurement',
|
||||
'finance',
|
||||
'department_head',
|
||||
'warehouse',
|
||||
'requester',
|
||||
]) {
|
||||
await enter(role);
|
||||
const dashboard = await request('/dashboard', {inertia:true});
|
||||
assert.equal(dashboard.status,200);
|
||||
assert.equal(dashboard.data.props.auth.user.purchasing_role,role);
|
||||
const groups = procurementNavigation(role,dashboard.data.props.payablesAvailable === true);
|
||||
const entries = groups.flatMap(group=>group.entries);
|
||||
const dashboard = await request('/dashboard', { inertia: true });
|
||||
assert.equal(dashboard.status, 200);
|
||||
assert.equal(dashboard.data.props.auth.user.purchasing_role, role);
|
||||
const groups = procurementNavigation(
|
||||
role,
|
||||
dashboard.data.props.payablesAvailable === true,
|
||||
);
|
||||
const entries = groups.flatMap((group) => group.entries);
|
||||
for (const entry of entries) {
|
||||
const response = await request(entry.path,{inertia:!entry.fullPage});
|
||||
assert.equal(response.status,200,role+' '+entry.path);
|
||||
if (!entry.fullPage) assert(response.data?.component,role+' Inertia '+entry.path);
|
||||
if (entry.id === 'new_request') assert.equal(typeof response.data.props.defaultTaxRate,'string','Configured VAT must reach new request lines');
|
||||
if (entry.id === 'organization_parameters') assert.equal(typeof response.data.props.companyRules.default_tax_rate,'string','Administration must expose the VAT setting');
|
||||
if (entry.id === 'organization_parameters') assert.equal(Object.values(response.data.props.comparisonPolicy.weights).reduce((sum, value)=>sum+value,0),100,'Comparison policy weights must reach administration');
|
||||
if (entry.id === 'people_hiring') assert.equal(response.data.props.ready,true,'People hiring must be installed in the evaluation environment');
|
||||
const response = await request(entry.path, {
|
||||
inertia: !entry.fullPage,
|
||||
});
|
||||
assert.equal(response.status, 200, role + ' ' + entry.path);
|
||||
if (!entry.fullPage)
|
||||
assert(response.data?.component, role + ' Inertia ' + entry.path);
|
||||
if (entry.id === 'new_request')
|
||||
assert.equal(
|
||||
typeof response.data.props.defaultTaxRate,
|
||||
'string',
|
||||
'Configured VAT must reach new request lines',
|
||||
);
|
||||
if (entry.id === 'organization_parameters')
|
||||
assert.equal(
|
||||
typeof response.data.props.companyRules.default_tax_rate,
|
||||
'string',
|
||||
'Administration must expose the VAT setting',
|
||||
);
|
||||
if (entry.id === 'organization_parameters')
|
||||
assert.equal(
|
||||
Object.values(
|
||||
response.data.props.comparisonPolicy.weights,
|
||||
).reduce((sum, value) => sum + value, 0),
|
||||
100,
|
||||
'Comparison policy weights must reach administration',
|
||||
);
|
||||
if (entry.id === 'people_hiring')
|
||||
assert.equal(
|
||||
response.data.props.ready,
|
||||
true,
|
||||
'People hiring must be installed in the evaluation environment',
|
||||
);
|
||||
if (entry.id === 'people_hiring') {
|
||||
assert.equal(typeof response.data.props.workdesk.counts.tasks,'number');
|
||||
assert.equal(typeof response.data.props.workdesk.counts.approvals,'number');
|
||||
assert(response.data.props.processes.data.every(p=>!('rules' in p)&&!('selection' in p)&&!('contract' in p)),'People index must not expose private process payloads');
|
||||
assert.equal(
|
||||
typeof response.data.props.workdesk.counts.tasks,
|
||||
'number',
|
||||
);
|
||||
assert.equal(
|
||||
typeof response.data.props.workdesk.counts.approvals,
|
||||
'number',
|
||||
);
|
||||
assert(
|
||||
response.data.props.processes.data.every(
|
||||
(p) =>
|
||||
!('rules' in p) &&
|
||||
!('selection' in p) &&
|
||||
!('contract' in p),
|
||||
),
|
||||
'People index must not expose private process payloads',
|
||||
);
|
||||
}
|
||||
assert.equal(activeProcurementEntry(groups,entry.path),entry.id,role+' active '+entry.path);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, entry.path),
|
||||
entry.id,
|
||||
role + ' active ' + entry.path,
|
||||
);
|
||||
if (entry.id === 'my_drafts') {
|
||||
assert.equal(response.data.props.filters.view,'mine');
|
||||
assert.equal(response.data.props.filters.status,'draft');
|
||||
assert.equal(response.data.props.filters.view, 'mine');
|
||||
assert.equal(response.data.props.filters.status, 'draft');
|
||||
}
|
||||
}
|
||||
for (const path of ['/settings/profile','/settings/appearance']) assert.equal((await request(path,{inertia:true})).status,200,role+' '+path);
|
||||
for (const path of ['/settings/profile', '/settings/appearance'])
|
||||
assert.equal(
|
||||
(await request(path, { inertia: true })).status,
|
||||
200,
|
||||
role + ' ' + path,
|
||||
);
|
||||
// Hidden administration and finance pages remain enforced on the server.
|
||||
if (role !== 'admin') assert.equal((await request('/empresa')).status,403,role+' company isolation');
|
||||
if (!['admin','finance'].includes(role)) assert.equal((await request('/facturas')).status,403,role+' finance isolation');
|
||||
results.push({role,checked_entries:entries.length,paths:entries.map(entry=>entry.path)});
|
||||
if (role !== 'admin')
|
||||
assert.equal(
|
||||
(await request('/empresa')).status,
|
||||
403,
|
||||
role + ' company isolation',
|
||||
);
|
||||
if (!['admin', 'finance'].includes(role))
|
||||
assert.equal(
|
||||
(await request('/facturas')).status,
|
||||
403,
|
||||
role + ' finance isolation',
|
||||
);
|
||||
results.push({
|
||||
role,
|
||||
checked_entries: entries.length,
|
||||
paths: entries.map((entry) => entry.path),
|
||||
});
|
||||
}
|
||||
await enter('admin');
|
||||
const all = await request('/requisicoes?view=all',{inertia:true});
|
||||
const all = await request('/requisicoes?view=all', { inertia: true });
|
||||
const requestId = all.data.props.requisitions.data[0]?.id;
|
||||
const processLinks = [];
|
||||
if (requestId) {
|
||||
const comparison = await request('/requisicoes/'+requestId+'/cotacoes',{inertia:true});
|
||||
assert.equal(comparison.status,200);
|
||||
const comparison = await request(
|
||||
'/requisicoes/' + requestId + '/cotacoes',
|
||||
{ inertia: true },
|
||||
);
|
||||
assert.equal(comparison.status, 200);
|
||||
assert(Array.isArray(comparison.data.props.topIds));
|
||||
assert(comparison.data.props.comparisonPolicy.minimum_proposals >= 1);
|
||||
assert(comparison.data.props.comparisonPolicy.minimum_evaluators >= 1);
|
||||
assert(comparison.data.props.quotations.every(q=>Array.isArray(q.ineligibility_reasons)));
|
||||
assert.equal((await request('/requisicoes/'+requestId,{inertia:true})).status,200);
|
||||
const process = await request('/requisicoes/'+requestId+'/processo',{inertia:true});
|
||||
assert.equal(process.status,200);
|
||||
assert(
|
||||
comparison.data.props.quotations.every((q) =>
|
||||
Array.isArray(q.ineligibility_reasons),
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
(await request('/requisicoes/' + requestId, { inertia: true })).status,
|
||||
200,
|
||||
);
|
||||
const process = await request('/requisicoes/' + requestId + '/processo', {
|
||||
inertia: true,
|
||||
});
|
||||
assert.equal(process.status, 200);
|
||||
function collectLinks(value) {
|
||||
if (!value || typeof value !== 'object') return;
|
||||
if (typeof value.href === 'string' && value.href.startsWith('/') && !value.href.startsWith('//')) processLinks.push(value.href);
|
||||
for (const child of Object.values(value)) if (typeof child === 'object') collectLinks(child);
|
||||
if (
|
||||
typeof value.href === 'string' &&
|
||||
value.href.startsWith('/') &&
|
||||
!value.href.startsWith('//')
|
||||
)
|
||||
processLinks.push(value.href);
|
||||
for (const child of Object.values(value))
|
||||
if (typeof child === 'object') collectLinks(child);
|
||||
}
|
||||
collectLinks(process.data.props);
|
||||
for (const href of new Set(processLinks)) assert.equal((await request(href)).status,200,'Process link '+href);
|
||||
for (const href of new Set(processLinks))
|
||||
assert.equal((await request(href)).status, 200, 'Process link ' + href);
|
||||
}
|
||||
const invoices = await request('/facturas',{inertia:true});
|
||||
const invoices = await request('/facturas', { inertia: true });
|
||||
for (const invoice of invoices.data.props.invoices.data) {
|
||||
const page = await request('/obrigacoes-fornecedores?invoice='+invoice.id);
|
||||
assert.equal(page.status,200);
|
||||
const page = await request(
|
||||
'/obrigacoes-fornecedores?invoice=' + invoice.id,
|
||||
);
|
||||
assert.equal(page.status, 200);
|
||||
assert(page.text.includes('href="/facturas"'));
|
||||
}
|
||||
assert.deepEqual((await request('/api/supplier-payables')).data,baseline,'Business history must remain unchanged');
|
||||
const groups = procurementNavigation('admin',true);
|
||||
assert.equal(activeProcurementEntry(groups,'/requisicoes?status=draft&view=mine'),'my_drafts');
|
||||
assert.equal(activeProcurementEntry(groups,'/ordens/1/recepcoes'),'receipts');
|
||||
assert.equal(activeProcurementEntry(groups,'/requisicoes/1/cotacoes'),'quotations');
|
||||
assert.equal(activeProcurementEntry(groups,'/requisicoes/1/relatorio'),'quotations');
|
||||
assert.equal(activeProcurementEntry(groups,'/requisicoes/1/adjudicacao'),'orders');
|
||||
assert.equal(activeProcurementEntry(groups,'/stock/operacoes'),'stock_operations');
|
||||
await fs.writeFile('docs/proofs/menu-links.json',JSON.stringify({checked_at:new Date().toISOString(),results,
|
||||
request_detail_checked:!!requestId,invoice_count:invoices.data.props.invoices.data.length,
|
||||
process_links_checked:[...new Set(processLinks)],
|
||||
canonical_query_order:true,active_subroutes:true,vat_setting_and_default_checked:true,comparison_ties_and_reasons_checked:!!requestId,business_history_unchanged:true,business_commands:0},null,2)+'\n');
|
||||
console.log('All visible menu links passed for six roles, with active entries, query filters, detail links and unchanged business history.');
|
||||
assert.deepEqual(
|
||||
(await request('/api/supplier-payables')).data,
|
||||
baseline,
|
||||
'Business history must remain unchanged',
|
||||
);
|
||||
const groups = procurementNavigation('admin', true);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, '/requisicoes?status=draft&view=mine'),
|
||||
'my_drafts',
|
||||
);
|
||||
assert.equal(activeProcurementEntry(groups, '/ordens/1/recepcoes'), 'receipts');
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, '/requisicoes/1/cotacoes'),
|
||||
'quotations',
|
||||
);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, '/requisicoes/1/relatorio'),
|
||||
'quotations',
|
||||
);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, '/requisicoes/1/adjudicacao'),
|
||||
'orders',
|
||||
);
|
||||
assert.equal(
|
||||
activeProcurementEntry(groups, '/stock/operacoes'),
|
||||
'stock_operations',
|
||||
);
|
||||
await fs.writeFile(
|
||||
'docs/proofs/menu-links.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
results,
|
||||
request_detail_checked: !!requestId,
|
||||
invoice_count: invoices.data.props.invoices.data.length,
|
||||
process_links_checked: [...new Set(processLinks)],
|
||||
canonical_query_order: true,
|
||||
active_subroutes: true,
|
||||
vat_setting_and_default_checked: true,
|
||||
comparison_ties_and_reasons_checked: !!requestId,
|
||||
business_history_unchanged: true,
|
||||
business_commands: 0,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'All visible menu links passed for six roles, with active entries, query filters, detail links and unchanged business history.',
|
||||
);
|
||||
|
||||
@@ -1,49 +1,128 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import {execFile} from 'node:child_process';
|
||||
import {promisify} from 'node:util';
|
||||
const run=promisify(execFile);
|
||||
const php='C:/Users/macbook/.config/investmoz/php-8.5.11/php.exe';
|
||||
const sgf='C:/laragon/www/sgf-tempo';
|
||||
for(const host of ['compras','sgf']) {
|
||||
const exists=await fs.access(`C:/laragon/data/sgf-tempo/secrets/payables-proof-${host}.json`).then(()=>true,()=>false);
|
||||
assert(!exists,'An existing proof must be reconciled before creating a new one.');
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
const run = promisify(execFile);
|
||||
const php = 'C:/Users/macbook/.config/investmoz/php-8.5.11/php.exe';
|
||||
const sgf = 'C:/laragon/www/sgf-tempo';
|
||||
for (const host of ['compras', 'sgf']) {
|
||||
const exists = await fs
|
||||
.access(`C:/laragon/data/sgf-tempo/secrets/payables-proof-${host}.json`)
|
||||
.then(
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
assert(
|
||||
!exists,
|
||||
'An existing proof must be reconciled before creating a new one.',
|
||||
);
|
||||
}
|
||||
async function job(name) {
|
||||
const {stdout}=await run(php,['scripts/payable-history-pg.php',name],{cwd:process.cwd(),windowsHide:true,timeout:90000,maxBuffer:100000});
|
||||
const { stdout } = await run(
|
||||
php,
|
||||
['scripts/payable-history-pg.php', name],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
windowsHide: true,
|
||||
timeout: 90000,
|
||||
maxBuffer: 100000,
|
||||
},
|
||||
);
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
try {
|
||||
await run(php,[`${sgf}/scripts/payables-provision.php`,'--proof'],{cwd:sgf,windowsHide:true,timeout:180000,maxBuffer:100000});
|
||||
const before=await job('setup');
|
||||
const fault=await job('fault');
|
||||
assert(fault.fault_rolled_back);assert.equal(fault.business_fingerprint,before.business_fingerprint);
|
||||
const recognised=await Promise.all([job('validate'),job('validate')]);
|
||||
assert(recognised.every(result=>result.state==='recognised'));
|
||||
assert.equal(recognised[0].obligation_id,recognised[1].obligation_id);
|
||||
const after=await job('inspect');
|
||||
assert.equal(after.principal_minor,1160000);assert.equal(after.balance_minor,860000);
|
||||
assert.equal(after.confirmed_minor,300000);assert.equal(after.pending_minor,100000);assert.equal(after.rejected_minor,50000);
|
||||
assert.equal(after.obligations,1);assert.equal(after.imports,1);assert.equal(after.payment_links,3);
|
||||
assert.equal(after.business_fingerprint,before.business_fingerprint);
|
||||
assert(after.ui_balance_correct);assert(after.changed_history_refused);
|
||||
assert.equal(after.due_on,null);assert.equal(after.days_overdue,null);assert.equal(after.append_only_tables.length,2);
|
||||
if(process.argv.includes('--decisions')) {
|
||||
const fault=await job('decision_fault');
|
||||
assert(fault.rolled_back);assert.equal(fault.balance_minor,860000);
|
||||
const concurrent=await Promise.all([job('decision_confirm'),job('decision_confirm')]);
|
||||
assert(concurrent.every(r=>r.balance_minor===760000 && r.frozen===r.original));
|
||||
const final=await job('decision_inspect');
|
||||
assert.equal(final.balance_minor,760000);assert.equal(final.confirmed_minor,400000);assert.equal(final.pending_minor,0);
|
||||
assert.equal(final.decisions,1);assert.equal(final.events,1);assert.equal(final.original_status,'confirmed');assert(final.append_only);
|
||||
assert(concurrent.every(r=>r.frozen===final.frozen));
|
||||
await fs.writeFile('docs/proofs/payable-payment-decisions-pg.json',JSON.stringify({checked_at:new Date().toISOString(),passed:true,atomic_rollback:true,
|
||||
concurrent_decision_once:true,original_import_preserved:true,new_transfers_executed:false,scenario:final,visual_review:false},null,2)+'\n');
|
||||
await run(php, [`${sgf}/scripts/payables-provision.php`, '--proof'], {
|
||||
cwd: sgf,
|
||||
windowsHide: true,
|
||||
timeout: 180000,
|
||||
maxBuffer: 100000,
|
||||
});
|
||||
const before = await job('setup');
|
||||
const fault = await job('fault');
|
||||
assert(fault.fault_rolled_back);
|
||||
assert.equal(fault.business_fingerprint, before.business_fingerprint);
|
||||
const recognised = await Promise.all([job('validate'), job('validate')]);
|
||||
assert(recognised.every((result) => result.state === 'recognised'));
|
||||
assert.equal(recognised[0].obligation_id, recognised[1].obligation_id);
|
||||
const after = await job('inspect');
|
||||
assert.equal(after.principal_minor, 1160000);
|
||||
assert.equal(after.balance_minor, 860000);
|
||||
assert.equal(after.confirmed_minor, 300000);
|
||||
assert.equal(after.pending_minor, 100000);
|
||||
assert.equal(after.rejected_minor, 50000);
|
||||
assert.equal(after.obligations, 1);
|
||||
assert.equal(after.imports, 1);
|
||||
assert.equal(after.payment_links, 3);
|
||||
assert.equal(after.business_fingerprint, before.business_fingerprint);
|
||||
assert(after.ui_balance_correct);
|
||||
assert(after.changed_history_refused);
|
||||
assert.equal(after.due_on, null);
|
||||
assert.equal(after.days_overdue, null);
|
||||
assert.equal(after.append_only_tables.length, 2);
|
||||
if (process.argv.includes('--decisions')) {
|
||||
const fault = await job('decision_fault');
|
||||
assert(fault.rolled_back);
|
||||
assert.equal(fault.balance_minor, 860000);
|
||||
const concurrent = await Promise.all([
|
||||
job('decision_confirm'),
|
||||
job('decision_confirm'),
|
||||
]);
|
||||
assert(
|
||||
concurrent.every(
|
||||
(r) => r.balance_minor === 760000 && r.frozen === r.original,
|
||||
),
|
||||
);
|
||||
const final = await job('decision_inspect');
|
||||
assert.equal(final.balance_minor, 760000);
|
||||
assert.equal(final.confirmed_minor, 400000);
|
||||
assert.equal(final.pending_minor, 0);
|
||||
assert.equal(final.decisions, 1);
|
||||
assert.equal(final.events, 1);
|
||||
assert.equal(final.original_status, 'confirmed');
|
||||
assert(final.append_only);
|
||||
assert(concurrent.every((r) => r.frozen === final.frozen));
|
||||
await fs.writeFile(
|
||||
'docs/proofs/payable-payment-decisions-pg.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
passed: true,
|
||||
atomic_rollback: true,
|
||||
concurrent_decision_once: true,
|
||||
original_import_preserved: true,
|
||||
new_transfers_executed: false,
|
||||
scenario: final,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
}
|
||||
await fs.writeFile('docs/proofs/payable-history-pg.json',JSON.stringify({checked_at:new Date().toISOString(),passed:true,
|
||||
fault_recovery:true,concurrent_recognition:true,original_business_history_unchanged:true,new_transfers_executed:false,
|
||||
scenario:after,visual_review:false},null,2)+'\n');
|
||||
console.log('PostgreSQL historical import passed: gross principal, single deduction, pending/rejected preservation, concurrency, rollback and immutable history.');
|
||||
await fs.writeFile(
|
||||
'docs/proofs/payable-history-pg.json',
|
||||
JSON.stringify(
|
||||
{
|
||||
checked_at: new Date().toISOString(),
|
||||
passed: true,
|
||||
fault_recovery: true,
|
||||
concurrent_recognition: true,
|
||||
original_business_history_unchanged: true,
|
||||
new_transfers_executed: false,
|
||||
scenario: after,
|
||||
visual_review: false,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
) + '\n',
|
||||
);
|
||||
console.log(
|
||||
'PostgreSQL historical import passed: gross principal, single deduction, pending/rejected preservation, concurrency, rollback and immutable history.',
|
||||
);
|
||||
} finally {
|
||||
await run(php,[`${sgf}/scripts/payables-provision.php`,'--cleanup-proof'],{cwd:sgf,windowsHide:true,timeout:90000,maxBuffer:100000});
|
||||
await run(
|
||||
php,
|
||||
[`${sgf}/scripts/payables-provision.php`, '--cleanup-proof'],
|
||||
{ cwd: sgf, windowsHide: true, timeout: 90000, maxBuffer: 100000 },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user