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 { ExecuteViaC2Modal } from '@/components/ExecuteViaC2Modal'; import { renderWithProviders } from '../utils'; vi.mock('@/hooks/useAuth', () => ({ useAuth: () => ({ user: { id: 1, username: 'alice', role: 'redteam', created_at: '2026-01-01' }, status: 'authenticated', login: vi.fn(), logout: vi.fn(), isAdmin: false, isRedteam: true, isSoc: false, canEditEngagements: true, }), })); const CALLBACKS = [ { display_id: 1, active: true, host: 'WIN-TARGET', user: 'administrator', domain: 'lab.local', last_checkin: '2026-06-10T10:00:00', }, { display_id: 2, active: false, host: 'WIN-DC01', user: 'SYSTEM', domain: 'lab.local', last_checkin: '2026-06-10T09:00:00', }, ]; let mock: MockAdapter; beforeEach(() => { mock = new MockAdapter(apiClient); mock.onGet('/engagements/42/c2/callbacks').reply(200, { callbacks: CALLBACKS }); }); afterEach(() => { mock.restore(); vi.clearAllMocks(); }); function renderModal(initialCommands = 'whoami\nipconfig') { const onClose = vi.fn(); renderWithProviders( , ); return { onClose }; } describe('ExecuteViaC2Modal', () => { it('renders modal with title and callback table', async () => { renderModal(); expect(screen.getByTestId('c2-modal')).toBeInTheDocument(); expect(screen.getByText('Execute via C2')).toBeInTheDocument(); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); }); it('renders callback rows with mono data', async () => { renderModal(); await waitFor(() => { expect(screen.getByText('WIN-TARGET')).toBeInTheDocument(); expect(screen.getByText('WIN-DC01')).toBeInTheDocument(); expect(screen.getByText('administrator')).toBeInTheDocument(); }); }); it('Launch button is disabled before selecting a callback', async () => { renderModal(); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); expect(screen.getByTestId('c2-launch-btn')).toBeDisabled(); }); it('Launch button is disabled when commands are empty', async () => { renderModal(''); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); fireEvent.click(screen.getAllByTestId('c2-callback-row')[0]); expect(screen.getByTestId('c2-launch-btn')).toBeDisabled(); }); it('Launch button enabled after selecting row and having commands', async () => { renderModal('whoami'); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); fireEvent.click(screen.getAllByTestId('c2-callback-row')[0]); expect(screen.getByTestId('c2-launch-btn')).not.toBeDisabled(); }); it('calls executeC2 with correct body and closes modal on success', async () => { mock.onPost('/simulations/7/c2/execute').reply(200, { tasks: [ { id: 1, mythic_task_display_id: 10, command: 'whoami', status: 'submitted', completed: false }, { id: 2, mythic_task_display_id: 11, command: 'ipconfig', status: 'submitted', completed: false }, ], }); const { onClose } = renderModal('whoami\nipconfig'); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); fireEvent.click(screen.getAllByTestId('c2-callback-row')[0]); fireEvent.click(screen.getByTestId('c2-launch-btn')); await waitFor(() => { expect(onClose).toHaveBeenCalled(); }); const req = mock.history['post'][0]; const body = JSON.parse(req.data as string); expect(body.callback_display_id).toBe(1); expect(body.commands).toEqual(['whoami', 'ipconfig']); }); it('shows inline error and keeps modal open on executeC2 failure', async () => { mock.onPost('/simulations/7/c2/execute').reply(500, { error: 'Mythic unreachable' }); const { onClose } = renderModal('whoami'); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); fireEvent.click(screen.getAllByTestId('c2-callback-row')[0]); fireEvent.click(screen.getByTestId('c2-launch-btn')); await waitFor(() => { expect(screen.getByText('Mythic unreachable')).toBeInTheDocument(); }); expect(onClose).not.toHaveBeenCalled(); }); it('Cancel button calls onClose', async () => { const { onClose } = renderModal(); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); fireEvent.click(screen.getByRole('button', { name: /cancel/i })); expect(onClose).toHaveBeenCalled(); }); it('prefills commands textarea from initialCommands', async () => { renderModal('net user\nwhoami /all'); const textarea = screen.getByTestId('c2-commands-textarea') as HTMLTextAreaElement; expect(textarea.value).toBe('net user\nwhoami /all'); }); it('Enter key on callback row selects it (a11y)', async () => { renderModal('whoami'); await waitFor(() => { expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2); }); const firstRow = screen.getAllByTestId('c2-callback-row')[0]; fireEvent.keyDown(firstRow, { key: 'Enter' }); // Row is now selected → Launch button should be enabled expect(screen.getByTestId('c2-launch-btn')).not.toBeDisabled(); }); });