docs: enterprise refactor plan with ralph specs
This commit is contained in:
77
frontend/src/__tests__/AnomalyList.test.tsx
Normal file
77
frontend/src/__tests__/AnomalyList.test.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { AnomalyList } from '../components/AnomalyList';
|
||||
import type { AnomalySummary } from '../types';
|
||||
|
||||
function makeAnomaly(overrides: Partial<AnomalySummary> = {}): AnomalySummary {
|
||||
return {
|
||||
id: 'anom_1',
|
||||
type: 'http_error',
|
||||
severity: 'high',
|
||||
description: 'HTTP 500 on form submit',
|
||||
timestamp: 1000000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function renderList(anomalies: AnomalySummary[]) {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<AnomalyList anomalies={anomalies} title="Test Anomalies" />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
describe('AnomalyList', () => {
|
||||
it('renders title', () => {
|
||||
renderList([]);
|
||||
expect(screen.getByText('Test Anomalies')).toBeDefined();
|
||||
});
|
||||
|
||||
it('shows empty state when no anomalies', () => {
|
||||
renderList([]);
|
||||
expect(screen.getByText(/no anomalies/i)).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders anomaly cards', () => {
|
||||
renderList([makeAnomaly(), makeAnomaly({ id: 'anom_2', description: 'Another error' })]);
|
||||
expect(screen.getByText('HTTP 500 on form submit')).toBeDefined();
|
||||
expect(screen.getByText('Another error')).toBeDefined();
|
||||
});
|
||||
|
||||
it('filters by severity when severity button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderList([
|
||||
makeAnomaly({ id: 'a1', severity: 'high', description: 'High error' }),
|
||||
makeAnomaly({ id: 'a2', severity: 'low', description: 'Low error' }),
|
||||
]);
|
||||
|
||||
// Both are visible initially (all severities selected)
|
||||
expect(screen.getByText('High error')).toBeDefined();
|
||||
expect(screen.getByText('Low error')).toBeDefined();
|
||||
|
||||
// Click "high" to deselect it
|
||||
const highBtn = screen.getAllByRole('button').find((b) => b.textContent === 'HIGH');
|
||||
if (highBtn) await user.click(highBtn);
|
||||
|
||||
// High error should now be hidden
|
||||
expect(screen.queryByText('High error')).toBeNull();
|
||||
expect(screen.getByText('Low error')).toBeDefined();
|
||||
});
|
||||
|
||||
it('filters by description search', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderList([
|
||||
makeAnomaly({ id: 'a1', description: 'Server crashed unexpectedly' }),
|
||||
makeAnomaly({ id: 'a2', description: 'Timeout on login' }),
|
||||
]);
|
||||
|
||||
const searchInput = screen.getByPlaceholderText(/search/i);
|
||||
await user.type(searchInput, 'crashed');
|
||||
|
||||
expect(screen.getByText('Server crashed unexpectedly')).toBeDefined();
|
||||
expect(screen.queryByText('Timeout on login')).toBeNull();
|
||||
});
|
||||
});
|
||||
97
frontend/src/__tests__/NewSessionForm.test.tsx
Normal file
97
frontend/src/__tests__/NewSessionForm.test.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { NewSessionForm } from '../components/NewSessionForm';
|
||||
|
||||
// Mock the api module
|
||||
vi.mock('../hooks/useApi', () => ({
|
||||
api: {
|
||||
createSession: vi.fn(),
|
||||
},
|
||||
apiFetch: vi.fn(),
|
||||
}));
|
||||
|
||||
import { api } from '../hooks/useApi';
|
||||
|
||||
function renderForm(onCreated = vi.fn()) {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<NewSessionForm onCreated={onCreated} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('NewSessionForm', () => {
|
||||
it('renders URL field and submit button', () => {
|
||||
renderForm();
|
||||
expect(screen.getByLabelText(/target url/i)).toBeDefined();
|
||||
expect(screen.getByRole('button', { name: /start exploration/i })).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders auth type selector', () => {
|
||||
renderForm();
|
||||
expect(screen.getByLabelText(/auth type/i)).toBeDefined();
|
||||
});
|
||||
|
||||
it('shows login flow fields when login_flow is selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderForm();
|
||||
|
||||
const authSelect = screen.getByLabelText(/auth type/i);
|
||||
await user.selectOptions(authSelect, 'login_flow');
|
||||
|
||||
expect(screen.getByLabelText(/login url/i)).toBeDefined();
|
||||
expect(screen.getByLabelText(/username$/i)).toBeDefined();
|
||||
expect(screen.getByLabelText(/password$/i)).toBeDefined();
|
||||
});
|
||||
|
||||
it('does NOT show login flow fields when auth is none', () => {
|
||||
renderForm();
|
||||
expect(screen.queryByLabelText(/login url/i)).toBeNull();
|
||||
});
|
||||
|
||||
it('calls onCreated with sessionId on success', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onCreated = vi.fn();
|
||||
(api.createSession as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
sessionId: 'sess_test',
|
||||
status: 'running',
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
renderForm(onCreated);
|
||||
|
||||
const urlInput = screen.getByLabelText(/target url/i);
|
||||
await user.clear(urlInput);
|
||||
await user.type(urlInput, 'http://localhost:3000');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /start exploration/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onCreated).toHaveBeenCalledWith('sess_test');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows error message on failed session creation', async () => {
|
||||
const user = userEvent.setup();
|
||||
(api.createSession as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('Max concurrent sessions reached'));
|
||||
|
||||
renderForm();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /start exploration/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/max concurrent sessions reached/i)).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders fuzzing toggle and intensity selector', () => {
|
||||
renderForm();
|
||||
expect(screen.getByLabelText(/enable fuzzing/i)).toBeDefined();
|
||||
});
|
||||
});
|
||||
34
frontend/src/__tests__/SeverityBadge.test.tsx
Normal file
34
frontend/src/__tests__/SeverityBadge.test.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { SeverityBadge } from '../components/SeverityBadge';
|
||||
|
||||
describe('SeverityBadge', () => {
|
||||
it('renders severity text in uppercase', () => {
|
||||
render(<SeverityBadge severity="high" />);
|
||||
expect(screen.getByText('HIGH')).toBeDefined();
|
||||
});
|
||||
|
||||
it('applies red background for critical', () => {
|
||||
const { container } = render(<SeverityBadge severity="critical" />);
|
||||
const badge = container.firstChild as HTMLElement;
|
||||
expect(badge.className).toContain('bg-red-500');
|
||||
});
|
||||
|
||||
it('applies orange background for high', () => {
|
||||
const { container } = render(<SeverityBadge severity="high" />);
|
||||
const badge = container.firstChild as HTMLElement;
|
||||
expect(badge.className).toContain('bg-orange-500');
|
||||
});
|
||||
|
||||
it('applies yellow background for medium', () => {
|
||||
const { container } = render(<SeverityBadge severity="medium" />);
|
||||
const badge = container.firstChild as HTMLElement;
|
||||
expect(badge.className).toContain('bg-yellow-500');
|
||||
});
|
||||
|
||||
it('applies blue background for low', () => {
|
||||
const { container } = render(<SeverityBadge severity="low" />);
|
||||
const badge = container.firstChild as HTMLElement;
|
||||
expect(badge.className).toContain('bg-blue-500');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user