feat(scenario): run logs, lint/format tooling, CONTRIBUTING
- add ScenarioRunLogEntity to persist step script output to DB - stepLogger dual-writes to NestJS logger and DB (fire-and-forget) - add GET /scenarios/:id/run/:runId returning run, stepRuns and logs - add POST /scenarios/:id/run/:runId/wait (polls until terminal state) - 9 new integration tests for the two endpoints (136 total) - add eslint with typescript-eslint and eslint-config-prettier - add npm scripts: format, lint, lint:fix - resolve all lint errors across src and test (no any types) - add CONTRIBUTING.md covering dev workflow
This commit is contained in:
@@ -1,17 +1,19 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import request from 'supertest';
|
||||
import { buildTestApp } from './app.harness';
|
||||
import { SessionEntity } from '../src/session/session.entity';
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { getRepositoryToken } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import request from "supertest";
|
||||
import { buildTestApp } from "./app.harness";
|
||||
import { SessionEntity } from "../src/session/session.entity";
|
||||
|
||||
describe('SessionController', () => {
|
||||
describe("SessionController", () => {
|
||||
let app: INestApplication;
|
||||
let repo: Repository<SessionEntity>;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await buildTestApp();
|
||||
repo = app.get<Repository<SessionEntity>>(getRepositoryToken(SessionEntity));
|
||||
repo = app.get<Repository<SessionEntity>>(
|
||||
getRepositoryToken(SessionEntity),
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -22,98 +24,120 @@ describe('SessionController', () => {
|
||||
return repo.save(
|
||||
repo.create({
|
||||
sessionName: name,
|
||||
token: 'tok',
|
||||
cookies: '[]',
|
||||
localStorage: '{}',
|
||||
token: "tok",
|
||||
cookies: "[]",
|
||||
localStorage: "{}",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ── GET /sessions ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('GET /sessions', () => {
|
||||
it('returns 200 with a paginated result', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/sessions').expect(200);
|
||||
describe("GET /sessions", () => {
|
||||
it("returns 200 with a paginated result", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions")
|
||||
.expect(200);
|
||||
expect(Array.isArray(res.body.data)).toBe(true);
|
||||
expect(typeof res.body.total).toBe('number');
|
||||
expect(res.body).toHaveProperty('page');
|
||||
expect(res.body).toHaveProperty('limit');
|
||||
expect(typeof res.body.total).toBe("number");
|
||||
expect(res.body).toHaveProperty("page");
|
||||
expect(res.body).toHaveProperty("limit");
|
||||
});
|
||||
|
||||
it('includes seeded sessions', async () => {
|
||||
await seedSession('visible-session');
|
||||
const res = await request(app.getHttpServer()).get('/sessions').expect(200);
|
||||
const names = res.body.data.map((s: any) => s.sessionName);
|
||||
expect(names).toContain('visible-session');
|
||||
it("includes seeded sessions", async () => {
|
||||
await seedSession("visible-session");
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions")
|
||||
.expect(200);
|
||||
const names = res.body.data.map((s: { sessionName: string }) => s.sessionName);
|
||||
expect(names).toContain("visible-session");
|
||||
});
|
||||
|
||||
it('does not expose token, cookies or localStorage fields', async () => {
|
||||
await seedSession('private-session');
|
||||
const res = await request(app.getHttpServer()).get('/sessions').expect(200);
|
||||
const item = res.body.data.find((s: any) => s.sessionName === 'private-session');
|
||||
it("does not expose token, cookies or localStorage fields", async () => {
|
||||
await seedSession("private-session");
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions")
|
||||
.expect(200);
|
||||
const item = res.body.data.find(
|
||||
(s: { sessionName: string }) => s.sessionName === "private-session",
|
||||
) as Record<string, unknown>;
|
||||
expect(item).toBeDefined();
|
||||
expect(item.token).toBeUndefined();
|
||||
expect(item.cookies).toBeUndefined();
|
||||
expect(item.localStorage).toBeUndefined();
|
||||
});
|
||||
|
||||
it('respects page and limit params', async () => {
|
||||
await seedSession('paged-session-a');
|
||||
await seedSession('paged-session-b');
|
||||
it("respects page and limit params", async () => {
|
||||
await seedSession("paged-session-a");
|
||||
await seedSession("paged-session-b");
|
||||
|
||||
const res = await request(app.getHttpServer()).get('/sessions?page=1&limit=1').expect(200);
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions?page=1&limit=1")
|
||||
.expect(200);
|
||||
expect(res.body.data).toHaveLength(1);
|
||||
expect(res.body.page).toBe(1);
|
||||
expect(res.body.limit).toBe(1);
|
||||
});
|
||||
|
||||
it('returns empty data array for out-of-range page', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/sessions?page=9999&limit=20').expect(200);
|
||||
it("returns empty data array for out-of-range page", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions?page=9999&limit=20")
|
||||
.expect(200);
|
||||
expect(res.body.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('returns 400 for invalid page param', async () => {
|
||||
await request(app.getHttpServer()).get('/sessions?page=0').expect(400);
|
||||
it("returns 400 for invalid page param", async () => {
|
||||
await request(app.getHttpServer()).get("/sessions?page=0").expect(400);
|
||||
});
|
||||
|
||||
it('orders by sessionName ASC', async () => {
|
||||
await seedSession('zzz-sort-session');
|
||||
await seedSession('aaa-sort-session');
|
||||
it("orders by sessionName ASC", async () => {
|
||||
await seedSession("zzz-sort-session");
|
||||
await seedSession("aaa-sort-session");
|
||||
|
||||
const res = await request(app.getHttpServer()).get('/sessions?orderBy=sessionName&orderDir=ASC').expect(200);
|
||||
const names: string[] = res.body.data.map((s: any) => s.sessionName);
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions?orderBy=sessionName&orderDir=ASC")
|
||||
.expect(200);
|
||||
const names: string[] = res.body.data.map((s: { sessionName: string }) => s.sessionName);
|
||||
expect(names).toEqual([...names].sort());
|
||||
});
|
||||
|
||||
it('orders by sessionName DESC', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/sessions?orderBy=sessionName&orderDir=DESC').expect(200);
|
||||
const names: string[] = res.body.data.map((s: any) => s.sessionName);
|
||||
it("orders by sessionName DESC", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions?orderBy=sessionName&orderDir=DESC")
|
||||
.expect(200);
|
||||
const names: string[] = res.body.data.map((s: { sessionName: string }) => s.sessionName);
|
||||
expect(names).toEqual([...names].sort().reverse());
|
||||
});
|
||||
|
||||
it('returns 400 for invalid orderDir', async () => {
|
||||
await request(app.getHttpServer()).get('/sessions?orderDir=SIDEWAYS').expect(400);
|
||||
it("returns 400 for invalid orderDir", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get("/sessions?orderDir=SIDEWAYS")
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
// ── DELETE /sessions/:id ───────────────────────────────────────────────────
|
||||
|
||||
describe('DELETE /sessions/:id', () => {
|
||||
it('deletes an existing session and returns 200', async () => {
|
||||
const s = await seedSession('delete-me-session');
|
||||
await request(app.getHttpServer()).delete(`/sessions/${s.id}`).expect(200);
|
||||
describe("DELETE /sessions/:id", () => {
|
||||
it("deletes an existing session and returns 200", async () => {
|
||||
const s = await seedSession("delete-me-session");
|
||||
await request(app.getHttpServer())
|
||||
.delete(`/sessions/${s.id}`)
|
||||
.expect(200);
|
||||
|
||||
const res = await request(app.getHttpServer()).get('/sessions').expect(200);
|
||||
const names = res.body.data.map((sess: any) => sess.sessionName);
|
||||
expect(names).not.toContain('delete-me-session');
|
||||
const res = await request(app.getHttpServer())
|
||||
.get("/sessions")
|
||||
.expect(200);
|
||||
const names = res.body.data.map((sess: { sessionName: string }) => sess.sessionName);
|
||||
expect(names).not.toContain("delete-me-session");
|
||||
});
|
||||
|
||||
it('returns 404 for unknown id', async () => {
|
||||
await request(app.getHttpServer()).delete('/sessions/99999').expect(404);
|
||||
it("returns 404 for unknown id", async () => {
|
||||
await request(app.getHttpServer()).delete("/sessions/99999").expect(404);
|
||||
});
|
||||
|
||||
it('returns 400 for non-numeric id', async () => {
|
||||
await request(app.getHttpServer()).delete('/sessions/abc').expect(400);
|
||||
it("returns 400 for non-numeric id", async () => {
|
||||
await request(app.getHttpServer()).delete("/sessions/abc").expect(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user