import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { screen, waitFor, fireEvent } from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; import { apiClient } from '@/api/client'; import { C2ConfigCard } from '@/components/C2ConfigCard'; import { renderWithProviders } from '../utils'; vi.mock('@/hooks/useAuth', () => ({ useAuth: () => ({ user: { id: 1, username: 'alice', role: 'admin', created_at: '2026-01-01' }, status: 'authenticated', login: vi.fn(), logout: vi.fn(), isAdmin: true, isRedteam: false, isSoc: false, canEditEngagements: true, }), })); let mock: MockAdapter; beforeEach(() => { mock = new MockAdapter(apiClient); }); afterEach(() => { mock.restore(); vi.clearAllMocks(); }); describe('C2ConfigCard — no config (404)', () => { it('renders the card with empty fields when no config exists', async () => { mock.onGet('/engagements/1/c2-config').reply(404); renderWithProviders(); // Wait for loading to finish — query resolves to null on 404 await waitFor(() => { expect(screen.getByTestId('c2-url-input')).toBeInTheDocument(); }); expect(screen.getByTestId('c2-token-input')).toBeInTheDocument(); expect(screen.getByTestId('c2-verify-tls')).toBeInTheDocument(); expect(screen.getByTestId('c2-save-btn')).toBeInTheDocument(); // Delete button only shown when has_token expect(screen.queryByTestId('c2-delete-btn')).toBeNull(); }); }); describe('C2ConfigCard — with config (has_token=true)', () => { beforeEach(() => { mock.onGet('/engagements/1/c2-config').reply(200, { has_token: true, url: 'https://mythic.lab:7443', verify_tls: true, }); }); it('shows Replace token affordance when has_token=true', async () => { renderWithProviders(); await waitFor(() => { expect(screen.getByText('Replace token')).toBeInTheDocument(); }); // Token input shows placeholder bullets (readOnly) const tokenInput = screen.getByTestId('c2-token-input') as HTMLInputElement; expect(tokenInput.readOnly).toBe(true); expect(tokenInput.placeholder).toBe('••••••••'); }); it('shows Delete configuration button when has_token=true', async () => { renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('c2-delete-btn')).toBeInTheDocument(); }); }); it('clicking Replace token makes input editable', async () => { renderWithProviders(); await waitFor(() => { expect(screen.getByText('Replace token')).toBeInTheDocument(); }); fireEvent.click(screen.getByText('Replace token')); await waitFor(() => { const tokenInput = screen.getByTestId('c2-token-input') as HTMLInputElement; expect(tokenInput.readOnly).toBeFalsy(); }); }); it('Test connection button is enabled when config exists', async () => { renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('c2-test-btn')).not.toBeDisabled(); }); }); it('shows Connected on successful test', async () => { mock.onPost('/engagements/1/c2-config/test').reply(200, { ok: true, error: null }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('c2-test-btn')).not.toBeDisabled(); }); fireEvent.click(screen.getByTestId('c2-test-btn')); await waitFor(() => { expect(screen.getByText('Connected')).toBeInTheDocument(); }); }); it('shows error message on failed test', async () => { mock .onPost('/engagements/1/c2-config/test') .reply(200, { ok: false, error: 'Connection refused' }); renderWithProviders(); await waitFor(() => { expect(screen.getByTestId('c2-test-btn')).not.toBeDisabled(); }); fireEvent.click(screen.getByTestId('c2-test-btn')); await waitFor(() => { expect(screen.getByText('Connection refused')).toBeInTheDocument(); }); }); }); describe('C2ConfigCard — 503 disabled state', () => { it('shows 503 banner and disables all inputs', async () => { mock.onGet('/engagements/1/c2-config').reply(503); renderWithProviders(); await waitFor(() => { expect( screen.getByText(/C2 features are disabled/i), ).toBeInTheDocument(); }); expect(screen.getByTestId('c2-save-btn')).toBeDisabled(); expect(screen.getByTestId('c2-url-input')).toBeDisabled(); expect(screen.getByTestId('c2-token-input')).toBeDisabled(); expect(screen.getByTestId('c2-verify-tls')).toBeDisabled(); }); });