arbret/frontend/app/audit/page.test.tsx
2025-12-18 23:54:51 +01:00

172 lines
4.2 KiB
TypeScript

import { render, screen, waitFor, cleanup } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import AuditPage from "./page";
// Mock next/navigation
const mockPush = vi.fn();
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush }),
}));
// Default mock values for admin user
let mockUser: { id: number; email: string; roles: string[]; permissions: string[] } | null = {
id: 1,
email: "admin@example.com",
roles: ["admin"],
permissions: ["view_audit"],
};
let mockIsLoading = false;
const mockLogout = vi.fn();
const mockHasPermission = vi.fn((permission: string) =>
mockUser?.permissions.includes(permission) ?? false
);
vi.mock("../auth-context", () => ({
useAuth: () => ({
user: mockUser,
isLoading: mockIsLoading,
logout: mockLogout,
hasPermission: mockHasPermission,
}),
Permission: {
VIEW_COUNTER: "view_counter",
INCREMENT_COUNTER: "increment_counter",
USE_SUM: "use_sum",
VIEW_AUDIT: "view_audit",
},
}));
// Mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;
beforeEach(() => {
vi.clearAllMocks();
mockUser = {
id: 1,
email: "admin@example.com",
roles: ["admin"],
permissions: ["view_audit"],
};
mockIsLoading = false;
mockHasPermission.mockImplementation((permission: string) =>
mockUser?.permissions.includes(permission) ?? false
);
// Default: successful empty response
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ records: [], total: 0, page: 1, per_page: 10, total_pages: 1 }),
});
});
afterEach(() => {
cleanup();
vi.restoreAllMocks();
});
describe("AuditPage", () => {
it("shows loading state", () => {
mockIsLoading = true;
render(<AuditPage />);
expect(screen.getByText("Loading...")).toBeTruthy();
});
it("redirects to login when not authenticated", async () => {
mockUser = null;
render(<AuditPage />);
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith("/login");
});
});
it("redirects to home when user lacks audit permission", async () => {
mockUser = {
id: 1,
email: "user@example.com",
roles: ["regular"],
permissions: ["view_counter"],
};
mockHasPermission.mockReturnValue(false);
render(<AuditPage />);
await waitFor(() => {
expect(mockPush).toHaveBeenCalledWith("/");
});
});
it("displays error message when API fetch fails", async () => {
mockFetch.mockRejectedValue(new Error("Network error"));
render(<AuditPage />);
await waitFor(() => {
// Both tables should show errors since both calls fail
const errors = screen.getAllByText("Network error");
expect(errors.length).toBeGreaterThan(0);
});
});
it("displays error when API returns non-ok response", async () => {
mockFetch.mockResolvedValue({
ok: false,
status: 500,
json: () => Promise.resolve({ detail: "Internal server error" }),
});
render(<AuditPage />);
await waitFor(() => {
expect(screen.getByText("Failed to load counter records")).toBeTruthy();
});
});
it("displays records when fetch succeeds", async () => {
const counterResponse = {
records: [
{
id: 1,
user_email: "recorduser@example.com",
value_before: 0,
value_after: 1,
created_at: "2024-01-01T00:00:00Z",
},
],
total: 1,
page: 1,
per_page: 10,
total_pages: 1,
};
const sumResponse = {
records: [],
total: 0,
page: 1,
per_page: 10,
total_pages: 1,
};
mockFetch
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(counterResponse),
})
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(sumResponse),
});
render(<AuditPage />);
await waitFor(() => {
expect(screen.getByText("recorduser@example.com")).toBeTruthy();
});
});
it("shows table headers", async () => {
render(<AuditPage />);
await waitFor(() => {
// Check for counter table headers
expect(screen.getByText("Counter Activity")).toBeTruthy();
expect(screen.getByText("Sum Activity")).toBeTruthy();
});
});
});