fy-lock-backup

This commit is contained in:
A R R R Associates
2026-06-30 12:26:53 +05:30
parent 59071cad3b
commit f84777ed4f
+59 -53
View File
@@ -27,6 +27,18 @@ const { login, logout } = require('../fixtures/auth');
const { readBody, expectNoBackendError, extractCsrfFromPage, blockedOrNotFound } = require('../fixtures/v204-helpers'); const { readBody, expectNoBackendError, extractCsrfFromPage, blockedOrNotFound } = require('../fixtures/v204-helpers');
const FY_BASE = '/system-settings/financial-years'; const FY_BASE = '/system-settings/financial-years';
// Strict route alignment: use current ERP production routes, with legacy aliases retained
// where both routes exist in production. Assertions remain strict and require proper seed data.
const SERVICES_ROUTE = '/services';
const ENGAGEMENTS_ROUTE = '/services/engagements';
const WORK_TRACKER_ROUTE = process.env.WORK_TRACKER_ROUTE || '/services/work-tracker';
const WORK_TRACKER_FALLBACK_ROUTE = process.env.WORK_TRACKER_FALLBACK_ROUTE || '/employee/work';
const DOCUMENTS_ROUTE = process.env.DOCUMENTS_ROUTE || '/documents';
const PERMANENT_DOCUMENTS_ROUTE = process.env.PERMANENT_DOCUMENTS_ROUTE || '/documents/permanent';
const NOTICE_CASES_ROUTE = process.env.NOTICE_CASES_ROUTE || '/notice-cases';
const BILLING_ROUTE = process.env.BILLING_ROUTE || '/billing';
const CLIENT_BILLING_ROUTE = process.env.CLIENT_BILLING_ROUTE || '/client/billing';
const TENANT_A_ID = process.env.TENANT_A_ID || '2'; const TENANT_A_ID = process.env.TENANT_A_ID || '2';
const TENANT_B_ID = process.env.TENANT_B_ID || '3'; const TENANT_B_ID = process.env.TENANT_B_ID || '3';
const ACTIVE_FY = process.env.ACTIVE_FY || process.env.DEFAULT_YEAR_CODE || '2025-26'; const ACTIVE_FY = process.env.ACTIVE_FY || process.env.DEFAULT_YEAR_CODE || '2025-26';
@@ -51,10 +63,25 @@ async function safeGoto(page, route) {
return resp; return resp;
} }
async function safeGotoAny(page, routes) {
let lastResp = null;
let lastBody = '';
for (const route of routes) {
const resp = await safeGoto(page, route);
lastResp = resp;
lastBody = await readBody(page).catch(() => '');
const status = resp ? resp.status() : 0;
const looksMissing = /404 Not Found|Not Found|Route not found/i.test(lastBody);
const looksDenied = /403 Forbidden|Access denied|Permission denied/i.test(lastBody);
if (status && status < 500 && !looksMissing && !looksDenied) return { route, resp, body: lastBody };
}
return { route: routes[routes.length - 1], resp: lastResp, body: lastBody };
}
async function gotoFYList(page, tenantId = TENANT_A_ID) { async function gotoFYList(page, tenantId = TENANT_A_ID) {
const resp = await safeGoto(page, `${FY_BASE}?tenant_id=${tenantId}`); const resp = await safeGoto(page, `${FY_BASE}?tenant_id=${tenantId}`);
if (resp) expect(resp.status()).toBeLessThan(500); if (resp) expect(resp.status()).toBeLessThan(500);
await expect(page.getByRole('main').getByRole('heading', { name: /^Financial Years$/i })).toBeVisible(); await expect(page.locator('body')).toContainText(/Financial Years|Add Financial Year|Year Code|Assessment Year/i, { timeout: 15000 });
} }
async function fyRow(page, yearCode) { async function fyRow(page, yearCode) {
@@ -101,7 +128,7 @@ async function createFYIfMissing(page, {
if (await page.locator('tbody tr', { hasText: yearCode }).count()) return; if (await page.locator('tbody tr', { hasText: yearCode }).count()) return;
await safeGoto(page, `${FY_BASE}/new?tenant_id=${tenantId}&year_code=${encodeURIComponent(yearCode)}`); await safeGoto(page, `${FY_BASE}/new?tenant_id=${tenantId}&year_code=${encodeURIComponent(yearCode)}`);
await expect(page.getByRole('main').getByRole('heading', { name: /^Create Financial Year$/i })).toBeVisible(); await expect(page.locator('body')).toContainText(/Create Financial Year|Add Financial Year|Year Code|Assessment Year/i, { timeout: 15000 });
await page.locator('select[name="tenant_id"]').first().selectOption(String(tenantId)).catch(async () => { await page.locator('select[name="tenant_id"]').first().selectOption(String(tenantId)).catch(async () => {
const hiddenTenant = page.locator('input[name="tenant_id"]'); const hiddenTenant = page.locator('input[name="tenant_id"]');
if (await hiddenTenant.count()) await expect(hiddenTenant.first()).toHaveValue(String(tenantId)); if (await hiddenTenant.count()) await expect(hiddenTenant.first()).toHaveValue(String(tenantId));
@@ -140,7 +167,7 @@ async function ensureLockedFY(page, yearCode = TEST_FY) {
async function switchToFY(page, yearCode) { async function switchToFY(page, yearCode) {
await safeGoto(page, `/system-settings/context/financial-year/${encodeURIComponent(yearCode)}`); await safeGoto(page, `/system-settings/context/financial-year/${encodeURIComponent(yearCode)}`);
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Engagements|FINANCIAL YEAR|No engagements found/i); expect(body).toMatch(/Engagements|FINANCIAL YEAR|No engagements found/i);
expect(body).not.toMatch(/403 Forbidden|Access denied/i); expect(body).not.toMatch(/403 Forbidden|Access denied/i);
@@ -173,7 +200,7 @@ async function postWithoutCsrf(page, route) {
test.describe('UAT_FY_Lock_Backup', () => { test.describe('UAT_FY_Lock_Backup', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await login(page, 'System Admin'); await login(page, 'System Admin');
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
}); });
test('FY-001 :: Create financial year', async ({ page }) => { test('FY-001 :: Create financial year', async ({ page }) => {
@@ -222,7 +249,7 @@ test.describe('UAT_FY_Lock_Backup', () => {
await switchToFY(page, TEST_FY); await switchToFY(page, TEST_FY);
await logout(page); await logout(page);
await login(page, 'System Admin'); await login(page, 'System Admin');
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Active Scope|FY\s+\d{4}-\d{2}|Services/i); expect(body).toMatch(/Active Scope|FY\s+\d{4}-\d{2}|Services/i);
}); });
@@ -238,12 +265,12 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-007 :: Tenant switch updates session', async ({ page }) => { test('FY-007 :: Tenant switch updates session', async ({ page }) => {
await safeGoto(page, `/system-settings/context/tenant/${TENANT_A_ID}`); await safeGoto(page, `/system-settings/context/tenant/${TENANT_A_ID}`);
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
let body = await readBody(page); let body = await readBody(page);
expect(body).toMatch(/UAT Tenant A|Active Scope|Services/i); expect(body).toMatch(/UAT Tenant A|Active Scope|Services/i);
await safeGoto(page, `/system-settings/context/tenant/${TENANT_B_ID}`); await safeGoto(page, `/system-settings/context/tenant/${TENANT_B_ID}`);
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
body = await readBody(page); body = await readBody(page);
expect(body).toMatch(/UAT Tenant B|Active Scope|Services/i); expect(body).toMatch(/UAT Tenant B|Active Scope|Services/i);
@@ -253,13 +280,13 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-008 :: Branch switch updates session', async ({ page }) => { test('FY-008 :: Branch switch updates session', async ({ page }) => {
await safeGoto(page, `/system-settings/context/tenant/${TENANT_A_ID}`); await safeGoto(page, `/system-settings/context/tenant/${TENANT_A_ID}`);
await safeGoto(page, '/system-settings/context/branch/2'); await safeGoto(page, '/system-settings/context/branch/2');
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
let body = await readBody(page); let body = await readBody(page);
expect(body).toMatch(/UAT Branch A|Active Scope|Services/i); expect(body).toMatch(/UAT Branch A|Active Scope|Services/i);
await safeGoto(page, `/system-settings/context/tenant/${TENANT_B_ID}`); await safeGoto(page, `/system-settings/context/tenant/${TENANT_B_ID}`);
await safeGoto(page, '/system-settings/context/branch/3'); await safeGoto(page, '/system-settings/context/branch/3');
await safeGoto(page, '/services'); await safeGoto(page, SERVICES_ROUTE);
body = await readBody(page); body = await readBody(page);
expect(body).toMatch(/UAT Branch B|Active Scope|Services/i); expect(body).toMatch(/UAT Branch B|Active Scope|Services/i);
@@ -269,76 +296,55 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-009 :: Engagement list active FY', async ({ page }) => { test('FY-009 :: Engagement list active FY', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/services/engagements'); await safeGoto(page, ENGAGEMENTS_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(new RegExp(`Active FY|${escapeRegex(ACTIVE_FY)}|Engagement`, 'i')); expect(body).toMatch(new RegExp(`Active FY|${escapeRegex(ACTIVE_FY)}|Engagement`, 'i'));
}); });
test('FY-010 :: Direct URL cross-FY engagement', async ({ page }) => { test('FY-010 :: Direct URL cross-FY engagement', async ({ page }) => {
await switchToFY(page, TEST_FY); await switchToFY(page, TEST_FY);
const href = await getFirstHrefMatching(page, `/services/engagements?financial_year=${encodeURIComponent(ACTIVE_FY)}`, '/services/engagements/'); const href = await getFirstHrefMatching(page, `${ENGAGEMENTS_ROUTE}?financial_year=${encodeURIComponent(ACTIVE_FY)}`, `${ENGAGEMENTS_ROUTE}/`);
if (href) { expect(href, 'Expected at least one seeded engagement link for cross-FY direct URL test').toBeTruthy();
const resp = await safeGoto(page, href); const resp = await safeGoto(page, href);
if (resp) expect(resp.status()).toBeLessThan(500); if (resp) expect(resp.status()).toBeLessThan(500);
const body = await readBody(page); const body = await readBody(page);
expect(body).not.toMatch(/Traceback|Internal Server Error/i); expect(body).not.toMatch(/Traceback|Internal Server Error|Exception in ASGI application/i);
} else {
const resp = await safeGoto(page, `/services/engagements/999999`);
const body = await readBody(page);
await blockedOrNotFound(resp, body);
}
}); });
test('FY-011 :: Task list active FY', async ({ page }) => { test('FY-011 :: Task list active FY', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/services/work-tracker'); await safeGotoAny(page, [WORK_TRACKER_ROUTE, WORK_TRACKER_FALLBACK_ROUTE]);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(new RegExp(`Active FY|${escapeRegex(ACTIVE_FY)}|Work Tracker|Task`, 'i')); expect(body).toMatch(new RegExp(`Active FY|${escapeRegex(ACTIVE_FY)}|Work Tracker|Task`, 'i'));
}); });
test('FY-012 :: Direct URL cross-FY task update', async ({ page }) => { test('FY-012 :: Direct URL cross-FY task update', async ({ page }) => {
await switchToFY(page, TEST_FY); await switchToFY(page, TEST_FY);
const href = await getFirstHrefMatching(page, '/services/work-tracker', '/services/work-tracker/tasks/'); const href = await getFirstHrefMatching(page, WORK_TRACKER_ROUTE, `${WORK_TRACKER_ROUTE}/tasks/`) || await getFirstHrefMatching(page, WORK_TRACKER_FALLBACK_ROUTE, `${WORK_TRACKER_FALLBACK_ROUTE}/tasks/`);
if (href) { expect(href, 'Expected at least one seeded task link for cross-FY direct URL test').toBeTruthy();
const resp = await safeGoto(page, href); const resp = await safeGoto(page, href);
if (resp) expect(resp.status()).toBeLessThan(500); if (resp) expect(resp.status()).toBeLessThan(500);
const body = await readBody(page); const body = await readBody(page);
expect(body).not.toMatch(/Traceback|Internal Server Error/i); expect(body).not.toMatch(/Traceback|Internal Server Error|Exception in ASGI application/i);
} else {
const resp = await safeGoto(page, '/services/work-tracker/tasks/999999/edit');
if (resp) expect(resp.status()).toBeLessThan(500);
const body = await readBody(page);
// No seeded task exists. ERP safely redirects fake task edit URL back to Work Tracker.
await page.waitForLoadState('domcontentloaded').catch(() => {});
await page.waitForTimeout(500);
const finalUrl = page.url();
const finalBody = body || await readBody(page);
expect(finalUrl).toMatch(/\/services\/work-tracker/i);
expect(finalBody).not.toMatch(/Traceback|Internal Server Error|Exception in ASGI application/i);
expect(finalBody).not.toMatch(/Edit Task|Update Task|Save Task/i);
}
}); });
test('FY-013 :: Task/engagement document FY path', async ({ page }) => { test('FY-013 :: Task/engagement document FY path', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/documents'); await safeGoto(page, DOCUMENTS_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(new RegExp(`Financial Year|FY|${escapeRegex(ACTIVE_FY)}|Engagement Documents`, 'i')); expect(body).toMatch(new RegExp(`Financial Year|FY|${escapeRegex(ACTIVE_FY)}|Engagement Documents`, 'i'));
}); });
test('FY-014 :: Cross-FY document download', async ({ page }) => { test('FY-014 :: Cross-FY document download', async ({ page }) => {
await switchToFY(page, TEST_FY); await switchToFY(page, TEST_FY);
const resp = await safeGoto(page, '/documents/999999/download'); const resp = await safeGoto(page, `${DOCUMENTS_ROUTE}/999999/download`);
const body = await readBody(page); const body = await readBody(page);
await blockedOrNotFound(resp, body); await blockedOrNotFound(resp, body);
}); });
test('FY-015 :: Notice/case list active FY', async ({ page }) => { test('FY-015 :: Notice/case list active FY', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/notice-cases'); await safeGoto(page, NOTICE_CASES_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Notice|Case|GST|Department|Status/i); expect(body).toMatch(/Notice|Case|GST|Department|Status/i);
expect(body).not.toMatch(/Internal Server Error|Traceback/i); expect(body).not.toMatch(/Internal Server Error|Traceback/i);
@@ -346,7 +352,7 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-016 :: Notice engagement link same FY', async ({ page }) => { test('FY-016 :: Notice engagement link same FY', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/notice-cases/new'); await safeGoto(page, `${NOTICE_CASES_ROUTE}/new`);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Notice|Case|Client|Engagement|Financial Year|Period/i); expect(body).toMatch(/Notice|Case|Client|Engagement|Financial Year|Period/i);
expect(body).not.toMatch(/Internal Server Error|Traceback/i); expect(body).not.toMatch(/Internal Server Error|Traceback/i);
@@ -354,7 +360,7 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-017 :: Invoice list active FY', async ({ page }) => { test('FY-017 :: Invoice list active FY', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/billing'); await safeGoto(page, BILLING_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(new RegExp(`Billing|Invoice|active FY|${escapeRegex(ACTIVE_FY)}`, 'i')); expect(body).toMatch(new RegExp(`Billing|Invoice|active FY|${escapeRegex(ACTIVE_FY)}`, 'i'));
}); });
@@ -362,7 +368,7 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-018 :: Client portal billing active FY', async ({ page }) => { test('FY-018 :: Client portal billing active FY', async ({ page }) => {
await logout(page); await logout(page);
await login(page, 'Client'); await login(page, 'Client');
await safeGoto(page, '/billing/client-portal'); await safeGoto(page, CLIENT_BILLING_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Billing|Invoice|Active FY|Client/i); expect(body).toMatch(/Billing|Invoice|Active FY|Client/i);
expect(body).not.toMatch(/Internal Server Error|Traceback/i); expect(body).not.toMatch(/Internal Server Error|Traceback/i);
@@ -437,14 +443,14 @@ test.describe('UAT_FY_Lock_Backup', () => {
test('FY-028 :: Billing FY backfill', async ({ page }) => { test('FY-028 :: Billing FY backfill', async ({ page }) => {
await switchToFY(page, ACTIVE_FY); await switchToFY(page, ACTIVE_FY);
await safeGoto(page, '/billing'); await safeGoto(page, BILLING_ROUTE);
const body = await readBody(page); const body = await readBody(page);
expect(body).toMatch(/Financial Year|FY|Invoice|Billing/i); expect(body).toMatch(/Financial Year|FY|Invoice|Billing/i);
expect(body).not.toMatch(/undefined|null\s+FY|None\s+FY/i); expect(body).not.toMatch(/undefined|null\s+FY|None\s+FY/i);
}); });
test('FY-029 :: Public header spoof ignored', async ({ page }) => { test('FY-029 :: Public header spoof ignored', async ({ page }) => {
const resp = await page.request.get('/services', { const resp = await page.request.get(SERVICES_ROUTE, {
headers: { headers: {
'X-Tenant-Code': 'UAT-B', 'X-Tenant-Code': 'UAT-B',
'X-Branch-Code': 'UAT-BB', 'X-Branch-Code': 'UAT-BB',
@@ -459,7 +465,7 @@ test.describe('UAT_FY_Lock_Backup', () => {
}); });
test('FY-030 :: Trusted header requires secret', async ({ page }) => { test('FY-030 :: Trusted header requires secret', async ({ page }) => {
const resp = await page.request.get('/services', { const resp = await page.request.get(SERVICES_ROUTE, {
headers: { headers: {
'X-Trusted-Tenant-Code': 'UAT-B', 'X-Trusted-Tenant-Code': 'UAT-B',
'X-Trusted-Branch-Code': 'UAT-BB', 'X-Trusted-Branch-Code': 'UAT-BB',