import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { createMemoryRouter, RouterProvider, type RouteObject } from 'react-router-dom'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; const { apiMock, useSessionMock } = vi.hoisted(() => ({ apiMock: vi.fn(), useSessionMock: vi.fn(), })); vi.mock('@/lib/api', () => ({ api: apiMock, })); vi.mock('@/lib/auth-client', () => ({ useSession: useSessionMock, authClient: {}, })); import { AdminPage } from './admin'; import { AdminGuard } from '@/spa/guards'; const userFixtures = { users: [ { id: 'u-admin', name: 'Ada Admin', email: 'ada@example.test', role: 'admin', banned: false, banReason: null, createdAt: '2026-08-01T00:00:00.000Z', updatedAt: '2026-08-01T00:00:00.000Z', }, { id: 'u-member', name: 'Mel Member', email: 'mel@example.test', role: 'member', banned: true, banReason: 'spam', createdAt: '2026-08-02T00:00:00.000Z', updatedAt: '2026-08-02T00:00:00.000Z', }, ], total: 2, }; let root: Root | null = null; let container: HTMLDivElement; beforeAll(() => { Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true, }); }); afterAll(() => { Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT'); }); afterEach(async () => { await act(async () => { root?.unmount(); }); document.body.replaceChildren(); root = null; apiMock.mockReset(); useSessionMock.mockReset(); }); async function renderAdminRoute(): Promise { const routes: RouteObject[] = [ { element: , children: [{ path: '/admin', element: }], }, { path: '/', element:
home page
}, { path: '/login', element:
login page
}, ]; const router = createMemoryRouter(routes, { initialEntries: ['/admin'] }); container = document.createElement('div'); document.body.append(container); root = createRoot(container); await act(async () => { root?.render(); }); } function sessionWithRole(role: string | undefined): { data: unknown; isPending: boolean } { return { data: { user: { id: 'u-1', name: 'Test', email: 't@example.test', role } }, isPending: false, }; } describe('AdminGuard', () => { it('redirects unauthenticated visitors to /login', async () => { useSessionMock.mockReturnValue({ data: null, isPending: false }); await renderAdminRoute(); expect(container.textContent).toContain('login page'); expect(apiMock).not.toHaveBeenCalled(); }); it('redirects non-admin users to /', async () => { useSessionMock.mockReturnValue(sessionWithRole('member')); await renderAdminRoute(); expect(container.textContent).toContain('home page'); expect(apiMock).not.toHaveBeenCalled(); }); it('renders the admin page for admin users', async () => { useSessionMock.mockReturnValue(sessionWithRole('admin')); apiMock.mockResolvedValueOnce(userFixtures); await renderAdminRoute(); expect(container.textContent).toContain('Admin Panel'); }); }); describe('AdminPage users tab', () => { it('lists users with role and ban status after load', async () => { useSessionMock.mockReturnValue(sessionWithRole('admin')); apiMock.mockResolvedValueOnce(userFixtures); await renderAdminRoute(); expect(apiMock).toHaveBeenCalledWith('/api/admin/users'); expect(container.textContent).toContain('Ada Admin'); expect(container.textContent).toContain('Mel Member'); expect(container.textContent).toContain('Banned'); expect(container.textContent).toContain('2 user(s)'); }); it('shows the load error with a retry control', async () => { useSessionMock.mockReturnValue(sessionWithRole('admin')); apiMock.mockRejectedValueOnce(new Error('gateway unavailable')); await renderAdminRoute(); expect(container.textContent).toContain('gateway unavailable'); apiMock.mockResolvedValueOnce(userFixtures); const retry = [...container.querySelectorAll('button')].find((b) => b.textContent?.includes('Retry'), ); expect(retry).toBeTruthy(); await act(async () => { retry?.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); expect(container.textContent).toContain('Ada Admin'); }); it('posts to the ban endpoint and reloads on Ban', async () => { useSessionMock.mockReturnValue(sessionWithRole('admin')); apiMock.mockResolvedValue(userFixtures); await renderAdminRoute(); const banButton = [...container.querySelectorAll('button')].find( (b) => b.textContent === 'Ban', ); expect(banButton).toBeTruthy(); await act(async () => { banButton?.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); expect(apiMock).toHaveBeenCalledWith('/api/admin/users/u-admin/ban', { method: 'POST' }); }); }); describe('AdminPage health tab', () => { it('loads health status when the tab is opened', async () => { useSessionMock.mockReturnValue(sessionWithRole('admin')); apiMock.mockResolvedValueOnce(userFixtures).mockResolvedValueOnce({ status: 'ok', database: { status: 'ok', latencyMs: 3 }, cache: { status: 'ok', latencyMs: 1 }, agentPool: { activeSessions: 2 }, providers: [{ id: 'ollama', name: 'Ollama', available: true, modelCount: 4 }], checkedAt: '2026-08-26T00:00:00.000Z', }); await renderAdminRoute(); const healthTab = [...container.querySelectorAll('button')].find((b) => b.textContent?.includes('System Health'), ); await act(async () => { healthTab?.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); expect(apiMock).toHaveBeenCalledWith('/api/admin/health'); expect(container.textContent).toContain('Database (PostgreSQL)'); expect(container.textContent).toContain('Active sessions: 2'); expect(container.textContent).toContain('4 models'); }); });