import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import { existsSync, readFileSync } from "node:fs"; import { createHash } from "node:crypto"; import { startApi } from "./support/nextcloud.js"; // Protection of a server open to the internet, with the limits lowered so the tests reach them: // five API requests a minute and an 800 ms time limit. let api, base; let sent = 0; before(async () => { api = await startApi({ PSU_API_RATE_LIMIT: "5", PSU_REQUEST_TIMEOUT_MS: "800", PSU_SOURCE_TIMEOUT_MS: "5000" }); base = api.base; }); after(() => api?.stop()); const apiGet = (path) => { sent++; return fetch(base + path); }; test("Защита: заголовки безопасности у страницы и API", async () => { for (const path of ["/", "/api/health"]) { const response = await fetch(base + path); await response.arrayBuffer(); const csp = response.headers.get("content-security-policy"); assert.match(csp, /default-src 'self'/, path); assert.match(csp, /frame-ancestors 'none'/, path); assert.match(csp, /object-src 'none'/, path); assert.equal(response.headers.get("x-content-type-options"), "nosniff", path); assert.equal(response.headers.get("x-frame-options"), "DENY", path); assert.ok(response.headers.get("referrer-policy"), path); assert.ok(response.headers.get("permissions-policy"), path); // Plain HTTP: no HSTS. assert.equal(response.headers.get("strict-transport-security"), null, path); } }); test("Защита: CSP разрешает ровно inline-скрипт темы из собранной страницы", { skip: !existsSync(new URL("../dist/index.html", import.meta.url)) && "нет dist/, сначала npm run build" }, async () => { const html = readFileSync(new URL("../dist/index.html", import.meta.url), "utf8"); const inline = html.match(/