- sessionName is now optional in open/exec; skips session restore when omitted - add selector param to open: returns outerHTML or textContent of matched element - extract session restore logic into private setupSession() to remove duplication - replace per-exception instanceof checks with single HttpException base class check - embed label into InternalServerErrorException message instead of logging separately - update MCP tool schemas and HTTP DTOs to reflect optional sessionName and new selector - add integration tests: sessionless open/exec, selector, selector+readerMode
128 lines
4.3 KiB
TypeScript
128 lines
4.3 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 { 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 });
|
|
});
|
|
});
|
|
});
|