11import * as matchers from 'jest-extended' ;
2+ import { DataSource } from 'typeorm' ;
23import '../src/config' ;
34import createOrGetConnection from '../src/db' ;
5+ import { testSchema } from '../src/data-source' ;
46import { remoteConfig } from '../src/remoteConfig' ;
57import { loadAuthKeys } from '../src/auth' ;
68
@@ -61,17 +63,30 @@ const cleanDatabase = async (): Promise<void> => {
6163 await remoteConfig . init ( ) ;
6264
6365 const con = await createOrGetConnection ( ) ;
66+ const schema = con . options . schema || 'public' ;
67+
6468 for ( const entity of con . entityMetadatas ) {
6569 const repository = con . getRepository ( entity . name ) ;
6670 if ( repository . metadata . tableType === 'view' ) continue ;
6771 await repository . query ( `DELETE
68- FROM "${ entity . tableName } ";` ) ;
72+ FROM "${ schema } "." ${ entity . tableName } ";` ) ;
6973
7074 for ( const column of entity . primaryColumns ) {
7175 if ( column . generationStrategy === 'increment' ) {
72- await repository . query (
73- `ALTER SEQUENCE ${ entity . tableName } _${ column . databaseName } _seq RESTART WITH 1` ,
74- ) ;
76+ // Use pg_get_serial_sequence to find the actual sequence name
77+ // This handles both original and copied tables with different sequence naming
78+ try {
79+ const seqResult = await repository . query (
80+ `SELECT pg_get_serial_sequence('"${ schema } "."${ entity . tableName } "', '${ column . databaseName } ') as seq_name` ,
81+ ) ;
82+ if ( seqResult [ 0 ] ?. seq_name ) {
83+ await repository . query (
84+ `ALTER SEQUENCE ${ seqResult [ 0 ] . seq_name } RESTART WITH 1` ,
85+ ) ;
86+ }
87+ } catch {
88+ // Sequence might not exist, ignore
89+ }
7590 }
7691 }
7792 }
@@ -82,6 +97,92 @@ jest.mock('file-type', () => ({
8297 fileTypeFromBuffer : ( ) => fileTypeFromBuffer ( ) ,
8398} ) ) ;
8499
100+ /**
101+ * Create the worker schema for test isolation.
102+ * Creates a new schema and copies all table structures from public schema.
103+ * This is used when ENABLE_SCHEMA_ISOLATION=true for parallel Jest workers.
104+ */
105+ const createWorkerSchema = async ( ) : Promise < void > => {
106+ // Only create non-public schemas (when running with multiple Jest workers)
107+ if ( testSchema === 'public' ) {
108+ return ;
109+ }
110+
111+ // Bootstrap connection using public schema
112+ const bootstrapDataSource = new DataSource ( {
113+ type : 'postgres' ,
114+ host : process . env . TYPEORM_HOST || 'localhost' ,
115+ port : 5432 ,
116+ username : process . env . TYPEORM_USERNAME || 'postgres' ,
117+ password : process . env . TYPEORM_PASSWORD || '12345' ,
118+ database :
119+ process . env . TYPEORM_DATABASE ||
120+ ( process . env . NODE_ENV === 'test' ? 'api_test' : 'api' ) ,
121+ schema : 'public' ,
122+ } ) ;
123+
124+ await bootstrapDataSource . initialize ( ) ;
125+
126+ // Drop and create the worker schema
127+ await bootstrapDataSource . query (
128+ `DROP SCHEMA IF EXISTS "${ testSchema } " CASCADE` ,
129+ ) ;
130+ await bootstrapDataSource . query ( `CREATE SCHEMA "${ testSchema } "` ) ;
131+
132+ // Get all tables from public schema (excluding views and TypeORM metadata)
133+ const tables = await bootstrapDataSource . query ( `
134+ SELECT tablename FROM pg_tables
135+ WHERE schemaname = 'public'
136+ AND tablename NOT LIKE 'pg_%'
137+ AND tablename != 'typeorm_metadata'
138+ ` ) ;
139+
140+ // Copy table structure from public to worker schema
141+ for ( const { tablename } of tables ) {
142+ await bootstrapDataSource . query ( `
143+ CREATE TABLE "${ testSchema } "."${ tablename } "
144+ (LIKE "public"."${ tablename } " INCLUDING ALL)
145+ ` ) ;
146+ }
147+
148+ // Copy migrations table so TypeORM knows migrations are already applied
149+ await bootstrapDataSource . query ( `
150+ INSERT INTO "${ testSchema } "."migrations" SELECT * FROM "public"."migrations"
151+ ` ) ;
152+
153+ // Get all views from public schema and recreate them in worker schema
154+ const views = await bootstrapDataSource . query ( `
155+ SELECT viewname, definition FROM pg_views
156+ WHERE schemaname = 'public'
157+ ` ) ;
158+
159+ for ( const { viewname, definition } of views ) {
160+ // Replace public schema references with worker schema in view definition
161+ const modifiedDefinition = definition . replace (
162+ / p u b l i c \. / g,
163+ `${ testSchema } .` ,
164+ ) ;
165+ await bootstrapDataSource . query ( `
166+ CREATE OR REPLACE VIEW "${ testSchema } "."${ viewname } " AS ${ modifiedDefinition }
167+ ` ) ;
168+ }
169+
170+ await bootstrapDataSource . destroy ( ) ;
171+ } ;
172+
173+ let schemaInitialized = false ;
174+
175+ beforeAll ( async ( ) => {
176+ if ( ! schemaInitialized ) {
177+ // Create worker schema for parallel test isolation
178+ // Public schema is set up by the pretest script
179+ if ( testSchema !== 'public' ) {
180+ await createWorkerSchema ( ) ;
181+ }
182+ schemaInitialized = true ;
183+ }
184+ } ) ;
185+
85186beforeEach ( async ( ) => {
86187 loadAuthKeys ( ) ;
87188
0 commit comments