test(server): align specs with run environment and step schema
- update scenario tests to create runs with required environmentId - adjust browser controller expectations to session auto-create behavior - sync playwright/context mocks with current browser service usage
This commit is contained in:
@@ -9,6 +9,7 @@ const makePage = () => ({
|
|||||||
|
|
||||||
const makeContext = () => ({
|
const makeContext = () => ({
|
||||||
newPage: jest.fn().mockResolvedValue(makePage()),
|
newPage: jest.fn().mockResolvedValue(makePage()),
|
||||||
|
cookies: jest.fn().mockResolvedValue([]),
|
||||||
addCookies: jest.fn().mockResolvedValue(undefined),
|
addCookies: jest.fn().mockResolvedValue(undefined),
|
||||||
addInitScript: jest.fn().mockResolvedValue(undefined),
|
addInitScript: jest.fn().mockResolvedValue(undefined),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { Repository } from "typeorm";
|
|||||||
/**
|
/**
|
||||||
* Browser controller integration tests.
|
* Browser controller integration tests.
|
||||||
*
|
*
|
||||||
* POST /open and POST /exec need a running Playwright browser. We test
|
* POST /open and POST /exec need a running Playwright browser. We test
|
||||||
* validation rejections (no browser launched) and session-not-found paths,
|
* validation rejections and auto-create behavior for unknown named sessions,
|
||||||
* which are safe to run in a headless CI environment.
|
* which are safe to run in a headless CI environment.
|
||||||
*/
|
*/
|
||||||
describe("BrowserController", () => {
|
describe("BrowserController", () => {
|
||||||
@@ -54,11 +54,16 @@ describe("BrowserController", () => {
|
|||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when session does not exist", async () => {
|
it("auto-creates missing named session and succeeds", async () => {
|
||||||
await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post("/open")
|
.post("/open")
|
||||||
.send({ sessionName: "no-such-session", url: "https://example.com" })
|
.send({ sessionName: "no-such-session", url: "https://example.com" })
|
||||||
.expect(404);
|
.expect(201);
|
||||||
|
expect(res.body).toMatchObject({
|
||||||
|
url: expect.any(String),
|
||||||
|
title: expect.any(String),
|
||||||
|
content: expect.any(String),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("succeeds without a session (sessionless open)", async () => {
|
it("succeeds without a session (sessionless open)", async () => {
|
||||||
@@ -115,11 +120,12 @@ describe("BrowserController", () => {
|
|||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when session does not exist", async () => {
|
it("auto-creates missing named session and succeeds", async () => {
|
||||||
await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post("/exec")
|
.post("/exec")
|
||||||
.send({ sessionName: "no-such-session", code: "return 1;" })
|
.send({ sessionName: "no-such-session", code: "return 1;" })
|
||||||
.expect(404);
|
.expect(201);
|
||||||
|
expect(res.body).toEqual({ result: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("succeeds without a session (sessionless exec)", async () => {
|
it("succeeds without a session (sessionless exec)", async () => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { DataSource } from "typeorm";
|
|||||||
import { buildTestApp } from "./app.harness";
|
import { buildTestApp } from "./app.harness";
|
||||||
import { ScenarioService } from "../src/scenario/scenario.service";
|
import { ScenarioService } from "../src/scenario/scenario.service";
|
||||||
import { ScenarioSchedulerService } from "../src/scenario/scenario-scheduler.service";
|
import { ScenarioSchedulerService } from "../src/scenario/scenario-scheduler.service";
|
||||||
|
import { EnvironmentEntity } from "../src/environment/environment.entity";
|
||||||
|
|
||||||
describe("ScenarioRunStepEntity.output + helpers.getStepOutput", () => {
|
describe("ScenarioRunStepEntity.output + helpers.getStepOutput", () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
@@ -52,18 +53,26 @@ describe("ScenarioRunStepEntity.output + helpers.getStepOutput", () => {
|
|||||||
scenarioId: string,
|
scenarioId: string,
|
||||||
_order: number,
|
_order: number,
|
||||||
execCode: string,
|
execCode: string,
|
||||||
sessionName = "output-test-session",
|
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const step = await scenarioService.createStep(scenarioId, {
|
const step = await scenarioService.createStep(scenarioId, {
|
||||||
sessionName,
|
|
||||||
execCode,
|
execCode,
|
||||||
});
|
});
|
||||||
return step.id;
|
return step.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create a test environment and return its id. */
|
||||||
|
async function createEnvironment(
|
||||||
|
name = `output-env-${Math.random().toString(36).slice(2, 8)}`,
|
||||||
|
): Promise<string> {
|
||||||
|
const repo = dataSource.getRepository(EnvironmentEntity);
|
||||||
|
const env = await repo.save(repo.create({ name, data: {} }));
|
||||||
|
return env.id;
|
||||||
|
}
|
||||||
|
|
||||||
/** Trigger a run and process it to completion via the scheduler. */
|
/** Trigger a run and process it to completion via the scheduler. */
|
||||||
async function runScenario(scenarioId: string): Promise<string> {
|
async function runScenario(scenarioId: string): Promise<string> {
|
||||||
const run = await scenarioService.createRun(scenarioId);
|
const environmentId = await createEnvironment();
|
||||||
|
const run = await scenarioService.createRun(scenarioId, environmentId);
|
||||||
// Drive the scheduler directly — keeps tests synchronous and fast.
|
// Drive the scheduler directly — keeps tests synchronous and fast.
|
||||||
await scheduler.pickUpPendingRuns();
|
await scheduler.pickUpPendingRuns();
|
||||||
// Wait for the run to reach a terminal state (max 10 s).
|
// Wait for the run to reach a terminal state (max 10 s).
|
||||||
|
|||||||
@@ -41,6 +41,27 @@ describe("ScenarioController", () => {
|
|||||||
return res.body as { id: string };
|
return res.body as { id: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createEnvironment(name = `env-${Math.random()}`) {
|
||||||
|
const res = await request(app.getHttpServer())
|
||||||
|
.post("/environments")
|
||||||
|
.send({ name, data: {} })
|
||||||
|
.expect(201);
|
||||||
|
return res.body as { id: string; name: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createRun(scenarioId: string) {
|
||||||
|
const env = await createEnvironment();
|
||||||
|
const res = await request(app.getHttpServer())
|
||||||
|
.post(`/scenarios/${scenarioId}/run`)
|
||||||
|
.send({ environmentId: env.id })
|
||||||
|
.expect(201);
|
||||||
|
return res.body as {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
stepRuns: Array<{ status: string }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ── POST /scenarios ────────────────────────────────────────────────────────
|
// ── POST /scenarios ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe("POST /scenarios", () => {
|
describe("POST /scenarios", () => {
|
||||||
@@ -190,25 +211,22 @@ describe("ScenarioController", () => {
|
|||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post(`/scenarios/${sc.id}/steps`)
|
.post(`/scenarios/${sc.id}/steps`)
|
||||||
.send({
|
.send({
|
||||||
order: 0,
|
|
||||||
sessionName: "my-session",
|
|
||||||
execCode: "return 1;",
|
execCode: "return 1;",
|
||||||
})
|
})
|
||||||
.expect(201);
|
.expect(201);
|
||||||
|
|
||||||
expect(res.body.id).toBeDefined();
|
expect(res.body.id).toBeDefined();
|
||||||
expect(res.body.order).toBe(0);
|
expect(res.body.order).toBe(0);
|
||||||
expect(res.body.sessionName).toBe("my-session");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates a step without execCode", async () => {
|
it("creates a step without execCode", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.post(`/scenarios/${sc.id}/steps`)
|
.post(`/scenarios/${sc.id}/steps`)
|
||||||
.send({ order: 0, sessionName: "session-x" })
|
.send({ title: "step without exec" })
|
||||||
.expect(201);
|
.expect(201);
|
||||||
|
|
||||||
expect(res.body.sessionName).toBe("session-x");
|
expect(res.body.title).toBe("step without exec");
|
||||||
expect(res.body.execCode).toBeNull();
|
expect(res.body.execCode).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -371,15 +389,13 @@ describe("ScenarioController", () => {
|
|||||||
await createStep(sc.id, { order: 1 });
|
await createStep(sc.id, { order: 1 });
|
||||||
await createStep(sc.id, { order: 2 });
|
await createStep(sc.id, { order: 2 });
|
||||||
|
|
||||||
const res = await request(app.getHttpServer())
|
const res = await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
expect(res.body.status).toBe("pending");
|
expect(res.status).toBe("pending");
|
||||||
expect(Array.isArray(res.body.stepRuns)).toBe(true);
|
expect(Array.isArray(res.stepRuns)).toBe(true);
|
||||||
expect(res.body.stepRuns).toHaveLength(3);
|
expect(res.stepRuns).toHaveLength(3);
|
||||||
|
|
||||||
const statuses = res.body.stepRuns.map(
|
const statuses = res.stepRuns.map(
|
||||||
(s: { status: string }) => s.status,
|
(s: { status: string }) => s.status,
|
||||||
);
|
);
|
||||||
expect(statuses[0]).toBe("pending");
|
expect(statuses[0]).toBe("pending");
|
||||||
@@ -388,8 +404,10 @@ describe("ScenarioController", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 for unknown scenario", async () => {
|
it("returns 404 for unknown scenario", async () => {
|
||||||
|
const env = await createEnvironment();
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.post("/scenarios/00000000-0000-0000-0000-000000000001/run")
|
.post("/scenarios/00000000-0000-0000-0000-000000000001/run")
|
||||||
|
.send({ environmentId: env.id })
|
||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -400,9 +418,7 @@ describe("ScenarioController", () => {
|
|||||||
it("returns paginated runs with stepRuns embedded", async () => {
|
it("returns paginated runs with stepRuns embedded", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
await request(app.getHttpServer())
|
await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.get(`/scenarios/${sc.id}/runs`)
|
.get(`/scenarios/${sc.id}/runs`)
|
||||||
@@ -415,9 +431,7 @@ describe("ScenarioController", () => {
|
|||||||
it("filters by status", async () => {
|
it("filters by status", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
await request(app.getHttpServer())
|
await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
const pendingRes = await request(app.getHttpServer())
|
const pendingRes = await request(app.getHttpServer())
|
||||||
.get(`/scenarios/${sc.id}/runs?status=pending`)
|
.get(`/scenarios/${sc.id}/runs?status=pending`)
|
||||||
@@ -675,10 +689,8 @@ describe("ScenarioController", () => {
|
|||||||
it("returns run with stepRuns (with scenarioStep) and logs array", async () => {
|
it("returns run with stepRuns (with scenarioStep) and logs array", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
const runRes = await request(app.getHttpServer())
|
const runRes = await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
const runId = runRes.id;
|
||||||
.expect(201);
|
|
||||||
const runId = runRes.body.id;
|
|
||||||
|
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.get(`/scenarios/${sc.id}/run/${runId}`)
|
.get(`/scenarios/${sc.id}/run/${runId}`)
|
||||||
@@ -696,10 +708,8 @@ describe("ScenarioController", () => {
|
|||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
await createStep(sc.id, { order: 1 });
|
await createStep(sc.id, { order: 1 });
|
||||||
await createStep(sc.id, { order: 2 });
|
await createStep(sc.id, { order: 2 });
|
||||||
const runRes = await request(app.getHttpServer())
|
const runRes = await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
const runId = runRes.id;
|
||||||
.expect(201);
|
|
||||||
const runId = runRes.body.id;
|
|
||||||
|
|
||||||
const res = await request(app.getHttpServer())
|
const res = await request(app.getHttpServer())
|
||||||
.get(`/scenarios/${sc.id}/run/${runId}`)
|
.get(`/scenarios/${sc.id}/run/${runId}`)
|
||||||
@@ -720,10 +730,8 @@ describe("ScenarioController", () => {
|
|||||||
const sc1 = await createScenario();
|
const sc1 = await createScenario();
|
||||||
const sc2 = await createScenario();
|
const sc2 = await createScenario();
|
||||||
await createStep(sc1.id, { order: 0 });
|
await createStep(sc1.id, { order: 0 });
|
||||||
const runRes = await request(app.getHttpServer())
|
const runRes = await createRun(sc1.id);
|
||||||
.post(`/scenarios/${sc1.id}/run`)
|
const runId = runRes.id;
|
||||||
.expect(201);
|
|
||||||
const runId = runRes.body.id;
|
|
||||||
|
|
||||||
await request(app.getHttpServer())
|
await request(app.getHttpServer())
|
||||||
.get(`/scenarios/${sc2.id}/run/${runId}`)
|
.get(`/scenarios/${sc2.id}/run/${runId}`)
|
||||||
@@ -745,10 +753,8 @@ describe("ScenarioController", () => {
|
|||||||
it("returns 200 with run data immediately when run is already terminal", async () => {
|
it("returns 200 with run data immediately when run is already terminal", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
const runRes = await request(app.getHttpServer())
|
const runRes = await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
const runId = runRes.id;
|
||||||
.expect(201);
|
|
||||||
const runId = runRes.body.id;
|
|
||||||
|
|
||||||
// Manually mark run as pass so wait resolves immediately
|
// Manually mark run as pass so wait resolves immediately
|
||||||
const dataSource = app.get(DataSource);
|
const dataSource = app.get(DataSource);
|
||||||
@@ -769,10 +775,8 @@ describe("ScenarioController", () => {
|
|||||||
it("returns the run in fail state when it has failed", async () => {
|
it("returns the run in fail state when it has failed", async () => {
|
||||||
const sc = await createScenario();
|
const sc = await createScenario();
|
||||||
await createStep(sc.id, { order: 0 });
|
await createStep(sc.id, { order: 0 });
|
||||||
const runRes = await request(app.getHttpServer())
|
const runRes = await createRun(sc.id);
|
||||||
.post(`/scenarios/${sc.id}/run`)
|
const runId = runRes.id;
|
||||||
.expect(201);
|
|
||||||
const runId = runRes.body.id;
|
|
||||||
|
|
||||||
const dataSource = app.get(DataSource);
|
const dataSource = app.get(DataSource);
|
||||||
await dataSource.query(
|
await dataSource.query(
|
||||||
|
|||||||
Reference in New Issue
Block a user