Files
liqa/test/scenario.controller.spec.ts
T
ars9 00f310e899 test: add jest test suite with controller specs and mocks
- add jest, ts-jest, supertest, and related dev dependencies
- add test scripts (test, test:debug, test:watch) to package.json
- add jest.config.js with ts-jest transform and module name mappers
- add controller specs for auth, browser, environment, mcp, scenario, session
- add mocks for jsdom, playwright, and readability
- add test/tsconfig.json and exclude test dir from main tsconfig
2026-04-07 15:54:13 +03:00

347 lines
12 KiB
TypeScript

import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { buildTestApp } from './app.harness';
describe('ScenarioController', () => {
let app: INestApplication;
beforeAll(async () => {
app = await buildTestApp();
});
afterAll(async () => {
await app.close();
});
// ── helpers ────────────────────────────────────────────────────────────────
async function createScenario(name = 'test scenario') {
const res = await request(app.getHttpServer())
.post('/scenarios')
.send({ name })
.expect(201);
return res.body as { id: number; name: string };
}
async function createStep(scenarioId: number, overrides: Record<string, unknown> = {}) {
const res = await request(app.getHttpServer())
.post(`/scenarios/${scenarioId}/steps`)
.send({
order: 0,
type: 'exec',
sessionName: 'test-session',
execCode: 'return 1;',
...overrides,
})
.expect(201);
return res.body as { id: number };
}
// ── POST /scenarios ────────────────────────────────────────────────────────
describe('POST /scenarios', () => {
it('creates a scenario and returns 201', async () => {
const res = await request(app.getHttpServer())
.post('/scenarios')
.send({ name: 'my scenario' })
.expect(201);
expect(res.body.id).toBeDefined();
expect(res.body.name).toBe('my scenario');
});
it('returns 400 when name is missing', async () => {
await request(app.getHttpServer()).post('/scenarios').send({}).expect(400);
});
});
// ── GET /scenarios ─────────────────────────────────────────────────────────
describe('GET /scenarios', () => {
it('returns paginated result', async () => {
const res = await request(app.getHttpServer()).get('/scenarios').expect(200);
expect(res.body).toHaveProperty('data');
expect(res.body).toHaveProperty('total');
expect(res.body).toHaveProperty('page');
expect(res.body).toHaveProperty('limit');
expect(Array.isArray(res.body.data)).toBe(true);
});
it('respects page and limit params', async () => {
const res = await request(app.getHttpServer())
.get('/scenarios?page=1&limit=2')
.expect(200);
expect(res.body.limit).toBe(2);
expect(res.body.page).toBe(1);
});
it('returns 400 for invalid pagination params', async () => {
await request(app.getHttpServer()).get('/scenarios?page=0').expect(400);
});
});
// ── GET /scenarios/:id ─────────────────────────────────────────────────────
describe('GET /scenarios/:id', () => {
it('returns the scenario with steps array', async () => {
const sc = await createScenario('scenario-get-one');
const res = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}`)
.expect(200);
expect(res.body.id).toBe(sc.id);
expect(Array.isArray(res.body.steps)).toBe(true);
});
it('returns 404 for unknown id', async () => {
await request(app.getHttpServer()).get('/scenarios/99999').expect(404);
});
});
// ── PATCH /scenarios/:id ───────────────────────────────────────────────────
describe('PATCH /scenarios/:id', () => {
it('updates scenario name', async () => {
const sc = await createScenario('patch-me');
const res = await request(app.getHttpServer())
.patch(`/scenarios/${sc.id}`)
.send({ name: 'patched' })
.expect(200);
expect(res.body.name).toBe('patched');
});
it('returns 404 for unknown id', async () => {
await request(app.getHttpServer())
.patch('/scenarios/99999')
.send({ name: 'x' })
.expect(404);
});
});
// ── DELETE /scenarios/:id ──────────────────────────────────────────────────
describe('DELETE /scenarios/:id', () => {
it('deletes and returns 204', async () => {
const sc = await createScenario('delete-me');
await request(app.getHttpServer()).delete(`/scenarios/${sc.id}`).expect(204);
await request(app.getHttpServer()).get(`/scenarios/${sc.id}`).expect(404);
});
it('returns 404 for unknown id', async () => {
await request(app.getHttpServer()).delete('/scenarios/99999').expect(404);
});
});
// ── POST /scenarios/:id/steps ──────────────────────────────────────────────
describe('POST /scenarios/:id/steps', () => {
it('creates a step with required fields', async () => {
const sc = await createScenario();
const res = await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/steps`)
.send({ order: 0, type: 'exec', sessionName: 'my-session', execCode: 'return 1;' })
.expect(201);
expect(res.body.id).toBeDefined();
expect(res.body.order).toBe(0);
expect(res.body.type).toBe('exec');
expect(res.body.sessionName).toBe('my-session');
});
it('creates a login step', async () => {
const sc = await createScenario();
const res = await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/steps`)
.send({ order: 0, type: 'login', sessionName: 'session-x' })
.expect(201);
expect(res.body.type).toBe('login');
});
it('returns 400 when order is missing', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/steps`)
.send({ type: 'exec', sessionName: 'x' })
.expect(400);
});
it('returns 400 when type is invalid', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/steps`)
.send({ order: 0, type: 'unknown', sessionName: 'x' })
.expect(400);
});
it('returns 400 when sessionName is missing', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/steps`)
.send({ order: 0, type: 'exec' })
.expect(400);
});
it('returns 404 for unknown scenario', async () => {
await request(app.getHttpServer())
.post('/scenarios/99999/steps')
.send({ order: 0, type: 'exec', sessionName: 'x' })
.expect(404);
});
it('returns steps ordered by order field', async () => {
const sc = await createScenario();
await createStep(sc.id, { order: 2, type: 'exec', sessionName: 's' });
await createStep(sc.id, { order: 0, type: 'exec', sessionName: 's' });
await createStep(sc.id, { order: 1, type: 'exec', sessionName: 's' });
const res = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}`)
.expect(200);
const orders = res.body.steps.map((s: any) => s.order);
expect(orders).toEqual([0, 1, 2]);
});
});
// ── GET /scenarios/:id/steps/:stepId ──────────────────────────────────────
describe('GET /scenarios/:id/steps/:stepId', () => {
it('returns the step', async () => {
const sc = await createScenario();
const step = await createStep(sc.id);
const res = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/steps/${step.id}`)
.expect(200);
expect(res.body.id).toBe(step.id);
});
it('returns 404 for unknown step', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/steps/99999`)
.expect(404);
});
});
// ── PATCH /scenarios/:id/steps/:stepId ────────────────────────────────────
describe('PATCH /scenarios/:id/steps/:stepId', () => {
it('updates step fields', async () => {
const sc = await createScenario();
const step = await createStep(sc.id, { order: 0, execCode: 'return 1;' });
const res = await request(app.getHttpServer())
.patch(`/scenarios/${sc.id}/steps/${step.id}`)
.send({ order: 5, execCode: 'return 99;' })
.expect(200);
expect(res.body.order).toBe(5);
expect(res.body.execCode).toBe('return 99;');
});
it('returns 404 for unknown step', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.patch(`/scenarios/${sc.id}/steps/99999`)
.send({ order: 1 })
.expect(404);
});
});
// ── DELETE /scenarios/:id/steps/:stepId ───────────────────────────────────
describe('DELETE /scenarios/:id/steps/:stepId', () => {
it('deletes the step and returns 204', async () => {
const sc = await createScenario();
const step = await createStep(sc.id);
await request(app.getHttpServer())
.delete(`/scenarios/${sc.id}/steps/${step.id}`)
.expect(204);
await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/steps/${step.id}`)
.expect(404);
});
});
// ── POST /scenarios/:id/run ────────────────────────────────────────────────
describe('POST /scenarios/:id/run', () => {
it('creates a run with stepRuns in correct initial states', async () => {
const sc = await createScenario();
await createStep(sc.id, { order: 0 });
await createStep(sc.id, { order: 1 });
await createStep(sc.id, { order: 2 });
const res = await request(app.getHttpServer())
.post(`/scenarios/${sc.id}/run`)
.expect(201);
expect(res.body.status).toBe('pending');
expect(Array.isArray(res.body.stepRuns)).toBe(true);
expect(res.body.stepRuns).toHaveLength(3);
const statuses = res.body.stepRuns.map((s: any) => s.status);
expect(statuses[0]).toBe('pending');
expect(statuses[1]).toBe('waiting');
expect(statuses[2]).toBe('waiting');
});
it('returns 404 for unknown scenario', async () => {
await request(app.getHttpServer())
.post('/scenarios/99999/run')
.expect(404);
});
});
// ── GET /scenarios/:id/runs ────────────────────────────────────────────────
describe('GET /scenarios/:id/runs', () => {
it('returns paginated runs with stepRuns embedded', async () => {
const sc = await createScenario();
await createStep(sc.id, { order: 0 });
await request(app.getHttpServer()).post(`/scenarios/${sc.id}/run`).expect(201);
const res = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/runs`)
.expect(200);
expect(res.body.total).toBeGreaterThanOrEqual(1);
expect(Array.isArray(res.body.data[0].stepRuns)).toBe(true);
});
it('filters by status', async () => {
const sc = await createScenario();
await createStep(sc.id, { order: 0 });
await request(app.getHttpServer()).post(`/scenarios/${sc.id}/run`).expect(201);
const pendingRes = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/runs?status=pending`)
.expect(200);
expect(pendingRes.body.total).toBeGreaterThanOrEqual(1);
pendingRes.body.data.forEach((r: any) => expect(r.status).toBe('pending'));
const passRes = await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/runs?status=pass`)
.expect(200);
expect(passRes.body.total).toBe(0);
});
it('returns 400 for invalid status filter', async () => {
const sc = await createScenario();
await request(app.getHttpServer())
.get(`/scenarios/${sc.id}/runs?status=invalid`)
.expect(400);
});
it('returns 404 for unknown scenario', async () => {
await request(app.getHttpServer()).get('/scenarios/99999/runs').expect(404);
});
});
});