Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/routes/admin/analytics/settings.test.ts
T

131 lines
5.3 KiB
TypeScript

// @vitest-environment node
import { readFileSync } from "node:fs";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ActionFunctionArgs } from "react-router";
vi.mock("@toolctrl/db/analytics.server", () => ({
deleteAllAnalyticsData: vi.fn(),
getOrCreateAnalyticsSettings: vi.fn(),
removeAnalyticsIgnoredPage: vi.fn(),
updateAnalyticsSettingsPreferences: vi.fn(),
}));
vi.mock("@toolctrl/db/impersonation-audit.server", async () => {
const actual = await vi.importActual<typeof import("@toolctrl/db/impersonation-audit.server")>("@toolctrl/db/impersonation-audit.server");
return {
...actual,
createImpersonationAudit: vi.fn(),
};
});
vi.mock("@toolctrl/db/audit-events.server", () => ({
createAuditEvent: vi.fn(),
}));
vi.mock("~/locale/i18next.server", () => ({
getTranslations: vi.fn(async () => ({ t: (key: string) => key })),
}));
vi.mock("~/utils/helpers/.server/PermissionsService", () => ({
verifyUserHasPermission: vi.fn(),
}));
vi.mock("~/utils/session.server", () => ({
getUserInfo: vi.fn(),
validateCSRFToken: vi.fn(),
}));
import { deleteAllAnalyticsData } from "@toolctrl/db/analytics.server";
import { createImpersonationAudit, IMPERSONATED_DESTRUCTIVE_ACTION_CONFIRMATION } from "@toolctrl/db/impersonation-audit.server";
import { verifyUserHasPermission } from "~/utils/helpers/.server/PermissionsService";
import { getUserInfo } from "~/utils/session.server";
import { action } from "./settings";
function deleteAllRequest(body: Record<string, string> = {}) {
return new Request("https://admin.example.test/analytics/settings?tab=danger", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ action: "delete-all", ...body }),
});
}
async function callDeleteAll(body: Record<string, string> = {}) {
return action({
request: deleteAllRequest(body),
params: {},
context: {},
unstable_pattern: "admin/analytics/settings",
} as ActionFunctionArgs) as Promise<Response>;
}
describe("admin analytics settings route DB boundary", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(verifyUserHasPermission).mockResolvedValue(undefined as never);
vi.mocked(getUserInfo).mockResolvedValue({ userId: "user-1", lightOrDarkMode: "light", lng: "en", cookies: [] } as never);
});
it("uses package-owned analytics settings helpers", () => {
expect(deleteAllAnalyticsData).toBeDefined();
});
it("keeps visible delete confirmation text behind locale keys", () => {
const source = readFileSync(path.resolve(process.cwd(), "apps/web/app/routes/admin/analytics/settings.tsx"), "utf8");
const en = JSON.parse(readFileSync(path.resolve(process.cwd(), "apps/web/public/locales/en/translations.json"), "utf8"));
const de = JSON.parse(readFileSync(path.resolve(process.cwd(), "apps/web/public/locales/de/translations.json"), "utf8"));
expect(source).toContain("analytics.danger.confirmText");
expect(source).not.toContain('"delete all analytics data"');
expect(source).not.toContain("`delete all analytics data`");
expect(en.analytics.danger.confirmText).toBe("delete all analytics data");
expect(de.analytics.danger.confirmText).toBe("alle Analytics-Daten löschen");
});
it("deletes analytics data for normal sessions", async () => {
const response = await callDeleteAll();
await expect(response.json()).resolves.toEqual({ deleteSuccess: true });
expect(response.status).toBe(200);
expect(verifyUserHasPermission).toHaveBeenCalledWith(expect.any(Request), "admin.analytics.delete");
expect(deleteAllAnalyticsData).toHaveBeenCalledTimes(1);
expect(createImpersonationAudit).not.toHaveBeenCalled();
});
it("blocks impersonated deletion without the explicit impersonation phrase", async () => {
vi.mocked(getUserInfo).mockResolvedValue({
userId: "target-user-1",
impersonatingFromUserId: "actor-user-1",
lightOrDarkMode: "light",
lng: "en",
cookies: [],
} as never);
const response = await callDeleteAll({ "impersonation-confirm": "delete all analytics data" });
await expect(response.json()).resolves.toEqual({ deleteError: "admin.analytics.settings.errors.confirmImpersonatedDelete" });
expect(response.status).toBe(400);
expect(deleteAllAnalyticsData).not.toHaveBeenCalled();
expect(createImpersonationAudit).toHaveBeenCalledWith({
actorUserId: "actor-user-1",
targetUserId: "target-user-1",
tenantId: null,
action: "destructive_action_blocked",
reason: "analytics.delete-all",
requestPath: "/analytics/settings",
});
});
it("allows impersonated deletion with the explicit impersonation phrase", async () => {
vi.mocked(getUserInfo).mockResolvedValue({
userId: "target-user-1",
impersonatingFromUserId: "actor-user-1",
lightOrDarkMode: "light",
lng: "en",
cookies: [],
} as never);
const response = await callDeleteAll({ "impersonation-confirm": IMPERSONATED_DESTRUCTIVE_ACTION_CONFIRMATION });
await expect(response.json()).resolves.toEqual({ deleteSuccess: true });
expect(response.status).toBe(200);
expect(deleteAllAnalyticsData).toHaveBeenCalledTimes(1);
expect(createImpersonationAudit).not.toHaveBeenCalled();
});
});