mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
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
142 lines
4.6 KiB
TypeScript
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);
|
|
});
|
|
});
|