feat(frontend): c2 tasks panel + history import (sprint 8 phase 2)
- Add getC2Tasks / listCallbackHistory / importC2 API functions + types - useC2Tasks with 2500ms polling (stops when all tasks completed) - useC2CallbackHistory, useImportC2 hooks - C2TaskStatusBadge, C2TasksPanel (expandable output rows, polling indicator) - C2CallbackPicker extracted as shared component (reused in both modals) - ImportC2HistoryModal: 2-step callback picker → paginated history table - SimulationFormPage: RT card + tasks panel share left grid column; Import C2 history button - 37 new tests (api/c2, C2TasksPanel, ImportC2HistoryModal, SimulationFormPage panel visibility) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -332,3 +332,104 @@ describe('SimulationFormPage — Execute via C2 button visibility', () => {
|
||||
expect(screen.queryByTestId('c2-execute-btn')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SimulationFormPage — C2 tasks panel visibility', () => {
|
||||
let mock: MockAdapter;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRole = 'redteam';
|
||||
mock = new MockAdapter(apiClient);
|
||||
mock.onGet('/simulations/7').reply(200, BASE_SIM);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('shows C2 tasks panel when c2 config exists (even with no tasks)', async () => {
|
||||
mock.onGet('/engagements/42/c2-config').reply(200, {
|
||||
has_token: true,
|
||||
url: 'https://mythic.lab:7443',
|
||||
verify_tls: true,
|
||||
});
|
||||
mock.onGet('/simulations/7/c2/tasks').reply(200, { tasks: [] });
|
||||
renderWithProviders(<EditPage />, {
|
||||
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('c2-tasks-panel')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('hides C2 tasks panel when no c2 config and no tasks', async () => {
|
||||
mock.onGet('/engagements/42/c2-config').reply(404);
|
||||
mock.onGet('/simulations/7/c2/tasks').reply(200, { tasks: [] });
|
||||
renderWithProviders(<EditPage />, {
|
||||
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
|
||||
});
|
||||
// Wait for page data to load then confirm no panel
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/^Name/i)).not.toBeDisabled();
|
||||
});
|
||||
expect(screen.queryByTestId('c2-tasks-panel')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows C2 tasks panel when tasks exist even without c2 config', async () => {
|
||||
mock.onGet('/engagements/42/c2-config').reply(404);
|
||||
mock.onGet('/simulations/7/c2/tasks').reply(200, {
|
||||
tasks: [
|
||||
{
|
||||
id: 1,
|
||||
mythic_task_display_id: 10,
|
||||
callback_display_id: 1,
|
||||
command: 'whoami',
|
||||
params: null,
|
||||
status: 'completed',
|
||||
completed: true,
|
||||
output: 'SYSTEM',
|
||||
mapping_applied: false,
|
||||
created_at: '2026-06-10T10:00:00',
|
||||
completed_at: '2026-06-10T10:00:05',
|
||||
},
|
||||
],
|
||||
});
|
||||
renderWithProviders(<EditPage />, {
|
||||
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('c2-tasks-panel')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('SOC role never sees C2 tasks panel', async () => {
|
||||
mockRole = 'soc';
|
||||
mock.onGet('/engagements/42/c2-config').reply(200, {
|
||||
has_token: true,
|
||||
url: 'https://mythic.lab:7443',
|
||||
verify_tls: true,
|
||||
});
|
||||
mock.onGet('/simulations/7/c2/tasks').reply(200, { tasks: [] });
|
||||
renderWithProviders(<EditPage />, {
|
||||
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('soc-blocked-banner')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByTestId('c2-tasks-panel')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows Import C2 history button when c2 config exists', async () => {
|
||||
mock.onGet('/engagements/42/c2-config').reply(200, {
|
||||
has_token: true,
|
||||
url: 'https://mythic.lab:7443',
|
||||
verify_tls: true,
|
||||
});
|
||||
mock.onGet('/simulations/7/c2/tasks').reply(200, { tasks: [] });
|
||||
renderWithProviders(<EditPage />, {
|
||||
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('c2-import-trigger-btn')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user