Files
liqa/server/test/browser.controller.spec.ts
T
ars9 e66e53c817 chore: apply code formatting and linting
- format long import statements with consistent line wrapping
- apply consistent indentation across client and server modules
- remove generated tsconfig.tsbuildinfo file
2026-04-14 22:54:03 +03:00

259 lines
8.4 KiB
TypeScript

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 { EnvironmentEntity } from "../src/environment/environment.entity";
import { CredentialEntity } from "../src/credential/credential.entity";
import { Repository } from "typeorm";
/**
* Browser controller integration tests.
*
* POST /open and POST /exec need a running Playwright browser. We test
* validation rejections and auto-create behavior for unknown named sessions,
* which are safe to run in a headless CI environment.
*/
describe("BrowserController", () => {
let app: INestApplication;
let sessionRepo: Repository<SessionEntity>;
let environmentRepo: Repository<EnvironmentEntity>;
let credentialRepo: Repository<CredentialEntity>;
const FAKE_SESSION = "test-browser-session";
beforeAll(async () => {
app = await buildTestApp();
sessionRepo = app.get<Repository<SessionEntity>>(
getRepositoryToken(SessionEntity),
);
environmentRepo = app.get<Repository<EnvironmentEntity>>(
getRepositoryToken(EnvironmentEntity),
);
credentialRepo = app.get<Repository<CredentialEntity>>(
getRepositoryToken(CredentialEntity),
);
// 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();
});
async function createEnvironment(
data: Record<string, string | undefined>,
name = `browser-env-${Math.random().toString(36).slice(2, 8)}`,
): Promise<string> {
const environment = await environmentRepo.save(
environmentRepo.create({
name,
data,
}),
);
return environment.id;
}
async function createCredential(
data: Record<string, unknown>,
name = `browser-cred-${Math.random().toString(36).slice(2, 8)}`,
): Promise<string> {
const credential = await credentialRepo.save(
credentialRepo.create({
name,
data: JSON.stringify(data),
lastUsedAt: null,
}),
);
return credential.id;
}
// ── 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("auto-creates missing named session and succeeds", async () => {
const res = await request(app.getHttpServer())
.post("/open")
.send({ sessionName: "no-such-session", url: "https://example.com" })
.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 () => {
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("auto-creates missing named session and succeeds", async () => {
const res = await request(app.getHttpServer())
.post("/exec")
.send({ sessionName: "no-such-session", code: "return 1;" })
.expect(201);
expect(res.body).toEqual({ result: 1 });
});
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 });
});
it("exposes environment via context.env in a named session", async () => {
const environmentId = await createEnvironment({
BASE_URL: "https://env.example.com",
});
const res = await request(app.getHttpServer())
.post("/exec")
.send({
sessionName: "no-such-session",
code: "return context.env.BASE_URL;",
environmentId,
})
.expect(201);
expect(res.body).toEqual({ result: "https://env.example.com" });
});
it("exposes environment via context.env in a sessionless exec", async () => {
const environmentId = await createEnvironment({ KEY: "value123" });
const res = await request(app.getHttpServer())
.post("/exec")
.send({
code: "return context.env.KEY;",
environmentId,
})
.expect(201);
expect(res.body).toEqual({ result: "value123" });
});
it("exposes credentials via context.getCredential in a named session", async () => {
const credentialId = await createCredential({
username: "user1",
password: "pass1",
});
const res = await request(app.getHttpServer())
.post("/exec")
.send({
sessionName: "no-such-session",
code: "return context.getCredential('admin');",
credentials: { admin: credentialId },
})
.expect(201);
expect(res.body).toEqual({
result: { username: "user1", password: "pass1" },
});
});
it("exposes credentials via context.getCredential in a sessionless exec", async () => {
const credentialId = await createCredential({ token: "abc" });
const res = await request(app.getHttpServer())
.post("/exec")
.send({
code: "return context.getCredential('svc');",
credentials: { svc: credentialId },
})
.expect(201);
expect(res.body).toEqual({ result: { token: "abc" } });
});
it("throws when getCredential is called with an unknown alias", async () => {
const res = await request(app.getHttpServer())
.post("/exec")
.send({
code: "return context.getCredential('missing');",
credentials: {},
})
.expect(500);
expect(res.body.message).toMatch(/missing/);
});
it("environment and credentials default to empty when omitted", async () => {
const res = await request(app.getHttpServer())
.post("/exec")
.send({
code: "return Object.keys(context.env).length === 0 ? 'empty' : 'not-empty';",
})
.expect(201);
expect(res.body).toEqual({ result: "empty" });
});
});
});