Files
HealthLog/tests/integration/admin-data-wipe.test.ts
T
Marc-André Bombeck de33f1ad4c chore: remove the bug-report integration
Drop the in-app bug-report surface end to end: the user submission page,
the feedback intake and admin inbox, the optional GitHub-issue
escalation, the operator config, and the dedicated achievement badges.

- delete the /bugreport page and the bugreport / feedback / admin-feedback API routes
- drop the Feedback model, its enums, and the AppSettings GitHub-issue and enable columns (migration 0205)
- remove the admin Feedback section, the Bug Report integration card, and the system-status row
- drop the bug-report nav entry and the app-settings provider that only carried its flag
- remove the bug-hunter achievement badges and their metric
- prune the related i18n keys across every locale and the encrypted-column and key-rotation registries
2026-06-27 20:37:35 +02:00

142 lines
4.6 KiB
TypeScript

/**
* Integration regression guard for `DELETE /api/admin/data`.
*
* The admin "wipe all data" path nulls personal columns on `User` and
* deletes a curated list of personal-data tables in one transaction.
* v1.4.6 left three notification-channel-style tables out of that
* list, leaving encrypted Telegram bot tokens (NotificationChannel.config)
* and Web Push endpoints behind. This test is the regression guard
* that the wipe now extends to:
*
* - notification_channels (Telegram/ntfy/web-push channel config)
* - push_subscriptions (raw browser push endpoints)
* - telegram_scheduled_deletions (queued message-delete tickets)
*
* AuditLog rows are intentionally PRESERVED — a wipe must not erase the
* audit trail of the operation itself.
*/
import { beforeEach, describe, expect, it, vi } from "vitest";
import { cookieJar, headerJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables } from "./setup";
// next/headers cookie + ip headers stub for getSession() inside
// requireAdmin(). The admin route reads x-forwarded-for too; we stub
// both to keep the handler reachable. Maps live in
// `mock-next-headers.ts` — see that file for the rationale (suite
// runs with vitest `isolate: false`, so per-file Maps would leak).
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),
}));
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
cookieJar.clear();
headerJar.clear();
});
describe("DELETE /api/admin/data wipe scope", () => {
it("clears notification channels, push subscriptions, telegram deletions; preserves AuditLog", async () => {
const prisma = getPrismaClient();
// ── arrange ──
const admin = await prisma.user.create({
data: {
username: "wipe-admin",
email: "wipe-admin@example.test",
role: "ADMIN",
},
});
const session = await prisma.session.create({
data: {
userId: admin.id,
expiresAt: new Date(Date.now() + 60_000),
},
});
cookieJar.set("healthlog_session", session.id);
// Seed the channels & adjacent rows the wipe must clear.
await prisma.notificationChannel.create({
data: {
userId: admin.id,
type: "TELEGRAM",
enabled: true,
// Pretend-encrypted blob — content is irrelevant, presence is.
config: "encrypted:test",
},
});
await prisma.pushSubscription.create({
data: {
userId: admin.id,
endpoint: "https://push.example/sub-" + admin.id,
p256dh: "p256dh-enc",
auth: "auth-enc",
},
});
await prisma.telegramScheduledDeletion.create({
data: {
userId: admin.id,
chatId: "1234",
messageId: 42,
deleteAfter: new Date(Date.now() + 60_000),
},
});
// ── act: invoke the route handler exactly as Next.js would ──
const { DELETE } = await import("@/app/api/admin/data/route");
const req = new Request("http://localhost/api/admin/data", {
method: "DELETE",
headers: {
"content-type": "application/json",
"x-forwarded-for": "127.0.0.1",
},
body: JSON.stringify({ confirm: "DELETE ALL" }),
});
const res = await DELETE(req as unknown as Parameters<typeof DELETE>[0]);
expect(res.status).toBe(200);
// ── assert: notification-related tables are empty ──
expect(
await prisma.notificationChannel.count({ where: { userId: admin.id } }),
).toBe(0);
expect(
await prisma.pushSubscription.count({ where: { userId: admin.id } }),
).toBe(0);
expect(
await prisma.telegramScheduledDeletion.count({
where: { userId: admin.id },
}),
).toBe(0);
// ── assert: AuditLog rows survive ──
// The wipe writes admin.data.clear.start AND admin.data.clear audit
// entries. They must outlive the operation (audit log is immune).
const auditCount = await prisma.auditLog.count({
where: { userId: admin.id },
});
expect(auditCount).toBeGreaterThanOrEqual(2);
});
});