Files
HealthLog/tests/integration/admin-support-report.test.ts
Marc-André Bombeck b6da360856 refactor(db): drop the runtime schema mirror
ensureDbCompatibility ran ALTER TABLE / CREATE TABLE IF NOT EXISTS on the
login, register, token and session paths, mirroring columns every
migration under prisma/migrations already creates. The entrypoint applies
those migrations before the server listens and stops the boot when one
fails, so the mirror never had work to do, and DDL on a request path
needs the table's owner: a connection that may only read and write rows
fails every login on it. Drop the module, its call sites and the mocks
that stubbed it out.
2026-11-03 20:32:35 +01:00

150 lines
4.5 KiB
TypeScript

/**
* `GET /api/admin/support-report` against real Postgres.
*
* - admin only, through the real `apiHandler` and `requireAdmin`;
* - the raw reads (server version, time zone, size, the migrations table,
* the queue) and the group-bys run against the real schema;
* - accounts with distinctive usernames, emails and names appear only as
* "Account n", and their ids never.
*/
import { NextRequest } from "next/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
process.env.ENCRYPTION_KEY ??=
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
import { cookieJar, headerJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables } from "./setup";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
return {
headers: vi.fn(async () => ({
get: (name: string) => headerJar.get(name.toLowerCase()) ?? null,
})),
cookies: vi.fn(async () => ({
get: (name: string) => {
const value = cookieJar.get(name);
return value ? { name, value } : undefined;
},
set: (name: string, value: string) => {
cookieJar.set(name, value);
},
delete: (name: string) => {
cookieJar.delete(name);
},
})),
};
});
import { GET } from "@/app/api/admin/support-report/route";
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
headerJar.clear();
});
async function signIn(userId: string) {
const session = await getPrismaClient().session.create({
data: { userId, expiresAt: new Date(Date.now() + 60_000) },
});
cookieJar.set("healthlog_session", session.id);
}
function call() {
const route = GET as unknown as (request: NextRequest) => Promise<Response>;
return route(new NextRequest("http://localhost/api/admin/support-report"));
}
describe("GET /api/admin/support-report (integration)", () => {
it("refuses a signed-in non-admin", async () => {
const prisma = getPrismaClient();
const user = await prisma.user.create({
data: { username: "plainmember", email: "plain.member@example.test" },
});
await signIn(user.id);
const res = await call();
expect(res.status).toBe(403);
});
it("answers a redacted report built from the real schema", async () => {
const prisma = getPrismaClient();
const admin = await prisma.user.create({
data: {
username: "operatorwithaname",
email: "operator.distinct@example.test",
fullName: "Ottilie Operatorson",
role: "ADMIN",
},
});
const member = await prisma.user.create({
data: {
username: "memberwithaname",
email: "member.distinct@example.test",
fullName: "Mathilda Memberson",
},
});
await prisma.providerHealth.create({
data: {
userId: member.id,
providerType: "LOCAL",
lastResult: "auth_failed",
lastStatus: 401,
consecutiveFailures: 3,
lastFailureAt: new Date(),
nextRetryAt: new Date(Date.now() + 3_600_000),
},
});
await prisma.integrationWatch.create({
data: {
userId: member.id,
integration: "nightscout",
incidentReason: "unexpected_silence",
incidentOpenedAt: new Date(),
},
});
await prisma.pushAttempt.create({
data: {
userId: member.id,
channel: "NTFY",
eventType: "MEDICATION_REMINDER",
result: "error",
reason: "http_500",
},
});
await signIn(admin.id);
const res = await call();
expect(res.status).toBe(200);
const body = await res.json();
const text: string = body.data.text;
expect(text).toMatch(/- PostgreSQL: \d+/);
expect(text).toMatch(/- Server time zone: \S+/);
expect(text).toMatch(/- Size: \d+ MB/);
expect(text).toMatch(/- Last migration: \d{4}_\w+ \(/);
expect(text).toContain("- Unfinished migrations: 0");
expect(text).toContain(
"- LOCAL: ok 0, failing 0, credential refused 1, paused 1",
);
expect(text).toContain("nightscout: unexpected_silence since");
expect(text).toContain("Account 2");
expect(text).toContain("- NTFY: ok 0, failed 1, skipped 0; http_500 1");
expect(text).toContain("- Accounts: 2");
for (const value of [
admin.id,
member.id,
admin.username,
member.username,
admin.email!,
member.email!,
"Ottilie",
"Mathilda",
]) {
expect(text.toLowerCase(), value).not.toContain(value.toLowerCase());
}
});
});