117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
import "@testing-library/jest-dom";
|
|
import { render, waitFor } from "@testing-library/react";
|
|
import { createMemoryRouter, RouterProvider } from "react-router";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { FeedbackWithDetails } from "../db/feedback.db.server";
|
|
import { feedbackRedactionMarkers } from "../services/FeedbackRedaction.server";
|
|
import FeedbackTable from "./FeedbackTable";
|
|
|
|
vi.mock("react-i18next", () => ({
|
|
useTranslation: () => ({ t: (key: string) => key, i18n: { language: "de", exists: () => true } }),
|
|
}));
|
|
|
|
vi.mock("~/components/ui/buttons/ButtonSecondary", () => ({
|
|
default: ({ children, isLoading, destructive, ...props }: React.ComponentProps<"button"> & { isLoading?: boolean; destructive?: boolean }) => (
|
|
<button type="button" aria-busy={isLoading} data-destructive={destructive ? "true" : undefined} {...props}>
|
|
{children}
|
|
</button>
|
|
),
|
|
}));
|
|
|
|
vi.mock("~/components/ui/icons/RefreshIcon", () => ({
|
|
default: ({ className }: { className?: string }) => <span className={className}>refresh</span>,
|
|
}));
|
|
|
|
vi.mock("~/components/ui/input/InputFilters", () => ({
|
|
default: () => <div data-testid="feedback-filters" />,
|
|
}));
|
|
|
|
vi.mock("~/components/ui/input/InputSearchWithURL", () => ({
|
|
default: () => <input aria-label="search" />,
|
|
}));
|
|
|
|
vi.mock("~/components/ui/links/FilterableValueLink", () => ({
|
|
FilterableValueLink: ({ value }: { value?: string | null }) => <span>{value}</span>,
|
|
}));
|
|
|
|
const rawEmail = "legacy.user@example.com";
|
|
const rawBearerToken = "raw-admin-token-123";
|
|
const rawHexSecret = "abcdef0123456789abcdef0123456789";
|
|
|
|
const feedbackRow: FeedbackWithDetails = {
|
|
id: "feedback_1",
|
|
createdAt: new Date("2026-01-01T10:00:00.000Z"),
|
|
tenantId: "tenant_1",
|
|
userId: "user_1",
|
|
message: `Login fails for ${rawEmail} with Bearer ${rawBearerToken} and trace ${rawHexSecret}`,
|
|
fromUrl: "/acme?token=raw-url-token",
|
|
tenant: {
|
|
id: "tenant_1",
|
|
name: "Acme",
|
|
slug: "acme",
|
|
icon: null,
|
|
logo: null,
|
|
deactivatedReason: null,
|
|
types: [],
|
|
active: true,
|
|
theme: null,
|
|
themeConfig: null,
|
|
},
|
|
user: {
|
|
id: "user_1",
|
|
email: "admin@example.com",
|
|
firstName: "Ada",
|
|
lastName: "Admin",
|
|
phone: null,
|
|
locale: "en",
|
|
createdAt: new Date("2025-01-01T00:00:00.000Z"),
|
|
},
|
|
};
|
|
|
|
describe("FeedbackTable", () => {
|
|
it("redacts sensitive legacy feedback message values before rendering the admin table", async () => {
|
|
const router = createMemoryRouter([
|
|
{
|
|
path: "/feedback",
|
|
element: (
|
|
<FeedbackTable
|
|
data={{
|
|
items: [feedbackRow],
|
|
filterableProperties: [],
|
|
pagination: { page: 1, pageSize: 10, totalItems: 1, totalPages: 1 },
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
], { initialEntries: ["/feedback"] });
|
|
|
|
const { container } = render(<RouterProvider router={router} />);
|
|
|
|
await waitFor(() => expect(container).toHaveTextContent(feedbackRedactionMarkers.email));
|
|
expect(container).toHaveTextContent(`Bearer ${feedbackRedactionMarkers.token}`);
|
|
expect(container).toHaveTextContent(feedbackRedactionMarkers.secret);
|
|
expect(container).not.toHaveTextContent(rawEmail);
|
|
expect(container).not.toHaveTextContent(rawBearerToken);
|
|
expect(container).not.toHaveTextContent(rawHexSecret);
|
|
});
|
|
|
|
it("keeps help desk dates and counts on locale-aware formatters", () => {
|
|
const repoRoot = process.cwd();
|
|
const source = [
|
|
"apps/web/app/modules/helpDesk/components/FeedbackTable.tsx",
|
|
"apps/web/app/routes/admin/help-desk/index.tsx",
|
|
"apps/web/app/routes/admin/help-desk/tickets.tsx",
|
|
]
|
|
.map((file) => fs.readFileSync(path.join(repoRoot, file), "utf8"))
|
|
.join("\n");
|
|
|
|
expect(source).toContain("useLocaleFormatters");
|
|
expect(source).toContain("formatters.dateTime");
|
|
expect(source).toContain("formatters.number");
|
|
expect(source).not.toContain("DateUtils");
|
|
expect(source).not.toContain("NumberUtils");
|
|
});
|
|
});
|