import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { WebSocketStatusCard } from '../WebSocketStatusCard'; import * as websocketApi from '../../api/websocket'; // Mock the API functions vi.mock('../../api/websocket'); // Mock date-fns to avoid timezone issues in tests vi.mock('date-fns', () => ({ formatDistanceToNow: vi.fn(() => '5 minutes ago'), })); describe('WebSocketStatusCard', () => { let queryClient: QueryClient; beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); vi.clearAllMocks(); }); const renderComponent = (props = {}) => { return render( ); }; it('should render loading state', () => { vi.mocked(websocketApi.getWebSocketConnections).mockReturnValue( new Promise(() => {}) // Never resolves ); vi.mocked(websocketApi.getWebSocketStats).mockReturnValue( new Promise(() => {}) // Never resolves ); renderComponent(); // Loading state shows skeleton elements expect(screen.getAllByRole('generic').length).toBeGreaterThan(0); }); it('should render with no active connections', async () => { vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: [], count: 0, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 0, logs_connections: 0, cerberus_connections: 0, last_updated: '2024-01-15T10:10:00Z', }); renderComponent(); await waitFor(() => { expect(screen.getByText('WebSocket Connections')).toBeInTheDocument(); }); expect(screen.getByText('0 Active')).toBeInTheDocument(); expect(screen.getByText('No active WebSocket connections')).toBeInTheDocument(); }); it('should render with active connections', async () => { const mockConnections = [ { id: 'conn-1', type: 'logs' as const, connected_at: '2024-01-15T10:00:00Z', last_activity_at: '2024-01-15T10:05:00Z', remote_addr: '192.168.1.1:12345', filters: 'level=error', }, { id: 'conn-2', type: 'cerberus' as const, connected_at: '2024-01-15T10:02:00Z', last_activity_at: '2024-01-15T10:06:00Z', remote_addr: '192.168.1.2:54321', filters: 'source=waf', }, ]; vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: mockConnections, count: 2, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 2, logs_connections: 1, cerberus_connections: 1, oldest_connection: '2024-01-15T10:00:00Z', last_updated: '2024-01-15T10:10:00Z', }); renderComponent(); await waitFor(() => { expect(screen.getByText('WebSocket Connections')).toBeInTheDocument(); }); expect(screen.getByText('2 Active')).toBeInTheDocument(); expect(screen.getByText('General Logs')).toBeInTheDocument(); expect(screen.getByText('Security Logs')).toBeInTheDocument(); // Use getAllByText since we have two "1" values const ones = screen.getAllByText('1'); expect(ones).toHaveLength(2); }); it('should show details when expanded', async () => { const mockConnections = [ { id: 'conn-123', type: 'logs' as const, connected_at: '2024-01-15T10:00:00Z', last_activity_at: '2024-01-15T10:05:00Z', remote_addr: '192.168.1.1:12345', filters: 'level=error', }, ]; vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: mockConnections, count: 1, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 1, logs_connections: 1, cerberus_connections: 0, last_updated: '2024-01-15T10:10:00Z', }); renderComponent({ showDetails: true }); await waitFor(() => { expect(screen.getByText('WebSocket Connections')).toBeInTheDocument(); }); // Check for connection details expect(screen.getByText('Active Connections')).toBeInTheDocument(); expect(screen.getByText(/conn-123/i)).toBeInTheDocument(); expect(screen.getByText('192.168.1.1:12345')).toBeInTheDocument(); expect(screen.getByText('level=error')).toBeInTheDocument(); }); it('should toggle details on button click', async () => { const user = userEvent.setup(); const mockConnections = [ { id: 'conn-1', type: 'logs' as const, connected_at: '2024-01-15T10:00:00Z', last_activity_at: '2024-01-15T10:05:00Z', }, ]; vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: mockConnections, count: 1, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 1, logs_connections: 1, cerberus_connections: 0, last_updated: '2024-01-15T10:10:00Z', }); renderComponent(); await waitFor(() => { expect(screen.getByText('Show Details')).toBeInTheDocument(); }); // Initially hidden expect(screen.queryByText('Active Connections')).not.toBeInTheDocument(); // Click to show await user.click(screen.getByText('Show Details')); await waitFor(() => { expect(screen.getByText('Active Connections')).toBeInTheDocument(); }); // Click to hide await user.click(screen.getByText('Hide Details')); await waitFor(() => { expect(screen.queryByText('Active Connections')).not.toBeInTheDocument(); }); }); it('should handle API errors gracefully', async () => { vi.mocked(websocketApi.getWebSocketConnections).mockRejectedValue( new Error('API Error') ); vi.mocked(websocketApi.getWebSocketStats).mockRejectedValue( new Error('API Error') ); renderComponent(); await waitFor(() => { expect(screen.getByText('Unable to load WebSocket status')).toBeInTheDocument(); }); }); it('should display oldest connection when available', async () => { vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: [], count: 1, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 1, logs_connections: 1, cerberus_connections: 0, oldest_connection: '2024-01-15T09:55:00Z', last_updated: '2024-01-15T10:10:00Z', }); renderComponent(); await waitFor(() => { expect(screen.getByText('Oldest Connection')).toBeInTheDocument(); }); expect(screen.getByText('5 minutes ago')).toBeInTheDocument(); }); it('should apply custom className', async () => { vi.mocked(websocketApi.getWebSocketConnections).mockResolvedValue({ connections: [], count: 0, }); vi.mocked(websocketApi.getWebSocketStats).mockResolvedValue({ total_active: 0, logs_connections: 0, cerberus_connections: 0, last_updated: '2024-01-15T10:10:00Z', }); const { container } = renderComponent({ className: 'custom-class' }); await waitFor(() => { expect(screen.getByText('WebSocket Connections')).toBeInTheDocument(); }); const card = container.querySelector('.custom-class'); expect(card).toBeInTheDocument(); }); });