/** * Sprint 28 — security headers verification. * * Поверяет, что ВСЕ нужные security-заголовки выставлены на каждом * ответе SPA, в т.ч. на 4xx/404. Регрессионная защита: если кто-то * случайно уберёт `always` или `add_header` — этот тест поймает. */ import { expect, test } from '@playwright/test' import { baseUrl } from '../regression/factories/api-client.js' const REQUIRED_HEADERS = [ // Sprint 13. 'content-security-policy', 'x-frame-options', 'x-content-type-options', 'referrer-policy', 'permissions-policy', 'x-permitted-cross-domain-policies', // Sprint 28. 'strict-transport-security', ] test.describe('27.9 security headers', () => { test('все обязательные security-заголовки на главной странице', async () => { const r = await fetch(`${baseUrl}/`) expect(r.status).toBeLessThan(500) const headers = Object.fromEntries( [...r.headers.entries()].map(([k, v]) => [k.toLowerCase(), v]), ) for (const h of REQUIRED_HEADERS) { expect(headers[h], `missing header: ${h}`).toBeTruthy() } // HSTS специфика. expect(headers['strict-transport-security']).toMatch(/max-age=\d+/) }) test('заголовки сохраняются на 404 (always-параметр)', async () => { const r = await fetch(`${baseUrl}/this-path-does-not-exist-deliberately`) // Может быть 404 или 200 (SPA fallback) — оба ок. const headers = Object.fromEntries( [...r.headers.entries()].map(([k, v]) => [k.toLowerCase(), v]), ) for (const h of REQUIRED_HEADERS) { expect(headers[h], `missing header on 404: ${h}`).toBeTruthy() } }) })