2026-08-14 12:57:06 +02:00
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import pg from 'pg'
import { afterAll , beforeAll , describe , expect , it } from 'vitest'
import { runMigrations } from './migrations.js'
const { Client } = pg
const baseDatabaseUrl = process . env . TEST_DATABASE_URL
const suite = baseDatabaseUrl ? describe : describe.skip
const databaseName = `osint_test_ ${ process . pid } _ ${ Date . now () } `
let adminClient : InstanceType < typeof Client >
let testDatabaseUrl = ''
suite ( 'PostgreSQL migrations' , () => {
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 } `
testDatabaseUrl = testUrl . toString ()
})
afterAll ( async () => {
if ( ! adminClient ) return
await adminClient . query ( `DROP DATABASE IF EXISTS " ${ databaseName } "` )
await adminClient . end ()
})
it ( 'applies every migration transactionally and is idempotent' , async () => {
const migrationsDir = path . resolve ( path . dirname ( fileURLToPath ( import . meta . url )), '..' , 'migrations' )
const firstRun : string [] = []
await runMigrations ( testDatabaseUrl , migrationsDir , message => firstRun . push ( message ))
2026-08-18 16:28:11 +02:00
expect ( firstRun . filter ( message => message . startsWith ( 'apply ' ))). toHaveLength ( 22 )
2026-08-14 12:57:06 +02:00
const client = new Client ({ connectionString : testDatabaseUrl })
await client . connect ()
const tables = await client . query < { table_name : string } > ( `SELECT table_name FROM information_schema.tables WHERE table_schema = 'osint'` )
const tableNames = tables . rows . map ( row => row . table_name )
2026-08-14 14:09:24 +02:00
expect ( tableNames ). toEqual ( expect . arrayContaining ([
'boards' , 'levels' , 'level_templates' , 'level_template_versions' , 'exhibits' , 'folder_exhibits' ,
'document_exhibits' , 'folder_memberships' , 'exhibit_connections' , 'metadata_fields' , 'assets' , 'schema_migrations' ,
2026-08-14 14:44:58 +02:00
'party_exhibits' , 'person_parties' , 'organization_parties' , 'brief_concepts' , 'level_briefs' ,
2026-08-18 15:37:46 +02:00
'board_views' , 'timeline_views' ,
'mysteries' , 'npcs' , 'npc_poses' , 'playthroughs' ,
'story_nodes' , 'story_node_terminals' , 'utterances' ,
2026-08-14 14:09:24 +02:00
]))
2026-08-18 15:37:46 +02:00
expect ( tableNames ). not . toEqual ( expect . arrayContaining ([ 'cases' , 'widgets' , 'widget_relations' , 'cutscenes' , 'dialogue_steps' , 'mystery_chapters' , 'seen_dialogue' ]))
2026-08-14 12:57:06 +02:00
const ledger = await client . query < { count : string } > ( 'SELECT COUNT(*)::text AS count FROM osint.schema_migrations' )
2026-08-18 16:28:11 +02:00
expect ( ledger . rows [ 0 ]. count ). toBe ( '22' )
2026-08-14 16:37:50 +02:00
const connectionColumns = await client . query < { column_name : string } > ( `SELECT column_name FROM information_schema.columns WHERE table_schema='osint' AND table_name='exhibit_connections'` )
2026-08-14 18:26:20 +02:00
expect ( connectionColumns . rows . map ( row => row . column_name )). toEqual ( expect . arrayContaining ([ 'label' , 'tightness' , 'tag_style' , 'tag_position_percent' , 'tag_lateral_offset' ]))
2026-08-14 19:57:22 +02:00
const eventOccurrence = await client . query < { is_nullable : string } > ( `SELECT is_nullable FROM information_schema.columns WHERE table_schema='osint' AND table_name='event_exhibits' AND column_name='occurred_at'` )
expect ( eventOccurrence . rows [ 0 ]. is_nullable ). toBe ( 'YES' )
2026-08-14 12:57:06 +02:00
await client . end ()
const secondRun : string [] = []
await runMigrations ( testDatabaseUrl , migrationsDir , message => secondRun . push ( message ))
2026-08-18 16:28:11 +02:00
expect ( secondRun . filter ( message => message . startsWith ( 'skip ' ))). toHaveLength ( 22 )
2026-08-14 12:57:06 +02:00
expect ( secondRun . some ( message => message . startsWith ( 'apply ' ))). toBe ( false )
})
})