@@ -17,17 +17,16 @@ const name = `project_repair_test_${generateId().replaceAll('-', '')}`
1717const url = new URL ( readTestDatabaseUrl ( ) )
1818url . pathname = `/${ name } `
1919const client = postgres ( url . toString ( ) , { max : 1 , onnotice : ( ) => { } } )
20- const directory = await mkdtemp ( join ( tmpdir ( ) , 'project-repair-test-' ) )
21- const manifest = join ( directory , 'manifest.json' )
22- const report = join ( directory , 'report.json' )
20+ let directory : string
21+ let manifest : string
22+ let report : string
2323const redisUrl = readTestRedisUrl ( )
24- if ( ! redisUrl ) throw new Error ( 'Archive repair integration requires TEST_REDIS_URL' )
25- const cache = new Redis ( redisUrl )
26- const subscriber = new Redis ( redisUrl )
24+ const describeWithRedis = describe . runIf ( Boolean ( redisUrl ) )
25+ let cache : Redis
26+ let subscriber : Redis
2727const mcpServerId = generateId ( )
2828const cacheKey = `mcp:tools:workspace:env:server:${ mcpServerId } `
2929const published : unknown [ ] = [ ]
30- subscriber . on ( 'message' , ( _channel , message ) => published . push ( JSON . parse ( message ) ) )
3130const releaseNotifications = createDeferred < void > ( )
3231let tailNotified = false
3332const realtime = createServer ( async ( request , response ) => {
@@ -64,63 +63,69 @@ const run = (command: string, artifacts = { manifest, report }) =>
6463 }
6564 )
6665
67- beforeAll ( async ( ) => {
68- await new Promise < void > ( ( resolve ) => realtime . listen ( 0 , '127.0.0.1' , resolve ) )
69- const address = realtime . address ( )
70- if ( ! address || typeof address === 'string' ) throw new Error ( 'Realtime fixture failed to bind' )
71- environment . SOCKET_SERVER_URL = `http://127.0.0.1:${ address . port } `
72- await subscriber . subscribe ( 'mcp:workflow_tools_changed' )
73- await admin . unsafe ( `CREATE DATABASE "${ name } "` )
74- await client . unsafe (
75- 'CREATE EXTENSION vector; CREATE EXTENSION btree_gin; CREATE EXTENSION pg_trgm'
76- )
77- const folder = join ( directory , 'migrations' )
78- await mkdir ( join ( folder , 'meta' ) , { recursive : true } )
79- const expansion = journal . entries . find ( ( entry ) => entry . tag === '0404_workspace_project_column' )
80- if ( ! expansion ) throw new Error ( 'Missing expansion migration' )
81- const entries = journal . entries . filter ( ( entry ) => entry . when <= expansion . when )
82- const source = new URL ( '../../../../../packages/db/migrations/' , import . meta. url )
83- for ( const entry of entries )
84- await copyFile ( new URL ( `${ entry . tag } .sql` , source ) , join ( folder , `${ entry . tag } .sql` ) )
85- await writeFile ( join ( folder , 'meta/_journal.json' ) , JSON . stringify ( { ...journal , entries } ) )
86- await execute (
87- 'bun' ,
88- [
89- '--no-env-file' ,
90- '-e' ,
91- `
92- import postgres from 'postgres';
93- import { drizzle } from 'drizzle-orm/postgres-js';
94- import { migrate } from 'drizzle-orm/postgres-js/migrator';
95- import { runScriptMigrations, scriptMigrations } from '@sim/db/script-migrations/index';
96- const sql = postgres(process.env.MIGRATION_DATABASE_URL,{max:1,onnotice:()=>{}});
97- try { await migrate(drizzle(sql),{migrationsFolder:process.env.FIXTURE_MIGRATIONS}); await runScriptMigrations(sql, scriptMigrations.filter((item) => item.name !== '0031_project_membership')); }
98- finally { await sql.end(); }
99- ` ,
100- ] ,
101- {
102- cwd : new URL ( '../../../../../packages/db/' , import . meta. url ) ,
103- env : { ...environment , FIXTURE_MIGRATIONS : folder } ,
104- timeout : 120000 ,
105- }
106- )
107- } , 120000 )
66+ describeWithRedis ( 'Operator archive repair against the full compatible schema' , ( ) => {
67+ beforeAll ( async ( ) => {
68+ directory = await mkdtemp ( join ( tmpdir ( ) , 'project-repair-test-' ) )
69+ manifest = join ( directory , 'manifest.json' )
70+ report = join ( directory , 'report.json' )
71+ cache = new Redis ( redisUrl ! )
72+ subscriber = new Redis ( redisUrl ! )
73+ subscriber . on ( 'message' , ( _channel , message ) => published . push ( JSON . parse ( message ) ) )
74+ await new Promise < void > ( ( resolve ) => realtime . listen ( 0 , '127.0.0.1' , resolve ) )
75+ const address = realtime . address ( )
76+ if ( ! address || typeof address === 'string' ) throw new Error ( 'Realtime fixture failed to bind' )
77+ environment . SOCKET_SERVER_URL = `http://127.0.0.1:${ address . port } `
78+ await subscriber . subscribe ( 'mcp:workflow_tools_changed' )
79+ await admin . unsafe ( `CREATE DATABASE "${ name } "` )
80+ await client . unsafe (
81+ 'CREATE EXTENSION vector; CREATE EXTENSION btree_gin; CREATE EXTENSION pg_trgm'
82+ )
83+ const folder = join ( directory , 'migrations' )
84+ await mkdir ( join ( folder , 'meta' ) , { recursive : true } )
85+ const expansion = journal . entries . find ( ( entry ) => entry . tag === '0404_workspace_project_column' )
86+ if ( ! expansion ) throw new Error ( 'Missing expansion migration' )
87+ const entries = journal . entries . filter ( ( entry ) => entry . when <= expansion . when )
88+ const source = new URL ( '../../../../../packages/db/migrations/' , import . meta. url )
89+ for ( const entry of entries )
90+ await copyFile ( new URL ( `${ entry . tag } .sql` , source ) , join ( folder , `${ entry . tag } .sql` ) )
91+ await writeFile ( join ( folder , 'meta/_journal.json' ) , JSON . stringify ( { ...journal , entries } ) )
92+ await execute (
93+ 'bun' ,
94+ [
95+ '--no-env-file' ,
96+ '-e' ,
97+ `
98+ import postgres from 'postgres';
99+ import { drizzle } from 'drizzle-orm/postgres-js';
100+ import { migrate } from 'drizzle-orm/postgres-js/migrator';
101+ import { runScriptMigrations, scriptMigrations } from '@sim/db/script-migrations/index';
102+ const sql = postgres(process.env.MIGRATION_DATABASE_URL,{max:1,onnotice:()=>{}});
103+ try { await migrate(drizzle(sql),{migrationsFolder:process.env.FIXTURE_MIGRATIONS}); await runScriptMigrations(sql, scriptMigrations.filter((item) => item.name !== '0031_project_membership')); }
104+ finally { await sql.end(); }
105+ ` ,
106+ ] ,
107+ {
108+ cwd : new URL ( '../../../../../packages/db/' , import . meta. url ) ,
109+ env : { ...environment , FIXTURE_MIGRATIONS : folder } ,
110+ timeout : 120000 ,
111+ }
112+ )
113+ } , 120000 )
108114
109- afterAll ( async ( ) => {
110- releaseNotifications . resolve ( )
111- await new Promise < void > ( ( resolve , reject ) => {
112- realtime . close ( ( error ) => ( error ? reject ( error ) : resolve ( ) ) )
113- realtime . closeAllConnections ( )
115+ afterAll ( async ( ) => {
116+ releaseNotifications . resolve ( )
117+ await new Promise < void > ( ( resolve , reject ) => {
118+ realtime . close ( ( error ) => ( error ? reject ( error ) : resolve ( ) ) )
119+ realtime . closeAllConnections ( )
120+ } )
121+ await cache . del ( cacheKey )
122+ await Promise . all ( [ cache . quit ( ) , subscriber . quit ( ) ] )
123+ await client . end ( { timeout : 2 } )
124+ await admin . unsafe ( `DROP DATABASE IF EXISTS "${ name } " WITH (FORCE)` )
125+ await admin . end ( )
126+ await rm ( directory , { recursive : true , force : true } )
114127 } )
115- await cache . del ( cacheKey )
116- await Promise . all ( [ cache . quit ( ) , subscriber . quit ( ) ] )
117- await client . end ( { timeout : 2 } )
118- await admin . unsafe ( `DROP DATABASE IF EXISTS "${ name } " WITH (FORCE)` )
119- await admin . end ( )
120- await rm ( directory , { recursive : true , force : true } )
121- } )
122128
123- describe ( 'Operator archive repair against the full compatible schema' , ( ) => {
124129 it . each ( [ 'workspace' , 'project' , 'row' ] as const ) (
125130 'defers a busy %s lock, repairs unrelated environments and resumes without losing progress' ,
126131 async ( lock ) => {
0 commit comments