82 lines
3.9 KiB
JavaScript
82 lines
3.9 KiB
JavaScript
import { test, before, after, mock } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { anonymize, endpointOf, percentile } from "../server/metrics.js";
|
|
import { startApi } from "./support/nextcloud.js";
|
|
|
|
test("Метрики: адрес без последней части, адреса документов в одном счётчике", () => {
|
|
assert.equal(anonymize("203.0.113.57"), "203.0.113.0");
|
|
assert.equal(anonymize("::ffff:192.168.1.20"), "192.168.1.0");
|
|
assert.equal(anonymize("2001:db8:85a3:8d3:1319:8a2e:370:7348"), "2001:db8:85a3::");
|
|
assert.equal(endpointOf("/api/original/0fcb72d4cc36c729aa"), "/api/original/:id");
|
|
assert.equal(endpointOf("/api/timetable"), "/api/timetable");
|
|
// Bounds 25, 50, 100, …: half the answers took up to 50 ms, 95% up to 1000 ms.
|
|
const histogram = [3, 3, 2, 1, 0, 1, 0, 0, 0, 0];
|
|
assert.equal(percentile(histogram, 0.5), 50);
|
|
assert.equal(percentile(histogram, 0.95), 1000);
|
|
assert.equal(percentile(Array(10).fill(0), 0.5), null);
|
|
});
|
|
|
|
// The API with its log on, a low request limit and the log lines caught.
|
|
let api;
|
|
const lines = [];
|
|
before(async () => {
|
|
mock.method(console, "log", (line) => lines.push(line));
|
|
api = await startApi({ PSU_REQUEST_LOG: "on", PSU_API_RATE_LIMIT: "6" });
|
|
});
|
|
after(() => {
|
|
mock.restoreAll();
|
|
api?.stop();
|
|
});
|
|
const get = (path, headers) => fetch(api.base + path, { headers });
|
|
|
|
test("Метрики: строка журнала на запрос и сводка в /api/health", async () => {
|
|
const groups = await get("/api/groups?" + new URLSearchParams({ q: "23-БА" }));
|
|
const doc = (await groups.json()).results.find((g) => g.group === "23-БА").fileId;
|
|
const path = "/api/timetable?" + new URLSearchParams({ doc, group: "23-БА", from: "2026-10-19", days: 7 });
|
|
const etag = (await get(path)).headers.get("etag");
|
|
assert.equal((await get(path, { "If-None-Match": etag, "Cache-Control": "max-age=0" })).status, 304);
|
|
assert.equal((await get("/api/nope")).status, 404);
|
|
// Health checks are neither logged nor counted.
|
|
await get("/api/health");
|
|
await get("/api/ready");
|
|
|
|
const logged = lines.map((l) => JSON.parse(l));
|
|
assert.equal(logged.length, 4);
|
|
assert.deepEqual(logged.map((l) => l.status), [200, 200, 304, 404]);
|
|
const notFound = logged.at(-1);
|
|
assert.equal(notFound.method, "GET");
|
|
assert.equal(notFound.path, "/api/nope");
|
|
assert.equal(notFound.code, "unknown_endpoint");
|
|
assert.equal(notFound.ip, "127.0.0.0");
|
|
assert.equal(typeof notFound.ms, "number");
|
|
assert.ok(!logged.some((l) => l.path.startsWith("/api/health")));
|
|
|
|
const { traffic } = await (await get("/api/health")).json();
|
|
const hour = traffic.lastHour;
|
|
assert.equal(hour.requests, 4);
|
|
assert.equal(hour.notModified, 1);
|
|
assert.equal(hour.notModifiedShare, 0.25);
|
|
assert.equal(hour.clientErrors, 1);
|
|
assert.equal(hour.serverErrors, 0);
|
|
assert.equal(hour.clients, 1);
|
|
assert.deepEqual(hour.busiestClients, [{ client: "127.0.0.0", requests: 4 }]);
|
|
assert.equal(hour.endpoints["/api/timetable"].requests, 2);
|
|
assert.ok(hour.p50Ms !== null && hour.p95Ms !== null);
|
|
assert.equal(traffic.total.requests, 4);
|
|
});
|
|
|
|
test("Метрики: лимит запросов и сбои университета считаются отдельно", async () => {
|
|
const catalog = await (await get("/api/catalog")).json();
|
|
const broken = catalog.files.find((f) => f.name === "Сломанный.docx");
|
|
assert.equal((await get(`/api/original/${broken.id}`)).status, 502);
|
|
// The limit of 6 is reached: 4 earlier requests, the catalog and the original above.
|
|
assert.equal((await get("/api/nope")).status, 429);
|
|
const { traffic } = await (await get("/api/health")).json();
|
|
const hour = traffic.lastHour;
|
|
assert.equal(hour.limited, 1);
|
|
assert.equal(hour.sourceErrors, 1);
|
|
assert.ok(hour.sourceFailures >= 1);
|
|
assert.equal(hour.serverErrors, 1);
|
|
assert.equal(hour.endpoints["/api/original/:id"].errors, 1);
|
|
});
|