// Content routes integration tests with mock BrowserManager / 内容路由集成测试,使用模拟浏览器管理器 import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import http from 'node:http'; import { startServer } from '../server.js'; import type { PageInfo } from '@visionl/core'; function createMockBM() { const pages = new Map(); const aliases = new Map(); const mockPage = { async screenshot(_opts: any): Promise { return Buffer.from('fake-png-data'); }, async evaluate(fn: any): Promise { const src = fn.toString(); if (src.includes('body.innerText')) return 'Hello World'; if (src.includes('documentElement.outerHTML')) return 'Hello World'; return null; }, }; const mock = { async createPage(pageUrl: string, alias?: string): Promise { if (alias && aliases.has(alias)) { throw Object.assign(new Error(`Alias "${alias}" already exists`), { code: 'ALIAS_EXISTS' }); } try { new URL(pageUrl); } catch { throw Object.assign(new Error(`Invalid URL: ${pageUrl}`), { code: 'INVALID_URL' }); } const id = 'p_' + Math.random().toString(16).slice(2, 10); const info: PageInfo = { id, url: pageUrl, alias, title: 'Test Page', status: 'active', profile: 'fp_test' }; pages.set(id, info); if (alias) aliases.set(alias, id); return info; }, getPage(idOrAlias: string) { const id = aliases.get(idOrAlias) || idOrAlias; const info = pages.get(id); if (!info) return undefined; return { info, page: mockPage, context: null }; }, listPages(): PageInfo[] { return Array.from(pages.values()); }, async closePage(idOrAlias: string): Promise { const id = aliases.get(idOrAlias) || idOrAlias; if (!pages.has(id)) { throw Object.assign(new Error(`Page "${idOrAlias}" not found`), { code: 'PAGE_NOT_FOUND' }); } const info = pages.get(id)!; if (info.alias) aliases.delete(info.alias); pages.delete(id); }, }; return { mock, pages, aliases, mockPage }; } describe('content routes', () => { let server: http.Server; let baseUrl: string; const port = 19530; const { mock } = createMockBM(); beforeAll(async () => { server = await startServer(port, mock as any); baseUrl = `http://127.0.0.1:${port}`; }); afterAll(async () => { await new Promise((resolve) => server.close(() => resolve())); }); let mockPages: Map; let mockAliases: Map; function resetMock() { mockPages = new Map(); mockAliases = new Map(); mock.createPage = async function (pageUrl: string, alias?: string): Promise { if (alias && mockAliases.has(alias)) { throw Object.assign(new Error(`Alias "${alias}" already exists`), { code: 'ALIAS_EXISTS' }); } try { new URL(pageUrl); } catch { throw Object.assign(new Error(`Invalid URL: ${pageUrl}`), { code: 'INVALID_URL' }); } const id = 'p_' + Math.random().toString(16).slice(2, 10); const info: PageInfo = { id, url: pageUrl, alias, title: 'Test Page', status: 'active', profile: 'fp_test' }; mockPages.set(id, info); if (alias) mockAliases.set(alias, id); return info; }; mock.getPage = function (idOrAlias: string) { const id = mockAliases.get(idOrAlias) || idOrAlias; const info = mockPages.get(id); if (!info) return undefined; return { info, page: { async screenshot(_opts: any): Promise { return Buffer.from('fake-png-data'); }, async evaluate(fn: any): Promise { const src = fn.toString(); if (src.includes('body.innerText')) return 'Hello World'; if (src.includes('documentElement.outerHTML')) return 'Hello World'; return null; }, }, context: null, }; }; mock.listPages = function (): PageInfo[] { return Array.from(mockPages.values()); }; mock.closePage = async function (idOrAlias: string): Promise { const id = mockAliases.get(idOrAlias) || idOrAlias; if (!mockPages.has(id)) { throw Object.assign(new Error(`Page "${idOrAlias}" not found`), { code: 'PAGE_NOT_FOUND' }); } const info = mockPages.get(id)!; if (info.alias) mockAliases.delete(info.alias); mockPages.delete(id); }; } beforeEach(() => { resetMock(); }); it('GET /pages/:id/screenshot returns base64 png', async () => { const createRes = await fetch(`${baseUrl}/pages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }), }); const { data: created } = await createRes.json(); const res = await fetch(`${baseUrl}/pages/${created.id}/screenshot`); expect(res.status).toBe(200); const json = await res.json(); expect(json.ok).toBe(true); expect(json.data.base64).toBe('ZmFrZS1wbmctZGF0YQ=='); // base64 of 'fake-png-data' expect(json.data.mime).toBe('image/png'); }); it('GET /pages/:id/text returns body text', async () => { const createRes = await fetch(`${baseUrl}/pages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }), }); const { data: created } = await createRes.json(); const res = await fetch(`${baseUrl}/pages/${created.id}/text`); expect(res.status).toBe(200); const json = await res.json(); expect(json.ok).toBe(true); expect(json.data.text).toBe('Hello World'); }); it('GET /pages/:id/html returns full HTML', async () => { const createRes = await fetch(`${baseUrl}/pages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }), }); const { data: created } = await createRes.json(); const res = await fetch(`${baseUrl}/pages/${created.id}/html`); expect(res.status).toBe(200); const json = await res.json(); expect(json.ok).toBe(true); expect(json.data.html).toBe('Hello World'); }); it('GET /pages/:id/screenshot returns 404 for unknown page', async () => { const res = await fetch(`${baseUrl}/pages/nonexistent/screenshot`); expect(res.status).toBe(404); const json = await res.json(); expect(json.ok).toBe(false); expect(json.error.code).toBe('PAGE_NOT_FOUND'); }); it('GET /pages/:id/text returns 404 for unknown page', async () => { const res = await fetch(`${baseUrl}/pages/nonexistent/text`); expect(res.status).toBe(404); const json = await res.json(); expect(json.error.code).toBe('PAGE_NOT_FOUND'); }); it('GET /pages/:id/html returns 404 for unknown page', async () => { const res = await fetch(`${baseUrl}/pages/nonexistent/html`); expect(res.status).toBe(404); const json = await res.json(); expect(json.error.code).toBe('PAGE_NOT_FOUND'); }); it('screenshot accessible by alias', async () => { await fetch(`${baseUrl}/pages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com', alias: 'home' }), }); const res = await fetch(`${baseUrl}/pages/home/screenshot`); expect(res.status).toBe(200); const json = await res.json(); expect(json.ok).toBe(true); expect(json.data.base64).toBe('ZmFrZS1wbmctZGF0YQ=='); }); it('unknown content action returns false (falls through to 404)', async () => { const createRes = await fetch(`${baseUrl}/pages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://example.com' }), }); const { data: created } = await createRes.json(); const res = await fetch(`${baseUrl}/pages/${created.id}/unknown`); expect(res.status).toBe(404); const json = await res.json(); expect(json.ok).toBe(false); }); });