import { createServer } from 'node:net' import path from 'node:path' import { fileURLToPath } from 'node:url' import pg from 'pg' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import type { CaseState } from '../src/types.js' import { runMigrations } from './migrations.js' const { Client } = pg const baseDatabaseUrl = process.env.TEST_DATABASE_URL const suite = baseDatabaseUrl ? describe : describe.skip const databaseName = `osint_api_test_${process.pid}_${Date.now()}` let adminClient: InstanceType let appServer: Awaited['server'] let appPool: Awaited['pool'] let baseUrl = '' async function availablePort() { return new Promise((resolve, reject) => { const probe = createServer() probe.once('error', reject) probe.listen(0, '127.0.0.1', () => { const address = probe.address() const port = typeof address === 'object' && address ? address.port : 0 probe.close(error => error ? reject(error) : resolve(port)) }) }) } suite('level persistence API', () => { beforeAll(async () => { const adminUrl = new URL(baseDatabaseUrl!) adminUrl.pathname = '/postgres' 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 = await availablePort() process.env.DATABASE_URL = databaseUrl process.env.LEVEL_EDITING_ENABLED = 'true' process.env.PORT = String(port) const serverModule = await import('./index.js') appServer = serverModule.server appPool = serverModule.pool baseUrl = `http://127.0.0.1:${port}` }) afterAll(async () => { if (appServer) await new Promise((resolve, reject) => appServer.close(error => error ? reject(error) : resolve())) if (appPool) await appPool.end() if (!adminClient) return await adminClient.query('SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1', [databaseName]) await adminClient.query(`DROP DATABASE IF EXISTS "${databaseName}"`) await adminClient.end() }) it('persists authored viewport, exhibits, and relation positions', async () => { const createResponse = await fetch(`${baseUrl}/api/levels`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'api-smoke-level', title: 'API Smoke Level' }), }) expect(createResponse.status).toBe(201) const state = await createResponse.json() as CaseState state.viewport = { x: 91, y: -42, zoom: 0.85 } state.documents = [{ id: 'doc-1', title: 'Evidence', kind: 'IMAGE', date: '2021-04-17', publishedAt: '2021-04-17T12:00:00Z', body: [], regions: [], fileType: 'image', metadata: {} }] state.evidence = [{ id: 'folder-1', type: 'folder', title: 'Folder', content: 'Evidence folder', x: 685, y: 417, width: 260, config: { open: true }, containedDocumentIds: ['doc-1'] }] state.relations = [{ id: 'membership-1', fromWidgetId: 'folder-1', toWidgetId: 'doc-1', type: 'contains', sortOrder: 0, config: { x: 1051, y: 417 } }] const saveResponse = await fetch(`${baseUrl}/api/levels/${state.id}?edit=1`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(state), }) expect(await saveResponse.json()).toEqual({ ok: true, mode: 'author' }) const loaded = await (await fetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState expect(loaded.viewport).toEqual(state.viewport) expect(loaded.evidence[0]).toMatchObject({ id: 'folder-1', x: 685, y: 417, config: { open: true } }) expect(loaded.relations[0]).toMatchObject({ id: 'membership-1', config: { x: 1051, y: 417 } }) }) })