test(e2e): sprint 6 acceptance — US-29 / US-30 / US-31
Adds 3 Playwright spec files covering all 13 ACs for the engagement export feature: - us29-export-formats.spec.ts (8 tests): dropdown, md/csv/pdf downloads, admin + redteam, filename convention - us30-export-rbac.spec.ts (3 tests): SOC button absent, SOC 403, no-token 401 - us31-export-robustness.spec.ts (4 tests): missing format 400, bad format 400, unknown engagement 404, zero-sim export OK Total: 201 → 223 Playwright tests. No regressions on sprints 1–5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
90
e2e/tests/us30-export-rbac.spec.ts
Normal file
90
e2e/tests/us30-export-rbac.spec.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* US-30 — SOC role has zero access to the export feature.
|
||||
*
|
||||
* AC-30.1: SOC login → Export button absent from DOM (not just hidden).
|
||||
* AC-30.2: Direct API call with SOC Bearer → 403.
|
||||
* AC-30.3: Direct API call without token → 401.
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
adminToken,
|
||||
createEngagement,
|
||||
deleteEngagement,
|
||||
deleteUserByUsername,
|
||||
ensureUser,
|
||||
login,
|
||||
type Engagement,
|
||||
} from '../fixtures/api';
|
||||
import { seedTokenInStorage } from '../fixtures/auth';
|
||||
|
||||
const SOC_USER = 'us30-soc';
|
||||
const ADMIN_USER = 'us30-admin';
|
||||
const PASS = 'us30-pass-strong!';
|
||||
|
||||
const BASE_URL = process.env.MIMIC_BASE_URL ?? 'http://localhost:5000';
|
||||
|
||||
test.describe('US-30 — SOC zero access to export', () => {
|
||||
let socTok: string;
|
||||
let adminTok: string;
|
||||
let engagement: Engagement;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await ensureUser(SOC_USER, PASS, 'soc');
|
||||
await ensureUser(ADMIN_USER, PASS, 'admin');
|
||||
socTok = (await login(SOC_USER, PASS)).token;
|
||||
adminTok = (await login(ADMIN_USER, PASS)).token;
|
||||
|
||||
engagement = await createEngagement(adminTok, {
|
||||
name: 'US30 RBAC Engagement',
|
||||
start_date: '2026-01-01',
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
try {
|
||||
await deleteEngagement(adminTok, engagement.id);
|
||||
const rootTok = await adminToken();
|
||||
for (const u of [SOC_USER, ADMIN_USER]) await deleteUserByUsername(rootTok, u);
|
||||
} catch { /* noop */ }
|
||||
});
|
||||
|
||||
// AC-30.1 — SOC: Export button absent from DOM
|
||||
test('AC-30.1 — SOC login: Export dropdown is NOT attached to the DOM', async ({
|
||||
page,
|
||||
context,
|
||||
}) => {
|
||||
await seedTokenInStorage(context, socTok);
|
||||
await page.goto(`/engagements/${engagement.id}`);
|
||||
|
||||
// Wait for the page to fully load (engagement header should be visible)
|
||||
await expect(page.locator('h1, h2').first()).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// The export dropdown wrapper must not be in the DOM at all
|
||||
await expect(
|
||||
page.locator('[data-testid="export-dropdown"]'),
|
||||
).not.toBeAttached();
|
||||
});
|
||||
|
||||
// AC-30.2 — SOC Bearer token → 403
|
||||
test('AC-30.2 — SOC Bearer: GET /api/engagements/<id>/export?format=md → 403', async ({
|
||||
request,
|
||||
}) => {
|
||||
const response = await request.get(
|
||||
`${BASE_URL}/api/engagements/${engagement.id}/export?format=md`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${socTok}` },
|
||||
},
|
||||
);
|
||||
expect(response.status()).toBe(403);
|
||||
});
|
||||
|
||||
// AC-30.3 — No token → 401
|
||||
test('AC-30.3 — No token: GET /api/engagements/<id>/export?format=md → 401', async ({
|
||||
request,
|
||||
}) => {
|
||||
const response = await request.get(
|
||||
`${BASE_URL}/api/engagements/${engagement.id}/export?format=md`,
|
||||
);
|
||||
expect(response.status()).toBe(401);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user