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:
+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.',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user