Files
arrr-erp-test-v2/tests/noticecases-services-work.spec.js
T
2026-06-24 20:05:36 +05:30

332 lines
14 KiB
JavaScript

/**
* =============================================================================
* UAT_NoticeCases_Services_Work -- Notice/case depth, services depth,
* work detail and task operations
* =============================================================================
*
* Covers:
* CASE-* : Notice case detail, edit, sub-pages (events, hearings, orders)
* SVC-* : Services — bulk imports, bulk lock, task operations, subscriptions
* WORK-* : Work detail — task status, comments, engagement detail
* SEC-* : CSRF rejection and IDOR probes for all three modules
*
* Required .env additions:
* NOTICE_CASE_A_ID= # already in main suite; also used here for sub-pages
* CASE_DOCUMENT_A_ID= # a seeded notice case document id
* SUBSCRIPTION_A_ID= # a seeded client service subscription id
* TASK_A_ID= # a seeded service execution task id
* ENGAGEMENT_A_ID= # a seeded service engagement id
*
* =============================================================================
*/
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
async function safeGoto(page, route) {
const resp = await page.goto(route).catch(() => null);
await page.waitForLoadState('domcontentloaded').catch(() => {});
return resp;
}
function idOr(envKey, fallback = '1') {
return process.env[envKey] || fallback;
}
function skipIfMissing(envKey) {
if (!process.env[envKey]) test.skip(true, `Set ${envKey} in .env after seeding`);
}
// ---------------------------------------------------------------------------
// Notice cases — sub-pages not covered in existing vapt-targeted.spec.js
// ---------------------------------------------------------------------------
test.describe('CASE: Notice case sub-pages', () => {
test('[V25-CASE-001] CASE-001 Notice case list loads', async ({ page }) => {
await login(page, 'System Admin');
await safeGoto(page, '/notice-cases');
await expectNoBackendError(page);
});
test('[V25-CASE-002] CASE-002 Notice case detail page loads', async ({ page }) => {
skipIfMissing('NOTICE_CASE_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/notice-cases/${idOr('NOTICE_CASE_A_ID')}`);
await expectNoBackendError(page);
});
test('[V25-CASE-003] CASE-003 Notice case edit page loads', async ({ page }) => {
skipIfMissing('NOTICE_CASE_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/notice-cases/${idOr('NOTICE_CASE_A_ID')}/edit`);
await expectNoBackendError(page);
});
test('[V25-CASE-004] CASE-004 Notice case events sub-page loads', async ({ page }) => {
skipIfMissing('NOTICE_CASE_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/notice-cases/${idOr('NOTICE_CASE_A_ID')}/events`);
await expectNoBackendError(page);
});
test('[V25-CASE-005] CASE-005 Notice case hearings sub-page loads', async ({ page }) => {
skipIfMissing('NOTICE_CASE_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/notice-cases/${idOr('NOTICE_CASE_A_ID')}/hearings`);
await expectNoBackendError(page);
});
test('[V25-CASE-006] CASE-006 Notice case orders sub-page loads', async ({ page }) => {
skipIfMissing('NOTICE_CASE_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/notice-cases/${idOr('NOTICE_CASE_A_ID')}/orders`);
await expectNoBackendError(page);
});
test('[V25-CASE-007] CASE-007 Notice case document download requires auth', async ({ page }) => {
skipIfMissing('CASE_DOCUMENT_A_ID');
// test as anonymous
const resp = await safeGoto(page, `/notice-cases/documents/${idOr('CASE_DOCUMENT_A_ID')}/download`);
const body = await readBody(page);
await blockedOrNotFound(resp, body);
});
test('[V25-CASE-008] CASE-008 Notice case document delete CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('CASE_DOCUMENT_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/notice-cases/documents/${idOr('CASE_DOCUMENT_A_ID')}/delete`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-CASE-009] CASE-009 Notice case upload CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('NOTICE_CASE_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/notice-cases/${idOr('NOTICE_CASE_A_ID')}/documents/upload`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-CASE-010] CASE-010 Client cannot access notice cases', async ({ page }) => {
await login(page, 'Client');
const resp = await safeGoto(page, '/notice-cases');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-CASE-011] CASE-011 IDOR: cross-case document download blocked', async ({ page }) => {
await login(page, 'Staff');
const resp = await safeGoto(page, '/notice-cases/999999999/documents/999999/download');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-CASE-012] CASE-012 Non-existent case detail returns safe response', async ({ page }) => {
await login(page, 'System Admin');
const resp = await safeGoto(page, '/notice-cases/999999999');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
});
// ---------------------------------------------------------------------------
// Services — bulk imports, bulk lock, subscription ops
// ---------------------------------------------------------------------------
test.describe('SVC: Services depth', () => {
test('[V25-SVC-001] SVC-001 Bulk imports page loads', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/services/bulk-imports');
await expectNoBackendError(page);
});
test('[V25-SVC-002] SVC-002 Bulk imports service-master template downloads', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/services/bulk-imports/templates/service-master.xlsx');
expect(resp.status()).toBeLessThan(500);
});
test('[V25-SVC-003] SVC-003 Bulk imports engagement-assignments template downloads', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/services/bulk-imports/templates/engagement-assignments.xlsx');
expect(resp.status()).toBeLessThan(500);
});
test('[V25-SVC-004] SVC-004 Bulk imports firm-task-templates template downloads', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/services/bulk-imports/templates/firm-task-templates.xlsx');
expect(resp.status()).toBeLessThan(500);
});
test('[V25-SVC-005] SVC-005 Bulk import preview CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/bulk-imports/service-master`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-SVC-006] SVC-006 Subscription detail loads', async ({ page }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/services/${idOr('SUBSCRIPTION_A_ID')}`);
await expectNoBackendError(page);
});
test('[V25-SVC-007] SVC-007 Subscription edit page loads', async ({ page }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/services/${idOr('SUBSCRIPTION_A_ID')}/edit`);
await expectNoBackendError(page);
});
test('[V25-SVC-008] SVC-008 Subscription lock CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/services/${idOr('SUBSCRIPTION_A_ID')}/lock`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-SVC-009] SVC-009 Bulk lock CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/bulk-lock`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-SVC-010] SVC-010 Subscription generate CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/services/subscriptions/${idOr('SUBSCRIPTION_A_ID')}/generate`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-SVC-011] SVC-011 Client cannot access services admin', async ({ page }) => {
await login(page, 'Client');
const resp = await safeGoto(page, '/services/bulk-imports');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-SVC-012] SVC-012 Non-existent subscription ID returns safe response', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/services/999999999');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-SVC-013] SVC-013 Task bulk-update CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/tasks/bulk-update`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
});
// ---------------------------------------------------------------------------
// Work detail — task operations
// ---------------------------------------------------------------------------
test.describe('WORK: Work detail and task operations', () => {
test('[V25-WORK-001] WORK-001 Engagement work detail page loads', async ({ page }) => {
skipIfMissing('ENGAGEMENT_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/work/engagements/${idOr('ENGAGEMENT_A_ID')}`);
await expectNoBackendError(page);
});
test('[V25-WORK-002] WORK-002 Task status update CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
{ form: { status: 'completed', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-WORK-003] WORK-003 Task comment POST CSRF-less is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/comment`,
{ form: { message: 'test comment', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-WORK-004] WORK-004 Task comments list page loads', async ({ page }) => {
skipIfMissing('TASK_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/services/tasks/${idOr('TASK_A_ID')}/comments`);
await expectNoBackendError(page);
});
test('[V25-WORK-005] WORK-005 Task edit page loads', async ({ page }) => {
skipIfMissing('TASK_A_ID');
await login(page, 'System Admin');
await safeGoto(page, `/services/tasks/${idOr('TASK_A_ID')}/edit`);
await expectNoBackendError(page);
});
test('[V25-WORK-006] WORK-006 Task upload CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/documents/tasks/${idOr('TASK_A_ID')}/upload`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-WORK-007] WORK-007 Non-existent task returns safe response', async ({ page }) => {
await login(page, 'System Admin');
const resp = await safeGoto(page, '/services/tasks/999999999/edit');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-WORK-008] WORK-008 IDOR: Staff cannot update task assigned to another user via direct POST', async ({ request }) => {
skipIfMissing('TASK_A_ID');
// Raw API attempt without valid session for a different user
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
{ form: { status: 'completed', csrf_token: 'invalid' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-WORK-009] WORK-009 Client cannot access work detail', async ({ page }) => {
skipIfMissing('ENGAGEMENT_A_ID');
await login(page, 'Client');
const resp = await safeGoto(page, `/work/engagements/${idOr('ENGAGEMENT_A_ID')}`);
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
});