feat(scenario): export/import endpoints and integration tests
- add GET /scenarios/:id/export returning ScenarioExportDto - add POST /scenarios/import creating scenario with all steps - add ScenarioExportDto and ScenarioStepExportDto classes - fix McpServer singleton by creating fresh instance per handle() call - add 12 integration tests covering shape, ordering, round-trip, validation
This commit is contained in:
@@ -371,4 +371,157 @@ describe('ScenarioController', () => {
|
||||
await request(app.getHttpServer()).get('/scenarios/99999/runs').expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
// ── GET /scenarios/:id/export ─────────────────────────────────────────────
|
||||
|
||||
describe('GET /scenarios/:id/export', () => {
|
||||
it('returns name and steps array', async () => {
|
||||
const sc = await createScenario('export-me');
|
||||
await createStep(sc.id, { order: 0, type: 'login', sessionName: 's', execCode: '{"keyId":"k","environmentName":"e"}' });
|
||||
await createStep(sc.id, { order: 1, type: 'exec', sessionName: 's', execCode: 'return 1;', validateCode: 'return true;' });
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.name).toBe('export-me');
|
||||
expect(Array.isArray(res.body.steps)).toBe(true);
|
||||
expect(res.body.steps).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('exports steps ordered by order field', async () => {
|
||||
const sc = await createScenario('export-order');
|
||||
await createStep(sc.id, { order: 2, sessionName: 's' });
|
||||
await createStep(sc.id, { order: 0, sessionName: 's' });
|
||||
await createStep(sc.id, { order: 1, sessionName: 's' });
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
const orders = res.body.steps.map((s: any) => s.order);
|
||||
expect(orders).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it('omits internal fields (id, scenarioId, timestamps)', async () => {
|
||||
const sc = await createScenario('export-shape');
|
||||
await createStep(sc.id, { order: 0, sessionName: 's' });
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
const step = res.body.steps[0];
|
||||
expect(step).not.toHaveProperty('id');
|
||||
expect(step).not.toHaveProperty('scenarioId');
|
||||
expect(step).not.toHaveProperty('createdAt');
|
||||
expect(step).not.toHaveProperty('updatedAt');
|
||||
});
|
||||
|
||||
it('exports null validateCode as null', async () => {
|
||||
const sc = await createScenario('export-null-validate');
|
||||
await createStep(sc.id, { order: 0, sessionName: 's', execCode: 'return 1;' });
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
expect(res.body.steps[0].validateCode).toBeNull();
|
||||
});
|
||||
|
||||
it('returns 404 for unknown scenario', async () => {
|
||||
await request(app.getHttpServer()).get('/scenarios/99999/export').expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
// ── POST /scenarios/import ────────────────────────────────────────────────
|
||||
|
||||
describe('POST /scenarios/import', () => {
|
||||
it('creates a new scenario with all steps', async () => {
|
||||
const payload = {
|
||||
name: 'imported scenario',
|
||||
steps: [
|
||||
{ order: 0, type: 'login', sessionName: 's', execCode: '{"keyId":"k","environmentName":"e"}', validateCode: null },
|
||||
{ order: 1, type: 'exec', sessionName: 's', execCode: 'return 1;', validateCode: 'return true;' },
|
||||
{ order: 2, type: 'sign', sessionName: 's', execCode: '{"keyId":"k"}', validateCode: null },
|
||||
],
|
||||
};
|
||||
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send(payload)
|
||||
.expect(201);
|
||||
|
||||
expect(res.body.id).toBeDefined();
|
||||
expect(res.body.name).toBe('imported scenario');
|
||||
expect(res.body.steps).toHaveLength(3);
|
||||
expect(res.body.steps[0].type).toBe('login');
|
||||
expect(res.body.steps[1].type).toBe('exec');
|
||||
expect(res.body.steps[2].type).toBe('sign');
|
||||
});
|
||||
|
||||
it('assigns a new id (does not collide with source)', async () => {
|
||||
const sc = await createScenario('original');
|
||||
const exportRes = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
const importRes = await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send(exportRes.body)
|
||||
.expect(201);
|
||||
|
||||
expect(importRes.body.id).not.toBe(sc.id);
|
||||
});
|
||||
|
||||
it('round-trips a scenario faithfully', async () => {
|
||||
const sc = await createScenario('roundtrip');
|
||||
await createStep(sc.id, { order: 0, type: 'exec', sessionName: 'rs', execCode: 'return 42;', validateCode: 'return true;' });
|
||||
|
||||
const exportRes = await request(app.getHttpServer())
|
||||
.get(`/scenarios/${sc.id}/export`)
|
||||
.expect(200);
|
||||
|
||||
const importRes = await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send(exportRes.body)
|
||||
.expect(201);
|
||||
|
||||
expect(importRes.body.name).toBe('roundtrip');
|
||||
expect(importRes.body.steps).toHaveLength(1);
|
||||
expect(importRes.body.steps[0].execCode).toBe(exportRes.body.steps[0].execCode);
|
||||
expect(importRes.body.steps[0].validateCode).toBe(exportRes.body.steps[0].validateCode);
|
||||
});
|
||||
|
||||
it('imports with empty steps array', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send({ name: 'empty-import', steps: [] })
|
||||
.expect(201);
|
||||
|
||||
expect(res.body.name).toBe('empty-import');
|
||||
expect(res.body.steps).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns 400 when name is missing', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send({ steps: [] })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('returns 400 when steps is not an array', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send({ name: 'bad', steps: 'oops' })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('returns 400 when a step has an invalid type', async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post('/scenarios/import')
|
||||
.send({ name: 'bad-type', steps: [{ order: 0, type: 'unknown', sessionName: 's' }] })
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user