Files
arrr-erp-test-v2/tests/full-excel-variants.spec.js
T
2026-06-28 12:57:42 +05:30

201 lines
7.5 KiB
JavaScript

const { test, expect, request } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const matrix = require('../data/generated-test-matrix.json');
const { login, logout, fillFirst, clickFirst } = require('../fixtures/auth');
const { expectNoServerError, expectBlockedOrSafe, expectSecurityHeaders, expectCookieFlags } = require('../fixtures/assertions');
function title(v) {
return `[${v.variantId}] ${v.sheet} :: ${v.scenario} :: ${v.variantName}`;
}
function unauthorizedRole(v) {
const r = (v.role || '').toLowerCase();
if (r.includes('client')) return 'Staff';
if (r.includes('staff') || r.includes('employee')) return 'Client';
if (r.includes('consultant')) return 'Client';
return 'Staff';
}
async function safeGoto(page, route) {
const resp = await page.goto(route || '/employee/dashboard');
await page.waitForLoadState('domcontentloaded').catch(() => {});
return resp;
}
async function submitBlankFormIfAny(page) {
const submit = page.locator('form button[type="submit"], form input[type="submit"]').first();
if (await submit.count()) {
await submit.click().catch(() => {});
await page.waitForLoadState('domcontentloaded').catch(() => {});
}
}
async function boundaryInputIfAny(page) {
const longText = 'X'.repeat(2048);
const input = page.locator('form input[type="text"], form textarea').first();
if (await input.count()) await input.fill(longText).catch(() => {});
await submitBlankFormIfAny(page);
}
function sec101TenantBCaseId() {
return process.env.TENANT_B_NOTICE_CASE_ID || process.env.CROSS_TENANT_NOTICE_CASE_ID || '7';
}
async function expectNoTenantBNoticeLeak(page, resp) {
// A correct ERP may reject with 401/403/404/405/422 or redirect away.
// If it returns 200/303, the page still must not disclose Tenant B notice data.
await expectBlockedOrSafe(page, resp);
const body = await page.locator('body').innerText().catch(() => '');
expect(body).not.toMatch(/Tenant B SEC-101 Cross Tenant Notice/i);
expect(body).not.toMatch(/UAT-B-NOTICE-SEC101/i);
expect(body).not.toMatch(/UAT-NC-B-SEC101/i);
expect(body).not.toMatch(/Traceback|Internal Server Error|Exception/i);
}
async function runSec101CrossTenant(page, context, v) {
const tenantBNoticeCaseId = sec101TenantBCaseId();
await login(page, 'System Admin'); // Tenant A system admin from env, e.g. uat.admin@vavalam.com.
const variant = String(v.variantType || '').toLowerCase();
const probes = [];
if (variant === 'negative') {
probes.push(`/notice-cases/${tenantBNoticeCaseId}?tamper=1`);
probes.push(`/notice-cases/${tenantBNoticeCaseId}/../../${tenantBNoticeCaseId}`);
} else if (variant === 'rbac') {
await logout(page);
await login(page, 'Staff');
probes.push(`/notice-cases/${tenantBNoticeCaseId}`);
} else if (variant === 'idor') {
probes.push(`/notice-cases/${tenantBNoticeCaseId}`);
probes.push(`/notice-cases/${tenantBNoticeCaseId}/edit`);
probes.push(`/notice-cases/${tenantBNoticeCaseId}/documents`);
} else if (variant === 'headers') {
await context.setExtraHTTPHeaders({
'X-Tenant-ID': '3',
'X-Branch-ID': '3',
'X-Tenant-Code': 'UAT-B',
'X-Branch-Code': 'UAT-BB',
});
probes.push(`/notice-cases/${tenantBNoticeCaseId}`);
} else {
probes.push(`/notice-cases/${tenantBNoticeCaseId}`);
}
for (const route of probes) {
const resp = await safeGoto(page, route);
await expectNoTenantBNoticeLeak(page, resp);
}
}
async function runSecuritySpecific(page, context, v) {
const id = v.sourceId;
if (/SEC-101/i.test(id)) {
await runSec101CrossTenant(page, context, v);
return;
}
if (/SEC-001/i.test(id)) {
await page.goto('/login');
for (let i = 0; i < 5; i++) {
await fillFirst(page, ['input[name="email"]','input[name="login_id"]','input[type="email"]'], process.env.SYSTEM_ADMIN_EMAIL || 'uat.admin@vavalam.com');
await fillFirst(page, ['input[name="password"]','input[type="password"]'], 'wrong-password-' + i);
await clickFirst(page, ['button[type="submit"]','input[type="submit"]']);
await page.waitForLoadState('domcontentloaded').catch(() => {});
}
await expectNoServerError(page);
return;
}
if (/SEC-002/i.test(id)) {
await login(page, 'System Admin');
await logout(page);
const resp = await safeGoto(page, '/employee/dashboard');
await expectBlockedOrSafe(page, resp);
return;
}
if (/SEC-007|GEN-011/i.test(id)) {
const api = await request.newContext({ baseURL: BASE_URL });
const resp = await api.post('/notice-cases/new', { form: { title: 'csrf-test-no-token' } });
expect([400,401,403,404,405,422,303].includes(resp.status())).toBeTruthy();
return;
}
if (/SEC-017/i.test(id)) {
await login(page, 'System Admin');
await expectCookieFlags(context);
return;
}
if (/SEC-026/i.test(id)) {
const resp = await page.goto('/login');
await expectSecurityHeaders(resp);
return;
}
if (/SEC-010|SEC-103|SEC-104|SEC-110|SEC-111|DOM-VAPT/i.test(id)) {
await login(page, 'Staff');
const probes = ['/documents/../../app/main.py','/documents/999999/download','/notice-cases/999999999','/notice-cases/999999999/documents/999999/download','/domains/999999999/edit'];
for (const p of probes) {
const resp = await safeGoto(page, p);
await expectBlockedOrSafe(page, resp);
}
return;
}
await login(page, v.role || 'System Admin');
const resp = await safeGoto(page, v.route);
await expectNoServerError(page);
if (v.variantType === 'headers') await expectCookieFlags(context);
}
for (const v of matrix) {
test(title(v), async ({ page, context }) => {
test.info().annotations.push({ type: 'sourceId', description: v.sourceId });
test.info().annotations.push({ type: 'variantType', description: v.variantType });
test.info().annotations.push({ type: 'expected', description: String(v.expected || '').slice(0, 250) });
// SEC-101 was marked manual in the generated matrix, but it is now automated here
// using a seeded Tenant B notice case. This keeps ERP features unchanged and only
// replaces the scaffold/manual handling for these five variants.
if (/SEC-101/i.test(v.sourceId || '')) {
await runSec101CrossTenant(page, context, v);
return;
}
if (v.automation === 'manual') {
test.skip(true, v.manualReason || 'Marked manual in generated matrix');
}
if (v.variantType === 'security' || v.sheet.startsWith('VAPT_')) {
await runSecuritySpecific(page, context, v);
return;
}
if (v.variantType === 'rbac') {
await login(page, unauthorizedRole(v));
const resp = await safeGoto(page, v.route);
await expectNoServerError(page);
// A safe result may be allowed page for common dashboards, but must not leak obvious restricted data or crash.
if (!['/login','/employee/dashboard'].includes(v.route)) {
const body = await page.locator('body').innerText().catch(()=>'');
expect(body).not.toMatch(/Traceback|Internal Server Error|Exception/i);
}
return;
}
await login(page, v.role || 'System Admin');
const resp = await safeGoto(page, v.route);
await expectNoServerError(page);
if (v.variantType === 'positive') {
expect(resp.status()).toBeLessThan(500);
} else if (v.variantType === 'negative' || v.variantType === 'validation') {
await submitBlankFormIfAny(page);
await expectNoServerError(page);
} else if (v.variantType === 'boundary') {
await boundaryInputIfAny(page);
await expectNoServerError(page);
}
});
}