Files
ui/e2e/tests/protected_route.e2e.ts
T
Oleksandr Bezdieniezhnykh 2051088706 [AZ-458] [AZ-467] [AZ-468] [AZ-482] Batch 3 - SSE/RBAC/Header/security tests
Implements 4 blackbox-test tasks for AZ-455 Phase A baseline:

- AZ-458 SSE lifecycle + bearer rotation: 9 fast tests (8 pass, 1
  QUARANTINE for annotation-status); 4 e2e scenarios (gated by suite
  stack). Uses tests/helpers/sse-mock.ts with globalThis.EventSource
  monkey-patch per AC-3 (no stub of src/api/sse.ts). AC-2 bearer
  rotation captured as documented drift via it.fails() — FlightsPage
  useEffect deps do not include the token today.

- AZ-467 ProtectedRoute spinner + timeout + RBAC: 9 new fast tests
  extending the AZ-457 file (6 pass, 3 QUARANTINE), plus 3 e2e
  scenarios. FT-P-32 spinner a11y is it.fails() drift; FT-P-33 timeout
  and FT-N-03/05 RBAC redirects are it.skip QUARANTINE (no production
  behavior today). Positive control: admin_carol reaches /admin.

- AZ-468 Header flight-dropdown a11y: 6 fast tests (5 pass, 1
  QUARANTINE). FT-P-30/31 are it.fails() drift (aria-expanded /
  role=listbox / aria-activedescendant currently missing); FT-N-09
  is it.skip QUARANTINE (no document keydown handler exists).

- AZ-482 Secrets + banned-libs + AC-N1 anti-criterion: 3 new static
  checks (STC-SEC13 legacy integrations, STC-SEC14 concurrent-edit,
  STC-SEC1B dist/ OWM key) plus refactor of 4 existing checks
  (STC-N2/N4/S13/S6) to read from tests/security/banned-deps.json
  via scripts/check-banned-deps.mjs per AZ-482 constraint
  ("deny-list lives in tests/security/banned-deps.json so additions
  are visible in code review"). All 22 static checks PASS.

Totals: 57 fast tests pass + 9 skipped; 22/22 static checks pass.
Self-review verdict PASS_WITH_WARNINGS — all five findings are
documented drifts captured by it.fails() / it.skip QUARANTINE +
control tests. See _docs/03_implementation/batch_03_report.md
for the per-task / per-AC matrix and recommended Phase B follow-up
production tasks (Header a11y; ProtectedRoute spinner/timeout/RBAC;
SSE bearer-rotation reconnect; AnnotationsPage SSE).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 03:46:18 +03:00

63 lines
2.5 KiB
TypeScript

import { test, expect } from '@playwright/test'
// AZ-467 — e2e variants of the RBAC scenarios that require the real
// admin/ service to issue role-specific bearers and the suite's nginx to
// route /admin and /settings.
//
// FT-N-03 — Operator → /admin redirects to /flights (or to /login if
// permission middleware is unauthenticated-equivalent)
// FT-N-05 — integrator-dave → /settings redirects (no SETTINGS perm)
//
// Profile: e2e (gated by docker compose). Skipped in fast/host runs.
//
// Production status: src/auth/ProtectedRoute.tsx does NOT check
// permissions today (only `user != null`). These tests are wrapped in
// `test.fail()` to capture the drift — they will start passing once
// ProtectedRoute gains a `requirePermission` prop (or wrapping) and the
// /admin and /settings routes opt in.
const OPERATOR_EMAIL = 'op_bob@test.local' // Operator without ADMIN_WRITE / SETTINGS
const INTEGRATOR_EMAIL = 'integrator_dave@test.local' // SystemIntegrator without SETTINGS
const ADMIN_EMAIL = 'admin_carol@test.local' // Admin with full perms
const TEST_PASSWORD = 'TestPassword!23'
async function login(page: import('@playwright/test').Page, email: string) {
await page.goto('/login')
await page.getByLabel(/email/i).fill(email)
await page.getByLabel(/password/i).fill(TEST_PASSWORD)
await Promise.all([
page.waitForResponse(
(r) => r.url().includes('/api/admin/auth/login') && r.request().method() === 'POST',
),
page.getByRole('button', { name: /sign in/i }).click(),
])
}
test.describe('AZ-467 e2e — RBAC route gating', () => {
test('FT-N-03 — Operator hitting /admin is redirected to /flights (AC-3 drift)', async ({ page }) => {
test.fail(
true,
'AC-3 drift: src/auth/ProtectedRoute.tsx today checks only `user != null`. Test passes once route-level RBAC lands.',
)
await login(page, OPERATOR_EMAIL)
await page.goto('/admin')
await expect(page).toHaveURL(/\/flights$/)
})
test('FT-N-05 — integrator-dave hitting /settings is redirected away (AC-3 drift)', async ({ page }) => {
test.fail(
true,
'AC-3 drift: same as FT-N-03 — ProtectedRoute does not gate on permissions today.',
)
await login(page, INTEGRATOR_EMAIL)
await page.goto('/settings')
await expect(page).not.toHaveURL(/\/settings$/)
})
test('Admin reaches /admin normally (positive control)', async ({ page }) => {
await login(page, ADMIN_EMAIL)
await page.goto('/admin')
await expect(page).toHaveURL(/\/admin$/)
})
})