import { createServer } from 'node:net' import { createServer as createHttpServer, type Server as HttpServer } from 'node:http' import { randomUUID } from 'node:crypto' import { readFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import pg from 'pg' import jwt from 'jsonwebtoken' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import type { CaseState, DocumentExhibit, EventExhibit, FolderExhibit, NoteExhibit, PartyExhibit, TimelineView } 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 = '' let adminAuthorization = '' let judgeServer: HttpServer let judgeVerdict = { subject:'target',supports_claim:true,evidence_excerpt:'Ada Example patented a pocket telescope',confidence:.96 } let judgeHttpStatus = 200 function adminFetch(url: string, init: RequestInit = {}) { const headers = new Headers(init.headers) headers.set('authorization', adminAuthorization) return fetch(url, { ...init, headers }) } 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)) }) }) } const placed = (x: number, y: number, width: number, height: number, zIndex = 1) => ({ x, y, width, height, rotation: 0, zIndex, hidden: false }) suite('normalized 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() await runMigrations(databaseUrl, path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations'), () => undefined) const port = await availablePort() process.env.DATABASE_URL = databaseUrl process.env.LEVEL_EDITING_ENABLED = 'true' process.env.JWT_SECRET = 'osint-integration-jwt-secret' process.env.ASSET_STORAGE_DRIVER = 'memory' process.env.PORT = String(port) judgeServer = createHttpServer((_req, res) => { res.statusCode = judgeHttpStatus res.setHeader('content-type', 'application/json') res.end(JSON.stringify({ content: [{ type:'tool_use',name:'record_evidence_verdict',input:judgeVerdict }] })) }) await new Promise((resolve, reject) => judgeServer.listen(0, '127.0.0.1', resolve).once('error', reject)) const judgeAddress = judgeServer.address() process.env.EVIDENCE_JUDGE_PROVIDER = 'anthropic' process.env.EVIDENCE_JUDGE_MODEL = 'integration-haiku' process.env.ANTHROPIC_API_KEY = 'integration-key' process.env.ANTHROPIC_API_URL = `http://127.0.0.1:${typeof judgeAddress === 'object' && judgeAddress ? judgeAddress.port : 0}` const serverModule = await import('./index.js') appServer = serverModule.server appPool = serverModule.pool baseUrl = `http://127.0.0.1:${port}` adminAuthorization = `Bearer ${jwt.sign({ sub: 'integration-admin', role: 'admin' }, process.env.JWT_SECRET)}` }) afterAll(async () => { if (appServer) await new Promise((resolve, reject) => appServer.close(error => error ? reject(error) : resolve())) if (judgeServer) await new Promise((resolve, reject) => judgeServer.close(error => error ? reject(error) : resolve())) if (appPool) await appPool.end() if (!adminClient) return await adminClient.query(`DROP DATABASE IF EXISTS "${databaseName}"`) await adminClient.end() }) it('round-trips exhibits, relations, board views, private objects, and template clones', async () => { expect(await (await fetch(`${baseUrl}/api/session`)).json()).toEqual({ authenticated: false, isAdmin: false }) const createResponse = await adminFetch(`${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 const timeline = state.views.find((view): view is TimelineView => view.type === 'timeline')! timeline.rangeMode = 'fixed' timeline.range = { start: '2021-04-01', end: '2021-04-30' } state.viewport = { x: 91, y: -42, zoom: 0.85 } const document: DocumentExhibit = { id: randomUUID(), type: 'document', title: 'Evidence', publishedAt: '2021-04-17T12:00:00Z', body: ['Extracted body'], regions: [{ id: 'stamp', label: 'Date stamp', excerpt: '17 April 2021', date: '2021-04-17T09:00:00Z' }], fileType: 'image', metadata: {}, ...placed(1051, 417, 174, 145, 2) } const gatedDocument: DocumentExhibit = { id: randomUUID(), type: 'document', title: 'Later tip', body: [], regions: [], fileType: 'image', metadata: {}, requiredFlags: ['tip.received'], ...placed(1260, 417, 174, 145, 3) } const folder: FolderExhibit = { id: randomUUID(), type: 'folder', title: 'Folder', content: 'Evidence folder', isOpen: true, ...placed(685, 417, 260, 166) } const note: NoteExhibit = { id: randomUUID(), type: 'note', title: 'Extract', content: 'Date matters', ...placed(420, 300, 108, 154) } const event: EventExhibit = { id: randomUUID(), type: 'event', title: 'The meeting occurred', content: 'The evidence places the meeting on 18 April.', eventDate: '2021-04-18T14:30:00Z', ...placed(520, 610, 270, 174) } const party: PartyExhibit = { id: randomUUID(), type: 'party', partyKind: 'person', title: 'Ada Lovelace', content: 'Named as correspondent.', aliases: ['A. A. L.'], ...placed(720, 250, 280, 190) } state.exhibits = [document, gatedDocument, folder, note, event, party] state.relations = [ { id: randomUUID(), fromExhibitId: folder.id, toExhibitId: document.id, type: 'contains', sortOrder: 0 }, { id: randomUUID(), fromExhibitId: note.id, toExhibitId: document.id, type: 'source', sourceRegionId: 'stamp', sortOrder: 0 }, { id: randomUUID(), fromExhibitId: event.id, toExhibitId: document.id, type: 'supports', sortOrder: 0 }, { id: randomUUID(), fromExhibitId: event.id, toExhibitId: note.id, type: 'supports', sortOrder: 1 }, { id: randomUUID(), fromExhibitId: party.id, toExhibitId: document.id, type: 'concerns', sortOrder: 0 }, ] state.connections = [{ id: randomUUID(), fromExhibitId: folder.id, toExhibitId: document.id, label: 'Primary source', tightness: 85, tagStyle: 'compact', tagPosition: 72, tagOffset: -12 }] state.brief = { body: 'Identify Ada Lovelace.', concepts: [{ id: randomUUID(), label: 'Ada Lovelace', context: 'Named in evidence.', expectedPartyKind: 'person', resolvedPartyExhibitId: party.id }] } const save = await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(state) }) expect(await save.json()).toEqual({ ok: true, mode: 'author' }) const loaded = await (await adminFetch(`${baseUrl}/api/levels/${state.id}?edit=1`)).json() as CaseState expect(loaded.views[0]).toMatchObject({ type: 'timeline', rangeMode: 'fixed', range: timeline.range }) expect(loaded.exhibits.find(item => item.id === folder.id)).toMatchObject({ x: 685, y: 417, isOpen: true }) expect(loaded.relations).toEqual(expect.arrayContaining([expect.objectContaining({ type: 'supports', fromExhibitId: event.id, toExhibitId: note.id })])) expect(loaded.connections[0]).toMatchObject({ fromExhibitId: folder.id, toExhibitId: document.id, label: 'Primary source' }) expect(loaded.exhibits.find(item => item.id === gatedDocument.id)).toMatchObject({ requiredFlags: ['tip.received'] }) const beforeFlag = await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState expect(beforeFlag.exhibits.map(item => item.id)).not.toContain(gatedDocument.id) expect(beforeFlag.newlyVisibleDocumentIds).toContain(document.id) expect(await (await adminFetch(`${baseUrl}/api/levels/${state.id}/flags`)).json()).toEqual([ { key: 'tip.received', gatedDocumentCount: 1 }, ]) expect((await adminFetch(`${baseUrl}/api/levels/${state.id}/flags/tip.received`, { method: 'PUT' })).status).toBe(200) const afterFlag = await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState expect(afterFlag.exhibits.map(item => item.id)).toContain(gatedDocument.id) expect(afterFlag.newlyVisibleDocumentIds).toContain(gatedDocument.id) expect((await fetch(`${baseUrl}/api/levels/${state.id}/reveals/seen`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ documentIds: afterFlag.newlyVisibleDocumentIds }) })).status).toBe(200) expect((await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState).newlyVisibleDocumentIds).toEqual([]) expect((await adminFetch(`${baseUrl}/api/levels/${state.id}/flags/tip.received`, { method: 'DELETE' })).status).toBe(200) const matchRuleResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/evidence-match-rules`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Smoke source passage', sourceLabel: 'Archive smoke test', sourceUri: 'https://example.test/archive/smoke', flagKey: 'tip.received', minimumAnchorMatches: 1, anchors: [{ phrase: 'OSINT smoke evidence from the archive', minimumSimilarity: 0.72 }], }), }) expect(matchRuleResponse.status).toBe(201) expect(await matchRuleResponse.json()).toMatchObject({ name: 'Smoke source passage', sourceLabel: 'Archive smoke test', sourceUri: 'https://example.test/archive/smoke', flagKey: 'tip.received', anchors: [{ phrase: 'OSINT smoke evidence from the archive' }] }) expect(await (await adminFetch(`${baseUrl}/api/levels/${state.id}/evidence-match-rules`)).json()).toHaveLength(1) const goalResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/goals`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ key: 'smoke.prove-source', title: 'Prove the source', instructions: 'Paste a matching archival source.', completionMessage: 'Source verified.', requiredFlags: ['tip.received'], }), }) expect(goalResponse.status).toBe(201) expect(await goalResponse.json()).toMatchObject({ key: 'smoke.prove-source', status: 'pending', requiredFlags: ['tip.received'] }) const playGoalBefore = (await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState).goals[0] expect(playGoalBefore).toMatchObject({ key: 'smoke.prove-source', status: 'pending', newlyCompleted: false }) expect(playGoalBefore).not.toHaveProperty('id') expect(playGoalBefore).not.toHaveProperty('requiredFlags') const semanticGoalResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/goals`, { method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({ key:'smoke.semantic-proof',title:'Prove the semantic claim', instructions:'Upload another credible source.',completionMessage:'Claim verified.',requiredFlags:['semantic.proved'] }), }) expect(semanticGoalResponse.status).toBe(201) const semanticGoal = await semanticGoalResponse.json() as { id:string } const semanticRuleResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/evidence-semantic-rules`, { method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({ goalId:semanticGoal.id,name:'Ada inventor claim', targetSubject:'Ada Example',relatedSubject:'Ada Example Senior',assertion:'Ada Example was an inventor',successFlagKey:'semantic.proved', relatedFlagKey:'semantic.father',minimumConfidence:.85 }), }) expect(semanticRuleResponse.status).toBe(201) const upload = new FormData() upload.append('file', new Blob(['OSINT smoke evidence from the archlve'], { type: 'text/plain' }), 'smoke-evidence.txt') upload.append('x', '812') upload.append('y', '438') const uploadResponse = await fetch(`${baseUrl}/api/levels/${state.id}/documents`, { method: 'POST', body: upload }) expect(uploadResponse.status).toBe(201) const uploaded = await uploadResponse.json() as DocumentExhibit & { analysis: { extractionStatus: string; matchedFlags: string[]; awardedFlags: string[]; goals: CaseState['goals'] } } expect(uploaded).toMatchObject({ type: 'document', fileName: 'smoke-evidence.txt', fileType: 'text', x: 812, y: 438, body: ['OSINT smoke evidence from the archlve'], analysis: { extractionStatus: 'succeeded', matchedFlags: ['tip.received'], awardedFlags: ['tip.received'], goals: expect.arrayContaining([expect.objectContaining({ key: 'smoke.prove-source', status: 'complete', newlyCompleted: true })]) } }) expect(await (await fetch(`${baseUrl}/api/assets/${uploaded.assetId}`)).text()).toBe('OSINT smoke evidence from the archlve') const assetRow = await appPool.query<{ storage_provider: string; content: Buffer | null; object_key: string | null }>('SELECT storage_provider,content,object_key FROM osint.assets WHERE id=$1', [uploaded.assetId]) expect(assetRow.rows[0]).toMatchObject({ storage_provider: 's3', content: null, object_key: expect.stringMatching(/^assets\//) }) const automaticallyRevealed = await (await fetch(`${baseUrl}/api/levels/${state.id}`)).json() as CaseState expect(automaticallyRevealed.exhibits.map(item => item.id)).toContain(gatedDocument.id) expect(automaticallyRevealed.goals).toEqual(expect.arrayContaining([ expect.objectContaining({ key: 'smoke.prove-source', status: 'complete', newlyCompleted: false }), ])) const evaluationRows = await appPool.query<{ matched: boolean; matched_anchor_count: number }>( 'SELECT matched,matched_anchor_count FROM osint.evidence_match_evaluations WHERE document_exhibit_id=$1', [uploaded.id]) expect(evaluationRows.rows).toEqual([{ matched: true, matched_anchor_count: 1 }]) const semanticUpload = new FormData() semanticUpload.append('file', new Blob(['Archive entry: Ada Example patented a pocket telescope in 1948.'], { type:'text/plain' }), 'semantic-evidence.txt') const semanticDocumentResponse = await fetch(`${baseUrl}/api/levels/${state.id}/documents`, { method:'POST',body:semanticUpload }) expect(semanticDocumentResponse.status).toBe(201) const semanticDocument = await semanticDocumentResponse.json() as DocumentExhibit & { analysis:{ goals:CaseState['goals'] } } expect(semanticDocument.analysis.goals.find(goal => goal.key === 'smoke.semantic-proof')?.status).toBe('pending') const judgedResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/documents/${semanticDocument.id}/judge`, { method:'POST' }) expect(judgedResponse.status).toBe(200) expect(await judgedResponse.json()).toMatchObject({ status:'succeeded',subject:'target',supportsClaim:true,confidence:.96, awardedFlags:['semantic.proved'],goals:expect.arrayContaining([expect.objectContaining({ key:'smoke.semantic-proof',status:'complete',newlyCompleted:true })]) }) const semanticProvenance = await appPool.query<{ flag_key:string; awarded_by_semantic_evaluation_id:string | null }>( 'SELECT flag_key,awarded_by_semantic_evaluation_id FROM osint.level_flags WHERE level_id=(SELECT id FROM osint.levels WHERE slug=$1) AND flag_key=$2', [state.id,'semantic.proved']) expect(semanticProvenance.rows[0]).toMatchObject({ flag_key:'semantic.proved',awarded_by_semantic_evaluation_id:expect.any(String) }) expect(await (await adminFetch(`${baseUrl}/api/levels/${state.id}/documents/${semanticDocument.id}/judge`, { method:'POST' })).json()) .toMatchObject({ status:'not_needed',awardedFlags:[] }) const screenshot = new FormData() screenshot.append('file', new Blob([Buffer.from('89504e470d0a1a0a', 'hex')], { type: 'image/png' }), 'Screenshot 2026-08-22.png') const screenshotResponse = await fetch(`${baseUrl}/api/levels/${state.id}/documents`, { method: 'POST', body: screenshot }) expect(screenshotResponse.status).toBe(201) expect(await screenshotResponse.json()).toMatchObject({ type: 'document', fileType: 'image', fileName: 'Screenshot 2026-08-22.png' }) const templateResponse = await adminFetch(`${baseUrl}/api/levels/${state.id}/templates?edit=1`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Smoke Template' }) }) expect(templateResponse.status).toBe(201) const cloneResponse = await adminFetch(`${baseUrl}/api/templates/smoke-template/levels?edit=1`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 'smoke-template-copy', title: 'Playable copy' }) }) expect(cloneResponse.status).toBe(201) const clone = await cloneResponse.json() as CaseState expect(clone.views[0]).toMatchObject({ type: 'timeline', rangeMode: 'fixed', range: timeline.range }) expect(clone.exhibits.map(item => item.id)).not.toContain(folder.id) expect(clone.exhibits.find(item => item.type === 'folder')).toMatchObject({ x: 685, y: 417 }) expect(clone.relations.filter(relation => relation.type === 'supports')).toHaveLength(2) expect(clone.connections[0]).toMatchObject({ label: 'Primary source', tightness: 85 }) expect(clone.brief.concepts[0].resolvedPartyExhibitId).not.toBe(party.id) const authoredClone = await (await adminFetch(`${baseUrl}/api/levels/${clone.id}?edit=1`)).json() as CaseState expect(authoredClone.exhibits.find(item => item.type === 'document' && item.title === 'Later tip')).toMatchObject({ requiredFlags: ['tip.received'] }) expect(await (await adminFetch(`${baseUrl}/api/levels/${clone.id}/evidence-match-rules`)).json()).toEqual([ expect.objectContaining({ name: 'Smoke source passage', sourceLabel: 'Archive smoke test', sourceUri: 'https://example.test/archive/smoke', flagKey: 'tip.received', anchors: [expect.objectContaining({ phrase: 'OSINT smoke evidence from the archive' })] }), ]) expect(await (await adminFetch(`${baseUrl}/api/levels/${clone.id}/goals`)).json()).toEqual(expect.arrayContaining([ expect.objectContaining({ key: 'smoke.prove-source', status: 'pending', requiredFlags: ['tip.received'] }), expect.objectContaining({ key: 'smoke.semantic-proof', status: 'pending', requiredFlags: ['semantic.proved'] }), ])) expect(await (await adminFetch(`${baseUrl}/api/levels/${clone.id}/evidence-semantic-rules`)).json()).toEqual([ expect.objectContaining({ name:'Ada inventor claim',goalKey:'smoke.semantic-proof',successFlagKey:'semantic.proved' }), ]) }) it('imports the data-defined Scene 7 template with its private recognition rules', async () => { const { importMysteryTemplate } = await import('../scripts/importMysteryTemplate.js') const manifestPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'mysteries', 'barricelli-scene-7', 'mystery.json') const imported = await importMysteryTemplate(manifestPath, baseUrl, adminAuthorization.replace(/^Bearer /, '')) expect(imported.playableLevel).toMatchObject({ title:'The Barricelli Files',exhibits:[],goals:[expect.objectContaining({ key:'barricelli.inventor-proof',status:'pending',newlyCompleted:false, })] }) expect(await (await adminFetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/evidence-match-rules`)).json()).toEqual([ expect.objectContaining({ name:'Google Patents ยท GB695913A',sourceUri:'https://patents.google.com/patent/GB695913A/en', flagKey:'scene7.nils_inventor_proved',minimumAnchorMatches:2 }), ]) expect(await (await adminFetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/evidence-semantic-rules`)).json()).toEqual([ expect.objectContaining({ goalKey:'barricelli.inventor-proof',targetSubject:'Nils Aall Barricelli', relatedFlagKey:'scene7.father_inventor_discovered' }), ]) const fixtureDir = path.join(path.dirname(manifestPath), 'fixtures') const fatherUpload = new FormData() fatherUpload.append('file', new Blob([readFileSync(path.join(fixtureDir, 'father-only-negative-ocr.txt'))], { type:'text/plain' }), 'father-source.txt') const fatherDocument = await (await fetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/documents`, { method:'POST',body:fatherUpload })).json() as DocumentExhibit & { analysis:{ goals:CaseState['goals'] } } expect(fatherDocument.analysis.goals[0].status).toBe('pending') judgeVerdict = { subject:'related',supports_claim:true,evidence_excerpt:'den italienske maler og opfinder Barricelli',confidence:.96 } judgeHttpStatus = 429 expect(await (await adminFetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/documents/${fatherDocument.id}/judge`, { method:'POST' })).json()) .toMatchObject({ status:'failed',retryable:true,awardedFlags:[],goals:[expect.objectContaining({ status:'pending' })] }) judgeHttpStatus = 200 const fatherJudgment = await (await adminFetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/documents/${fatherDocument.id}/judge`, { method:'POST' })).json() expect(fatherJudgment).toMatchObject({ status:'succeeded',subject:'related',awardedFlags:['scene7.father_inventor_discovered'], goals:[expect.objectContaining({ key:'barricelli.inventor-proof',status:'pending' })] }) const targetUpload = new FormData() targetUpload.append('file', new Blob([readFileSync(path.join(fixtureDir, 'google-patents-target-ocr.txt'))], { type:'text/plain' }), 'google-patents-source.txt') const targetResponse = await fetch(`${baseUrl}/api/levels/${imported.playableLevel.id}/documents`, { method:'POST',body:targetUpload }) expect(targetResponse.status).toBe(201) expect(await targetResponse.json()).toMatchObject({ analysis:{ awardedFlags:['scene7.nils_inventor_proved'], goals:[expect.objectContaining({ key:'barricelli.inventor-proof',status:'complete',newlyCompleted:true })] } }) judgeVerdict = { subject:'target',supports_claim:true,evidence_excerpt:'Ada Example patented a pocket telescope',confidence:.96 } judgeHttpStatus = 200 }) })