/** * 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//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//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); }); });