mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(admin): fill the redacted support report with its redaction guard
This commit is contained in:
1 parent
1af0de5e87
commit
b873ce8bc8
8 files changed
+1801
-5
No files matched your search
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* `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);
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/lib/db-compat", () => ({
|
||||
ensureDbCompatibility: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
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() {
|
||||
return GET(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());
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user