docs: enterprise refactor plan with ralph specs
This commit is contained in:
224
tests/server/server.test.ts
Normal file
224
tests/server/server.test.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Integration tests for the ABE API server.
|
||||
* Uses supertest to hit the Express app directly (no real browser).
|
||||
* The SessionStore is given a mock that never calls Playwright.
|
||||
*/
|
||||
|
||||
import request from 'supertest';
|
||||
import { createApp } from '../../src/server/index';
|
||||
import { SessionStore, SessionRecord } from '../../src/server/SessionStore';
|
||||
import { IAnomaly } from '../../src/core/interfaces';
|
||||
|
||||
// ─── Mock SessionStore ────────────────────────────────────────────────────────
|
||||
|
||||
function makeAnomaly(overrides: Partial<IAnomaly> = {}): IAnomaly {
|
||||
return {
|
||||
id: 'anom_test1',
|
||||
type: 'http_error',
|
||||
severity: 'high',
|
||||
observationId: 'obs_1',
|
||||
actionTrace: [],
|
||||
description: 'HTTP 500 on form submit',
|
||||
evidence: {},
|
||||
timestamp: 1000000,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function makeSession(overrides: Partial<SessionRecord> = {}): SessionRecord {
|
||||
return {
|
||||
sessionId: 'sess_1',
|
||||
url: 'http://localhost:3000',
|
||||
seed: 42,
|
||||
maxStates: 50,
|
||||
status: 'running',
|
||||
startedAt: '2026-01-01T00:00:00.000Z',
|
||||
statesVisited: 5,
|
||||
anomaliesFound: 1,
|
||||
anomalies: [makeAnomaly()],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
class MockSessionStore extends SessionStore {
|
||||
private _sessions: SessionRecord[];
|
||||
|
||||
constructor(sessions: SessionRecord[] = []) {
|
||||
super('./reports');
|
||||
this._sessions = sessions;
|
||||
}
|
||||
|
||||
getAllSessions() { return this._sessions; }
|
||||
getSession(id: string) { return this._sessions.find((s) => s.sessionId === id); }
|
||||
getAllAnomalies(sessionId?: string, severity?: string) {
|
||||
return this._sessions
|
||||
.flatMap((s) => s.anomalies)
|
||||
.filter((a) => !sessionId || this.findSessionForAnomaly(a.id) === sessionId)
|
||||
.filter((a) => !severity || a.severity === severity);
|
||||
}
|
||||
getAnomaly(id: string) {
|
||||
return this._sessions.flatMap((s) => s.anomalies).find((a) => a.id === id);
|
||||
}
|
||||
findSessionForAnomaly(anomalyId: string) {
|
||||
return this._sessions.find((s) => s.anomalies.some((a) => a.id === anomalyId))?.sessionId;
|
||||
}
|
||||
screenshotPath(_anomalyId: string) { return undefined; }
|
||||
stopSession(id: string) {
|
||||
const s = this.getSession(id);
|
||||
if (!s || s.status !== 'running') return false;
|
||||
s.status = 'stopped';
|
||||
return true;
|
||||
}
|
||||
async startSession(params: { url: string; seed: number; maxStates: number }) {
|
||||
const record = makeSession({
|
||||
sessionId: `sess_new`,
|
||||
url: params.url,
|
||||
seed: params.seed,
|
||||
maxStates: params.maxStates,
|
||||
status: 'running',
|
||||
anomalies: [],
|
||||
anomaliesFound: 0,
|
||||
});
|
||||
this._sessions.push(record);
|
||||
return record;
|
||||
}
|
||||
async replayAnomaly(anomalyId: string) {
|
||||
if (!this.getAnomaly(anomalyId)) throw new Error('Anomaly not found');
|
||||
return `replay_${anomalyId}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('GET /api/sessions', () => {
|
||||
it('returns empty array when no sessions', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).get('/api/sessions');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns list of sessions', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).get('/api/sessions');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toHaveLength(1);
|
||||
expect(res.body[0].sessionId).toBe('sess_1');
|
||||
expect(res.body[0].status).toBe('running');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/sessions/:sessionId', () => {
|
||||
it('returns session detail', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).get('/api/sessions/sess_1');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.sessionId).toBe('sess_1');
|
||||
expect(res.body.seed).toBe(42);
|
||||
});
|
||||
|
||||
it('returns 404 for unknown session', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).get('/api/sessions/no_such');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/sessions', () => {
|
||||
it('creates a new session', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app)
|
||||
.post('/api/sessions')
|
||||
.send({ url: 'http://localhost:3000', seed: 1, maxStates: 10 });
|
||||
expect(res.status).toBe(201);
|
||||
expect(res.body.sessionId).toBeDefined();
|
||||
expect(res.body.status).toBe('running');
|
||||
});
|
||||
|
||||
it('returns 400 when url is missing', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).post('/api/sessions').send({ seed: 1 });
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /api/sessions/:sessionId', () => {
|
||||
it('stops a running session', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).delete('/api/sessions/sess_1');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.stopped).toBe(true);
|
||||
});
|
||||
|
||||
it('returns 404 for unknown session', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).delete('/api/sessions/no_such');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/anomalies', () => {
|
||||
it('returns all anomalies', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).get('/api/anomalies');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toHaveLength(1);
|
||||
expect(res.body[0].id).toBe('anom_test1');
|
||||
expect(res.body[0].severity).toBe('high');
|
||||
});
|
||||
|
||||
it('filters by severity', async () => {
|
||||
const session = makeSession({
|
||||
anomalies: [
|
||||
makeAnomaly({ id: 'anom_h', severity: 'high' }),
|
||||
makeAnomaly({ id: 'anom_l', severity: 'low' }),
|
||||
],
|
||||
anomaliesFound: 2,
|
||||
});
|
||||
const app = createApp(new MockSessionStore([session]));
|
||||
const res = await request(app).get('/api/anomalies?severity=low');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toHaveLength(1);
|
||||
expect(res.body[0].id).toBe('anom_l');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/anomalies/:anomalyId', () => {
|
||||
it('returns anomaly detail', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).get('/api/anomalies/anom_test1');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.type).toBe('http_error');
|
||||
expect(res.body.description).toBe('HTTP 500 on form submit');
|
||||
});
|
||||
|
||||
it('returns 404 for unknown anomaly', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).get('/api/anomalies/no_such');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/anomalies/:anomalyId/screenshot', () => {
|
||||
it('returns 404 when no screenshot exists', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).get('/api/anomalies/anom_test1/screenshot');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/anomalies/:anomalyId/replay', () => {
|
||||
it('returns replayId and running status', async () => {
|
||||
const app = createApp(new MockSessionStore([makeSession()]));
|
||||
const res = await request(app).post('/api/anomalies/anom_test1/replay');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.replayId).toBeDefined();
|
||||
expect(res.body.status).toBe('running');
|
||||
});
|
||||
|
||||
it('returns 404 for unknown anomaly', async () => {
|
||||
const app = createApp(new MockSessionStore([]));
|
||||
const res = await request(app).post('/api/anomalies/no_such/replay');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user