chore(repo): restructure as monorepo with server and client workspaces
- move NestJS app into server/ subdirectory - add client/ React+TypeScript (Vite) app with Hello World - update docker-compose to build and run both services - add root package.json declaring npm workspaces - update .gitignore to cover node_modules and dist at all depths
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { getRepositoryToken } from "@nestjs/typeorm";
|
||||
import request from "supertest";
|
||||
import { buildTestApp } from "./app.harness";
|
||||
import { SessionEntity } from "../src/session/session.entity";
|
||||
import { Repository } from "typeorm";
|
||||
|
||||
/**
|
||||
* Browser controller integration tests.
|
||||
*
|
||||
* POST /open and POST /exec need a running Playwright browser. We test
|
||||
* validation rejections (no browser launched) and session-not-found paths,
|
||||
* which are safe to run in a headless CI environment.
|
||||
*/
|
||||
describe("BrowserController", () => {
|
||||
let app: INestApplication;
|
||||
let sessionRepo: Repository<SessionEntity>;
|
||||
|
||||
const FAKE_SESSION = "test-browser-session";
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await buildTestApp();
|
||||
sessionRepo = app.get<Repository<SessionEntity>>(
|
||||
getRepositoryToken(SessionEntity),
|
||||
);
|
||||
|
||||
// Seed a session with minimal but valid JSON so the browser code can
|
||||
// deserialise it (it will still fail to open a real page, tested separately)
|
||||
await sessionRepo.save(
|
||||
sessionRepo.create({
|
||||
sessionName: FAKE_SESSION,
|
||||
token: "fake-token",
|
||||
cookies: "[]",
|
||||
localStorage: "{}",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
// ── POST /open ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe("POST /open", () => {
|
||||
it("returns 400 when body is empty", async () => {
|
||||
await request(app.getHttpServer()).post("/open").send({}).expect(400);
|
||||
});
|
||||
|
||||
it("returns 400 when url is missing", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/open")
|
||||
.send({ sessionName: FAKE_SESSION })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("returns 404 when session does not exist", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/open")
|
||||
.send({ sessionName: "no-such-session", url: "https://example.com" })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("succeeds without a session (sessionless open)", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/open")
|
||||
.send({ url: "https://example.com" })
|
||||
.expect(201);
|
||||
expect(res.body).toMatchObject({
|
||||
url: expect.any(String),
|
||||
title: expect.any(String),
|
||||
content: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it("returns only selector content when selector is provided", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/open")
|
||||
.send({ url: "https://example.com", selector: "#mock" })
|
||||
.expect(201);
|
||||
expect(res.body.content).toBe('<div id="mock">mock content</div>');
|
||||
});
|
||||
|
||||
it("returns selector text content in reader mode", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/open")
|
||||
.send({
|
||||
url: "https://example.com",
|
||||
selector: "#mock",
|
||||
readerMode: true,
|
||||
})
|
||||
.expect(201);
|
||||
expect(res.body.content).toBe("mock content");
|
||||
});
|
||||
});
|
||||
|
||||
// ── POST /exec ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe("POST /exec", () => {
|
||||
it("returns 400 when body is empty", async () => {
|
||||
await request(app.getHttpServer()).post("/exec").send({}).expect(400);
|
||||
});
|
||||
|
||||
it("returns 400 when code is missing", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({ sessionName: FAKE_SESSION })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("returns 400 when code has a syntax error", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({ sessionName: FAKE_SESSION, code: "this is not valid {{{" })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("returns 404 when session does not exist", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({ sessionName: "no-such-session", code: "return 1;" })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("succeeds without a session (sessionless exec)", async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post("/exec")
|
||||
.send({ code: "return 42;" })
|
||||
.expect(201);
|
||||
expect(res.body).toEqual({ result: 42 });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user