@@ -2,11 +2,17 @@ import { exec as callbackExec } from 'child_process';
2
2
import { promisify } from 'util' ;
3
3
4
4
import { createConnection , getConnection , ConnectionOptions , Connection } from 'typeorm' ;
5
- import { Credentials , UserSettings } from 'n8n-core' ;
5
+ import { UserSettings } from 'n8n-core' ;
6
6
7
7
import config from '../../../config' ;
8
- import { BOOTSTRAP_MYSQL_CONNECTION_NAME , BOOTSTRAP_POSTGRES_CONNECTION_NAME } from './constants' ;
9
- import { Db , ICredentialsDb , IDatabaseCollections } from '../../../src' ;
8
+ import {
9
+ BOOTSTRAP_MYSQL_CONNECTION_NAME ,
10
+ BOOTSTRAP_POSTGRES_CONNECTION_NAME ,
11
+ DB_INITIALIZATION_TIMEOUT ,
12
+ MAPPING_TABLES ,
13
+ MAPPING_TABLES_TO_CLEAR ,
14
+ } from './constants' ;
15
+ import { DatabaseType , Db , ICredentialsDb } from '../../../src' ;
10
16
import { randomApiKey , randomEmail , randomName , randomString , randomValidPassword } from './random' ;
11
17
import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity' ;
12
18
import { hashPassword } from '../../../src/UserManagement/UserManagementHelper' ;
@@ -19,7 +25,7 @@ import { createCredentiasFromCredentialsEntity } from '../../../src/CredentialsH
19
25
20
26
import type { Role } from '../../../src/databases/entities/Role' ;
21
27
import { User } from '../../../src/databases/entities/User' ;
22
- import type { CollectionName , CredentialPayload } from './types' ;
28
+ import type { CollectionName , CredentialPayload , MappingName } from './types' ;
23
29
import { WorkflowEntity } from '../../../src/databases/entities/WorkflowEntity' ;
24
30
import { ExecutionEntity } from '../../../src/databases/entities/ExecutionEntity' ;
25
31
import { TagEntity } from '../../../src/databases/entities/TagEntity' ;
@@ -33,6 +39,8 @@ export async function init() {
33
39
const dbType = config . getEnv ( 'database.type' ) ;
34
40
35
41
if ( dbType === 'sqlite' ) {
42
+ jest . setTimeout ( DB_INITIALIZATION_TIMEOUT ) ;
43
+
36
44
// no bootstrap connection required
37
45
const testDbName = `n8n_test_sqlite_${ randomString ( 6 , 10 ) } _${ Date . now ( ) } ` ;
38
46
await Db . init ( getSqliteOptions ( { name : testDbName } ) ) ;
@@ -42,6 +50,8 @@ export async function init() {
42
50
}
43
51
44
52
if ( dbType === 'postgresdb' ) {
53
+ jest . setTimeout ( DB_INITIALIZATION_TIMEOUT ) ;
54
+
45
55
let bootstrapPostgres ;
46
56
const pgOptions = getBootstrapPostgresOptions ( ) ;
47
57
@@ -87,6 +97,8 @@ export async function init() {
87
97
}
88
98
89
99
if ( dbType === 'mysqldb' ) {
100
+ // initialization timeout in test/setup.ts
101
+
90
102
const bootstrapMysql = await createConnection ( getBootstrapMySqlOptions ( ) ) ;
91
103
92
104
const testDbName = `mysql_${ randomString ( 6 , 10 ) } _${ Date . now ( ) } _n8n_test` ;
@@ -127,32 +139,89 @@ export async function terminate(testDbName: string) {
127
139
}
128
140
}
129
141
142
+ async function truncateMappingTables (
143
+ dbType : DatabaseType ,
144
+ collections : Array < CollectionName > ,
145
+ testDb : Connection ,
146
+ ) {
147
+ const mappingTables = collections . reduce < string [ ] > ( ( acc , collection ) => {
148
+ const found = MAPPING_TABLES_TO_CLEAR [ collection ] ;
149
+
150
+ if ( found ) acc . push ( ...found ) ;
151
+
152
+ return acc ;
153
+ } , [ ] ) ;
154
+
155
+ if ( dbType === 'sqlite' ) {
156
+ const promises = mappingTables . map ( ( tableName ) =>
157
+ testDb . query (
158
+ `DELETE FROM ${ tableName } ; DELETE FROM sqlite_sequence WHERE name=${ tableName } ;` ,
159
+ ) ,
160
+ ) ;
161
+
162
+ return Promise . all ( promises ) ;
163
+ }
164
+
165
+ if ( dbType === 'postgresdb' ) {
166
+ const schema = config . getEnv ( 'database.postgresdb.schema' ) ;
167
+
168
+ // `TRUNCATE` in postgres cannot be parallelized
169
+ for ( const tableName of mappingTables ) {
170
+ const fullTableName = `${ schema } .${ tableName } ` ;
171
+ await testDb . query ( `TRUNCATE TABLE ${ fullTableName } RESTART IDENTITY CASCADE;` ) ;
172
+ }
173
+
174
+ return Promise . resolve ( [ ] ) ;
175
+ }
176
+
177
+ // mysqldb, mariadb
178
+
179
+ const promises = mappingTables . flatMap ( ( tableName ) => [
180
+ testDb . query ( `DELETE FROM ${ tableName } ;` ) ,
181
+ testDb . query ( `ALTER TABLE ${ tableName } AUTO_INCREMENT = 1;` ) ,
182
+ ] ) ;
183
+
184
+ return Promise . all ( promises ) ;
185
+ }
186
+
130
187
/**
131
- * Truncate DB tables for collections .
188
+ * Truncate specific DB tables in a test DB .
132
189
*
133
190
* @param collections Array of entity names whose tables to truncate.
134
191
* @param testDbName Name of the test DB to truncate tables in.
135
192
*/
136
- export async function truncate ( collections : CollectionName [ ] , testDbName : string ) {
193
+ export async function truncate ( collections : Array < CollectionName > , testDbName : string ) {
137
194
const dbType = config . getEnv ( 'database.type' ) ;
138
-
139
195
const testDb = getConnection ( testDbName ) ;
140
196
141
197
if ( dbType === 'sqlite' ) {
142
198
await testDb . query ( 'PRAGMA foreign_keys=OFF' ) ;
143
- await Promise . all ( collections . map ( ( collection ) => Db . collections [ collection ] . clear ( ) ) ) ;
199
+
200
+ const truncationPromises = collections . map ( ( collection ) => {
201
+ const tableName = toTableName ( collection ) ;
202
+ return testDb . query (
203
+ `DELETE FROM ${ tableName } ; DELETE FROM sqlite_sequence WHERE name=${ tableName } ;` ,
204
+ ) ;
205
+ } ) ;
206
+
207
+ truncationPromises . push ( truncateMappingTables ( dbType , collections , testDb ) ) ;
208
+
209
+ await Promise . all ( truncationPromises ) ;
210
+
144
211
return testDb . query ( 'PRAGMA foreign_keys=ON' ) ;
145
212
}
146
213
147
214
if ( dbType === 'postgresdb' ) {
148
- return Promise . all (
149
- collections . map ( ( collection ) => {
150
- const schema = config . getEnv ( 'database.postgresdb.schema' ) ;
151
- const fullTableName = `${ schema } .${ toTableName ( collection ) } ` ;
215
+ const schema = config . getEnv ( 'database.postgresdb.schema' ) ;
152
216
153
- testDb . query ( `TRUNCATE TABLE ${ fullTableName } RESTART IDENTITY CASCADE;` ) ;
154
- } ) ,
155
- ) ;
217
+ // `TRUNCATE` in postgres cannot be parallelized
218
+ for ( const collection of collections ) {
219
+ const fullTableName = `${ schema } .${ toTableName ( collection ) } ` ;
220
+ await testDb . query ( `TRUNCATE TABLE ${ fullTableName } RESTART IDENTITY CASCADE;` ) ;
221
+ }
222
+
223
+ return await truncateMappingTables ( dbType , collections , testDb ) ;
224
+ // return Promise.resolve([])
156
225
}
157
226
158
227
/**
@@ -167,11 +236,17 @@ export async function truncate(collections: CollectionName[], testDbName: string
167
236
) ;
168
237
169
238
await truncateMySql ( testDb , isShared ) ;
239
+ await truncateMappingTables ( dbType , collections , testDb ) ;
170
240
await truncateMySql ( testDb , isNotShared ) ;
171
241
}
172
242
}
173
243
174
- function toTableName ( collectionName : CollectionName ) {
244
+ const isMapping = ( collection : string ) : collection is MappingName =>
245
+ Object . keys ( MAPPING_TABLES ) . includes ( collection ) ;
246
+
247
+ function toTableName ( sourceName : CollectionName | MappingName ) {
248
+ if ( isMapping ( sourceName ) ) return MAPPING_TABLES [ sourceName ] ;
249
+
175
250
return {
176
251
Credentials : 'credentials_entity' ,
177
252
Workflow : 'workflow_entity' ,
@@ -183,10 +258,10 @@ function toTableName(collectionName: CollectionName) {
183
258
SharedCredentials : 'shared_credentials' ,
184
259
SharedWorkflow : 'shared_workflow' ,
185
260
Settings : 'settings' ,
186
- } [ collectionName ] ;
261
+ } [ sourceName ] ;
187
262
}
188
263
189
- function truncateMySql ( connection : Connection , collections : Array < keyof IDatabaseCollections > ) {
264
+ function truncateMySql ( connection : Connection , collections : CollectionName [ ] ) {
190
265
return Promise . all (
191
266
collections . map ( async ( collection ) => {
192
267
const tableName = toTableName ( collection ) ;
0 commit comments