Files
ui/tests/msw/helpers.ts
Oleksandr Bezdieniezhnykh 38eb87fb08 [AZ-456] Test infrastructure: Vitest + MSW + Playwright + scripts
Scaffolds the Blackbox test project per AZ-456 / environment.md across
the three profiles:

- fast  : Vitest 3.x + jsdom + MSW 2.x + RTL/jest-dom; tests/setup.ts
          boots the MSW Node server with onUnhandledRequest:'error',
          afterEach resets handlers, clears bearer + navigate-to-login
          spy. Default handlers ship for every suite service plus OWM
          and tile stand-ins. Fixtures mirror seed_* in test-data.md.
- e2e   : Playwright ^1.49 with chromium + firefox projects against the
          suite docker-compose stack; owm-stub + tile-stub Bun servers,
          playwright-runner image, seeds.sql for the test-db.
- static: scripts/run-tests.sh extended — tsc --noEmit (test config),
          vite build, ripgrep checks (with grep -r fallback), CSV
          report at test-output/static-report.csv per AC-7 columns.

Smoke tests cover AC-3, AC-4 (fast, 5 tests, PASS) and AC-1, AC-2,
AC-5, AC-8 (e2e, gated by Risk 4 docker availability). Static profile
(13 checks) PASS — STC-SEC1 (no literal OWM key) lifted from
QUARANTINE per AZ-447 with a narrowed pattern.

Files:
  +24 tests/**, +10 e2e/**, +vitest.config.ts, +tsconfig.test.json
  ~package.json (test scripts + devDeps for vitest, @testing-library/*,
   msw, @playwright/test, jsdom, @types/node, @vitest/coverage-v8)
  ~scripts/run-tests.sh, scripts/run-performance-tests.sh — switched
   RESULTS_DIR to test-output/, compose path to project-local
  ~.gitignore — added /test-output/

Verification:
  bun run test:fast        → 11 / 11 PASS
  ./scripts/run-tests.sh   → static 13/13 + fast 11/11 PASS, exit 0

Tracker: AZ-456 → In Testing.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 02:57:04 +03:00

58 lines
1.9 KiB
TypeScript

import { HttpResponse, delay } from 'msw'
/** Small, opinionated wrappers over MSW's response helpers used across handlers + tests. */
export function jsonResponse<T>(body: T, init?: ResponseInit) {
return HttpResponse.json(body as object, init)
}
export function errorResponse(status: number, message: string) {
return HttpResponse.json({ error: message, status }, { status })
}
export function noContent() {
return new HttpResponse(null, { status: 204 })
}
export function paginate<T>(items: T[], page = 1, pageSize = items.length) {
const start = (page - 1) * pageSize
const slice = items.slice(start, start + pageSize)
return { items: slice, totalCount: items.length, page, pageSize }
}
/** Inject latency into a handler. Use as: `await latency(50)` inside the resolver. */
export function latency(ms: number) {
return delay(ms)
}
/** Build a Server-Sent-Events (SSE) `text/event-stream` body from a sequence of payloads. */
export function sse(events: Array<{ event?: string; data: unknown; id?: string }>) {
const encoder = new TextEncoder()
const stream = new ReadableStream({
start(controller) {
for (const e of events) {
const lines: string[] = []
if (e.id !== undefined) lines.push(`id: ${e.id}`)
if (e.event !== undefined) lines.push(`event: ${e.event}`)
const payload = typeof e.data === 'string' ? e.data : JSON.stringify(e.data)
for (const line of payload.split('\n')) lines.push(`data: ${line}`)
lines.push('', '')
controller.enqueue(encoder.encode(lines.join('\n')))
}
controller.close()
},
})
return new HttpResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
}
/** Drop the connection mid-flight (used by resilience tests). */
export function dropResponse() {
return HttpResponse.error()
}