import { once } from 'node:events' import path from 'node:path' import { fileURLToPath } from 'node:url' import pg from 'pg' import type { CaseState } from '../src/types.js' import { runMigrations } from './migrations.js' const { Client } = pg const baseDatabaseUrl = process.env.TEST_DATABASE_URL if (!baseDatabaseUrl) throw new Error('TEST_DATABASE_URL is required for the browser harness') const databaseName = `osint_e2e_${process.pid}_${Date.now()}` const adminUrl = new URL(baseDatabaseUrl) adminUrl.pathname = '/postgres' const adminClient = new Client({ connectionString: adminUrl.toString() }) await adminClient.connect() await adminClient.query(`CREATE DATABASE "${databaseName}"`) const testUrl = new URL(baseDatabaseUrl) testUrl.pathname = `/${databaseName}` const databaseUrl = testUrl.toString() const migrationsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations') await runMigrations(databaseUrl, migrationsDir, () => undefined) const port = Number(process.env.E2E_PORT || 18788) process.env.DATABASE_URL = databaseUrl process.env.LEVEL_EDITING_ENABLED = 'true' process.env.PORT = String(port) process.env.OSINT_MANAGED_SERVER = 'true' const { server, pool } = await import('./index.js') if (!server.listening) await once(server, 'listening') const baseUrl = `http://127.0.0.1:${port}` const documentId = '22222222-2222-4222-8222-222222222222' const folderId = '11111111-1111-4111-8111-111111111111' const created = await fetch(`${baseUrl}/api/levels`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'e2e-level', title: 'Browser Safety Test', subtitle: 'Disposable test level' }), }) if (!created.ok) throw new Error(`Could not create browser test level: ${created.status}`) const state = await created.json() as CaseState state.brief = { body: 'Classify the named people and organizations in this investigation.', concepts: [ { id: '44444444-4444-4444-8444-444444444444', label: 'Ada Lovelace', context: 'Named as the correspondent.', expectedPartyKind: 'person' }, { id: '55555555-5555-4555-8555-555555555555', label: 'Difference Engine Bureau', context: 'Issued the archive notice.', expectedPartyKind: 'organization' }, ] } state.documents = [{ id: documentId, title: 'Dated source image', kind: 'IMAGE', date: '2021-04-17', publishedAt: '2021-04-17T12:00:00.000Z', body: [], regions: [], fileType: 'image', metadata: {}, }] state.evidence = [{ id: folderId, type: 'folder', title: 'BROWSER TEST FOLDER', content: 'Disposable evidence', x: 600, y: 360, width: 260, config: { open: false }, containedDocumentIds: [documentId], }] state.relations = [{ id: `contains:${folderId}:${documentId}`, fromWidgetId: folderId, toWidgetId: documentId, type: 'contains', sortOrder: 0, config: { x: 980, y: 360 }, }] state.connections = [] state.viewport = { x: 0, y: 28, zoom: 0.7 } const saved = await fetch(`${baseUrl}/api/levels/${state.id}?edit=1`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(state), }) if (!saved.ok) throw new Error(`Could not seed browser test level: ${saved.status}`) let shuttingDown = false async function shutdown(exitCode: number) { if (shuttingDown) return shuttingDown = true if (server.listening) await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())) await pool.end() await adminClient.query(`DROP DATABASE IF EXISTS "${databaseName}"`) await adminClient.end() process.exit(exitCode) } process.on('SIGTERM', () => void shutdown(0)) process.on('SIGINT', () => void shutdown(0)) process.on('uncaughtException', error => { console.error(error); void shutdown(1) }) process.on('unhandledRejection', error => { console.error(error); void shutdown(1) }) console.log(`Browser safety harness ready on ${baseUrl}`)