first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import type { entities } from '@n8n/db';
|
||||
import { AuthRolesService, DbConnection, DbConnectionOptions } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { DataSourceOptions } from '@n8n/typeorm';
|
||||
import { DataSource as Connection } from '@n8n/typeorm';
|
||||
import assert from 'assert';
|
||||
import { randomString } from 'n8n-workflow';
|
||||
|
||||
export const testDbPrefix = 'n8n_test_';
|
||||
let isInitialized = false;
|
||||
let testDbName: string | undefined;
|
||||
let originalDatabase: string | undefined;
|
||||
|
||||
/**
|
||||
* Generate options for a bootstrap DB connection, to create and drop test databases.
|
||||
*/
|
||||
export const getBootstrapDBOptions = (): DataSourceOptions => {
|
||||
const globalConfig = Container.get(GlobalConfig);
|
||||
assert(globalConfig.database.type === 'postgresdb', 'Database type must be postgresdb');
|
||||
|
||||
return {
|
||||
type: 'postgres',
|
||||
...Container.get(DbConnectionOptions).getPostgresOverrides(),
|
||||
database: globalConfig.database.postgresdb.database,
|
||||
entityPrefix: globalConfig.database.tablePrefix,
|
||||
schema: globalConfig.database.postgresdb.schema,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize one test DB per suite run, with bootstrap connection if needed.
|
||||
*/
|
||||
export async function init() {
|
||||
if (isInitialized) return;
|
||||
|
||||
const globalConfig = Container.get(GlobalConfig);
|
||||
const dbType = globalConfig.database.type;
|
||||
testDbName = `${testDbPrefix}${randomString(6, 10).toLowerCase()}_${Date.now()}`;
|
||||
|
||||
if (dbType === 'postgresdb') {
|
||||
originalDatabase = globalConfig.database.postgresdb.database;
|
||||
const bootstrapPostgres = await new Connection(getBootstrapDBOptions()).initialize();
|
||||
await bootstrapPostgres.query(`CREATE DATABASE ${testDbName}`);
|
||||
await bootstrapPostgres.destroy();
|
||||
|
||||
globalConfig.database.postgresdb.database = testDbName;
|
||||
}
|
||||
|
||||
const dbConnection = Container.get(DbConnection);
|
||||
await dbConnection.init();
|
||||
await dbConnection.migrate();
|
||||
|
||||
await Container.get(AuthRolesService).init();
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
export function isReady() {
|
||||
const { connectionState } = Container.get(DbConnection);
|
||||
return connectionState.connected && connectionState.migrated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop test DB, closing bootstrap connection if existing.
|
||||
*/
|
||||
export async function terminate() {
|
||||
const dbConnection = Container.get(DbConnection);
|
||||
await dbConnection.close();
|
||||
dbConnection.connectionState.connected = false;
|
||||
|
||||
if (testDbName && originalDatabase) {
|
||||
const globalConfig = Container.get(GlobalConfig);
|
||||
if (globalConfig.database.type === 'postgresdb') {
|
||||
try {
|
||||
globalConfig.database.postgresdb.database = originalDatabase;
|
||||
const bootstrap = await new Connection(getBootstrapDBOptions()).initialize();
|
||||
await bootstrap.query(`DROP DATABASE IF EXISTS "${testDbName}"`);
|
||||
await bootstrap.destroy();
|
||||
} catch (error) {
|
||||
// Best effort - don't fail tests over cleanup
|
||||
console.warn(`Failed to drop test database "${testDbName}":`, error);
|
||||
}
|
||||
}
|
||||
testDbName = undefined;
|
||||
}
|
||||
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
type EntityName =
|
||||
| keyof typeof entities
|
||||
| 'InsightsRaw'
|
||||
| 'InsightsByPeriod'
|
||||
| 'InsightsMetadata'
|
||||
| 'DataTable'
|
||||
| 'DataTableColumn'
|
||||
| 'ChatHubSession'
|
||||
| 'ChatHubMessage'
|
||||
| 'ChatHubAgent'
|
||||
| 'ChatHubTool'
|
||||
| 'OAuthClient'
|
||||
| 'AuthorizationCode'
|
||||
| 'AccessToken'
|
||||
| 'RefreshToken'
|
||||
| 'UserConsent'
|
||||
| 'DynamicCredentialEntry'
|
||||
| 'DynamicCredentialResolver'
|
||||
| 'DynamicCredentialUserEntry';
|
||||
|
||||
/**
|
||||
* Truncate specific DB tables in a test DB.
|
||||
*/
|
||||
export async function truncate(entities: EntityName[]) {
|
||||
const connection = Container.get(Connection);
|
||||
|
||||
// Collect junction tables to clean
|
||||
const junctionTablesToClean = new Set<string>();
|
||||
|
||||
// Find all junction tables associated with the entities being truncated
|
||||
for (const name of entities) {
|
||||
try {
|
||||
const metadata = connection.getMetadata(name);
|
||||
for (const relation of metadata.manyToManyRelations) {
|
||||
if (relation.junctionEntityMetadata) {
|
||||
const junctionTableName = relation.junctionEntityMetadata.tablePath;
|
||||
junctionTablesToClean.add(junctionTableName);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip
|
||||
}
|
||||
}
|
||||
|
||||
// Clean junction tables first (since they reference the entities)
|
||||
for (const tableName of junctionTablesToClean) {
|
||||
await connection.query(`DELETE FROM ${tableName}`);
|
||||
}
|
||||
|
||||
for (const name of entities) {
|
||||
await connection.getRepository(name).delete({});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user