import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import type { ReactNode } from 'react'; // Bundle 2 Phase 8 — GroupMappingsPage tests. Pins: // - 403 ErrorState when caller lacks auth.oidc.list. // - Empty mapping list renders the fail-closed-warning empty state. // - Mapping list renders one row per mapping. // - Add form HIDDEN without auth.oidc.edit. // - Add form SHOWN with auth.oidc.edit + submission calls addGroupMapping. vi.mock('../../api/client', () => ({ listGroupMappings: vi.fn(), addGroupMapping: vi.fn(), removeGroupMapping: vi.fn(), authListRoles: vi.fn(), authMe: vi.fn(), })); import GroupMappingsPage from './GroupMappingsPage'; import * as client from '../../api/client'; function renderRoute(ui: ReactNode, path = '/auth/oidc/providers/op-okta/mappings') { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, }); return render( , ); } beforeEach(() => { vi.clearAllMocks(); cleanup(); }); const sampleRoles = [ { id: 'r-admin', tenant_id: 't-default', name: 'admin', description: 'Full access' }, { id: 'r-viewer', tenant_id: 't-default', name: 'viewer', description: 'Read-only' }, ]; const sampleMappings = [ { id: 'gm-1', provider_id: 'op-okta', group_name: 'engineers', role_id: 'r-admin', tenant_id: 't-default', created_at: '2026-05-10T00:00:00Z', }, ]; describe('GroupMappingsPage', () => { it('renders ErrorState when caller lacks auth.oidc.list', async () => { vi.mocked(client.authMe).mockResolvedValue({ actor_id: 'u-x', actor_type: 'User', tenant_id: 't-default', admin: false, roles: [], effective_permissions: [], }); renderRoute(); await waitFor(() => { expect(screen.queryByText(/auth\.oidc\.list/)).toBeTruthy(); }); }); it('renders empty fail-closed warning when no mappings configured', async () => { vi.mocked(client.listGroupMappings).mockResolvedValue({ mappings: [] }); vi.mocked(client.authListRoles).mockResolvedValue(sampleRoles); vi.mocked(client.authMe).mockResolvedValue({ actor_id: 'u-admin', actor_type: 'User', tenant_id: 't-default', admin: true, roles: ['r-admin'], effective_permissions: [{ permission: 'auth.oidc.list', scope_type: 'global' }], }); renderRoute(); await waitFor(() => { expect(screen.getByTestId('group-mappings-empty')).toBeTruthy(); }); }); it('renders mapping rows from listGroupMappings', async () => { vi.mocked(client.listGroupMappings).mockResolvedValue({ mappings: sampleMappings }); vi.mocked(client.authListRoles).mockResolvedValue(sampleRoles); vi.mocked(client.authMe).mockResolvedValue({ actor_id: 'u-admin', actor_type: 'User', tenant_id: 't-default', admin: true, roles: ['r-admin'], effective_permissions: [ { permission: 'auth.oidc.list', scope_type: 'global' }, { permission: 'auth.oidc.edit', scope_type: 'global' }, ], }); renderRoute(); await waitFor(() => { expect(screen.getByTestId('group-mapping-row-gm-1')).toBeTruthy(); }); expect(screen.getByText('engineers')).toBeTruthy(); expect(screen.getByText('r-admin')).toBeTruthy(); expect(screen.getByTestId('group-mapping-remove-gm-1')).toBeTruthy(); }); it('hides the add form when caller lacks auth.oidc.edit', async () => { vi.mocked(client.listGroupMappings).mockResolvedValue({ mappings: sampleMappings }); vi.mocked(client.authListRoles).mockResolvedValue(sampleRoles); vi.mocked(client.authMe).mockResolvedValue({ actor_id: 'u-viewer', actor_type: 'User', tenant_id: 't-default', admin: false, roles: ['r-viewer'], effective_permissions: [{ permission: 'auth.oidc.list', scope_type: 'global' }], }); renderRoute(); await waitFor(() => { expect(screen.getByTestId('group-mapping-row-gm-1')).toBeTruthy(); }); expect(screen.queryByTestId('group-mappings-add-form')).toBeNull(); // Remove button is also hidden in row when caller lacks edit. expect(screen.queryByTestId('group-mapping-remove-gm-1')).toBeNull(); }); it('submitting the add form calls addGroupMapping', async () => { vi.mocked(client.listGroupMappings).mockResolvedValue({ mappings: [] }); vi.mocked(client.authListRoles).mockResolvedValue(sampleRoles); vi.mocked(client.addGroupMapping).mockResolvedValue(sampleMappings[0]); vi.mocked(client.authMe).mockResolvedValue({ actor_id: 'u-admin', actor_type: 'User', tenant_id: 't-default', admin: true, roles: ['r-admin'], effective_permissions: [ { permission: 'auth.oidc.list', scope_type: 'global' }, { permission: 'auth.oidc.edit', scope_type: 'global' }, ], }); renderRoute(); await waitFor(() => { expect(screen.getByTestId('group-mappings-add-form')).toBeTruthy(); }); fireEvent.change(screen.getByTestId('group-mappings-group-name-input'), { target: { value: 'engineers' }, }); fireEvent.change(screen.getByTestId('group-mappings-role-select'), { target: { value: 'r-admin' }, }); fireEvent.click(screen.getByTestId('group-mappings-add-button')); await waitFor(() => { expect(client.addGroupMapping).toHaveBeenCalledWith('op-okta', 'engineers', 'r-admin'); }); }); });