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
@@ -64,14 +66,24 @@ const cleanDatabase = async (): Promise<void> => {
6466 for ( const entity of con . entityMetadatas ) {
6567 const repository = con . getRepository ( entity . name ) ;
6668 if ( repository . metadata . tableType === 'view' ) continue ;
67- await repository . query ( `DELETE
68- FROM "${ entity . tableName } ";` ) ;
69+ await repository . query ( `DELETE FROM "${ entity . tableName } ";` ) ;
6970
7071 for ( const column of entity . primaryColumns ) {
7172 if ( column . generationStrategy === 'increment' ) {
72- await repository . query (
73- `ALTER SEQUENCE ${ entity . tableName } _${ column . databaseName } _seq RESTART WITH 1` ,
74- ) ;
73+ // Use pg_get_serial_sequence to find the actual sequence name
74+ // This handles both original and copied tables with different sequence naming
75+ try {
76+ const seqResult = await repository . query (
77+ `SELECT pg_get_serial_sequence('"${ entity . tableName } "', '${ column . databaseName } ') as seq_name` ,
78+ ) ;
79+ if ( seqResult [ 0 ] ?. seq_name ) {
80+ await repository . query (
81+ `ALTER SEQUENCE ${ seqResult [ 0 ] . seq_name } RESTART WITH 1` ,
82+ ) ;
83+ }
84+ } catch {
85+ // Sequence might not exist, ignore
86+ }
7587 }
7688 }
7789 }
@@ -82,6 +94,96 @@ jest.mock('file-type', () => ({
8294 fileTypeFromBuffer : ( ) => fileTypeFromBuffer ( ) ,
8395} ) ) ;
8496
97+ /**
98+ * Create the worker schema for test isolation.
99+ * Creates a new schema and copies all table structures from public schema.
100+ * This is used when ENABLE_SCHEMA_ISOLATION=true for parallel Jest workers.
101+ */
102+ const createWorkerSchema = async ( ) : Promise < void > => {
103+ // Only create non-public schemas (when running with multiple Jest workers)
104+ if ( testSchema === 'public' ) {
105+ return ;
106+ }
107+
108+ // Bootstrap connection using public schema
109+ const bootstrapDataSource = new DataSource ( {
110+ type : 'postgres' ,
111+ host : process . env . TYPEORM_HOST || 'localhost' ,
112+ port : 5432 ,
113+ username : process . env . TYPEORM_USERNAME || 'postgres' ,
114+ password : process . env . TYPEORM_PASSWORD || '12345' ,
115+ database :
116+ process . env . TYPEORM_DATABASE ||
117+ ( process . env . NODE_ENV === 'test' ? 'api_test' : 'api' ) ,
118+ schema : 'public' ,
119+ } ) ;
120+
121+ await bootstrapDataSource . initialize ( ) ;
122+
123+ // Drop and create the worker schema
124+ await bootstrapDataSource . query (
125+ `DROP SCHEMA IF EXISTS "${ testSchema } " CASCADE` ,
126+ ) ;
127+ await bootstrapDataSource . query ( `CREATE SCHEMA "${ testSchema } "` ) ;
128+
129+ // Get all tables from public schema (excluding views and TypeORM metadata)
130+ const tables = await bootstrapDataSource . query ( `
131+ SELECT tablename FROM pg_tables
132+ WHERE schemaname = 'public'
133+ AND tablename NOT LIKE 'pg_%'
134+ AND tablename != 'typeorm_metadata'
135+ ` ) ;
136+
137+ // Copy table structure from public to worker schema
138+ for ( const { tablename } of tables ) {
139+ await bootstrapDataSource . query ( `
140+ CREATE TABLE "${ testSchema } "."${ tablename } "
141+ (LIKE "public"."${ tablename } " INCLUDING ALL)
142+ ` ) ;
143+ }
144+
145+ // Copy migrations table so TypeORM knows migrations are already applied
146+ await bootstrapDataSource . query ( `
147+ INSERT INTO "${ testSchema } "."migrations" SELECT * FROM "public"."migrations"
148+ ` ) ;
149+
150+ // Get all views from public schema and recreate them in worker schema
151+ const views = await bootstrapDataSource . query ( `
152+ SELECT viewname, definition FROM pg_views
153+ WHERE schemaname = 'public'
154+ ` ) ;
155+
156+ for ( const { viewname, definition } of views ) {
157+ // Replace public schema references with worker schema in view definition
158+ const modifiedDefinition = definition . replace (
159+ / p u b l i c \. / g,
160+ `${ testSchema } .` ,
161+ ) ;
162+ await bootstrapDataSource . query ( `
163+ CREATE OR REPLACE VIEW "${ testSchema } "."${ viewname } " AS ${ modifiedDefinition }
164+ ` ) ;
165+ }
166+
167+ // Note: Triggers are NOT copied because they reference functions in public schema
168+ // which would insert data into public schema tables instead of worker schema tables.
169+ // This is a known limitation of schema isolation.
170+
171+ await bootstrapDataSource . destroy ( ) ;
172+ } ;
173+
174+ let schemaInitialized = false ;
175+
176+ beforeAll ( async ( ) => {
177+ if ( ! schemaInitialized ) {
178+ // Create worker schema for parallel test isolation
179+ // Public schema is set up by the pretest script
180+ if ( testSchema !== 'public' ) {
181+ await createWorkerSchema ( ) ;
182+ }
183+ schemaInitialized = true ;
184+ }
185+ } ) ;
186+
85187beforeEach ( async ( ) => {
86188 loadAuthKeys ( ) ;
87189
0 commit comments