import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import { Route, Routes } from 'react-router-dom';
import MockAdapter from 'axios-mock-adapter';
import { apiClient } from '@/api/client';
import { SimulationFormPage } from '@/pages/SimulationFormPage';
import { renderWithProviders } from './utils';
import type { Simulation } from '@/api/types';
const BASE_SIM: Simulation = {
id: 7,
engagement_id: 42,
name: 'Recon test',
techniques: [],
tactics: [],
description: 'Some description',
commands: 'whoami\nipconfig',
prerequisites: null,
executed_at: null,
execution_result: null,
log_source: null,
logs: null,
soc_comment: null,
incident_number: null,
status: 'pending',
created_at: '2026-05-26T08:00:00',
updated_at: null,
created_by: { id: 1, username: 'alice' },
};
let mockRole: 'admin' | 'redteam' | 'soc' = 'redteam';
vi.mock('@/hooks/useAuth', () => ({
useAuth: () => ({
user: { id: 1, username: 'alice', role: mockRole, created_at: '2026-01-01' },
status: 'authenticated',
login: vi.fn(),
logout: vi.fn(),
isAdmin: mockRole === 'admin',
isRedteam: mockRole === 'redteam',
isSoc: mockRole === 'soc',
canEditEngagements: mockRole === 'admin' || mockRole === 'redteam',
}),
}));
// Wrap the page in a Route so useParams gets eid and sid
function EditPage() {
return (
} />
);
}
function NewPage() {
return (
} />
);
}
describe('SimulationFormPage — redteam mode (edit existing)', () => {
let mock: MockAdapter;
beforeEach(() => {
mockRole = 'redteam';
mock = new MockAdapter(apiClient);
mock.onGet('/simulations/7').reply(200, BASE_SIM);
});
afterEach(() => {
mock.restore();
});
it('renders loading state initially', () => {
mock.onGet('/simulations/7').reply(() => new Promise(() => {}));
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
expect(screen.getByTestId('loading-state')).toBeInTheDocument();
});
it('all Red Team fields are enabled for redteam', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/^Name/i)).not.toBeDisabled();
});
expect(screen.getByLabelText(/Description/i)).not.toBeDisabled();
expect(screen.getByLabelText(/Commands/i)).not.toBeDisabled();
expect(screen.getByLabelText(/Executed at/i)).not.toBeDisabled();
});
it('shows "Mark for review" button when status is pending', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByRole('button', { name: /Mark for review/i })).toBeInTheDocument();
});
});
it('does not show "Close" when status is pending', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => screen.getByRole('button', { name: /Mark for review/i }));
expect(screen.queryByRole('button', { name: /^Close$/i })).toBeNull();
});
it('shows "Mark for review" for in_progress status', async () => {
mock.onGet('/simulations/7').reply(200, { ...BASE_SIM, status: 'in_progress' });
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByRole('button', { name: /Mark for review/i })).toBeInTheDocument();
});
});
it('shows "Close" button when status is review_required', async () => {
mock.onGet('/simulations/7').reply(200, { ...BASE_SIM, status: 'review_required' });
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByRole('button', { name: /^Close$/i })).toBeInTheDocument();
});
});
it('shows "Delete" button for redteam', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByRole('button', { name: /^Delete$/i })).toBeInTheDocument();
});
});
});
describe('SimulationFormPage — SOC role + pending (blocked)', () => {
let mock: MockAdapter;
beforeEach(() => {
mockRole = 'soc';
mock = new MockAdapter(apiClient);
mock.onGet('/simulations/7').reply(200, BASE_SIM);
});
afterEach(() => {
mock.restore();
});
it('shows the SOC blocked banner', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByTestId('soc-blocked-banner')).toBeInTheDocument();
});
});
it('SOC inputs are disabled when status is pending', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/Log source/i)).toBeDisabled();
});
expect(screen.getByLabelText(/Incident number/i)).toBeDisabled();
});
it('Red Team inputs are disabled for SOC', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/^Name/i)).toBeDisabled();
});
expect(screen.getByLabelText(/Description/i)).toBeDisabled();
});
});
describe('SimulationFormPage — SOC role + review_required (can edit SOC fields)', () => {
let mock: MockAdapter;
beforeEach(() => {
mockRole = 'soc';
mock = new MockAdapter(apiClient);
mock.onGet('/simulations/7').reply(200, { ...BASE_SIM, status: 'review_required' });
});
afterEach(() => {
mock.restore();
});
it('SOC inputs are enabled when status is review_required', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/Log source/i)).not.toBeDisabled();
});
expect(screen.getByLabelText(/Incident number/i)).not.toBeDisabled();
});
it('Red Team inputs remain disabled for SOC even when review_required', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/^Name/i)).toBeDisabled();
});
});
it('does not show the blocked banner when status is review_required', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByLabelText(/Log source/i)).not.toBeDisabled();
});
expect(screen.queryByTestId('soc-blocked-banner')).toBeNull();
});
it('shows "Close" for SOC when review_required', async () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/7/edit'] },
});
await waitFor(() => {
expect(screen.getByRole('button', { name: /^Close$/i })).toBeInTheDocument();
});
});
});
describe('SimulationFormPage — new simulation', () => {
let mock: MockAdapter;
beforeEach(() => {
mockRole = 'redteam';
mock = new MockAdapter(apiClient);
});
afterEach(() => {
mock.restore();
});
it('renders the new simulation form with name field', () => {
renderWithProviders(, {
routerProps: { initialEntries: ['/engagements/42/simulations/new'] },
});
expect(screen.getByLabelText(/^Name/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Create simulation/i })).toBeInTheDocument();
});
});