From de55fdb625542148bee0eb6f41682495693226bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 30 Jun 2022 13:43:50 +0200 Subject: [PATCH] :test_tube: Truncate mapping tables --- .../integration/publicApi/workflows.test.ts | 4 +- .../cli/test/integration/shared/constants.ts | 18 ++++ .../cli/test/integration/shared/testDb.ts | 97 +++++++++++++++---- .../cli/test/integration/shared/types.d.ts | 3 + 4 files changed, 103 insertions(+), 19 deletions(-) diff --git a/packages/cli/test/integration/publicApi/workflows.test.ts b/packages/cli/test/integration/publicApi/workflows.test.ts index 17736af836..98875c1380 100644 --- a/packages/cli/test/integration/publicApi/workflows.test.ts +++ b/packages/cli/test/integration/publicApi/workflows.test.ts @@ -35,14 +35,14 @@ beforeAll(async () => { utils.initTestTelemetry(); utils.initTestLogger(); + utils.initConfigFile(); await utils.initNodeTypes(); - await utils.initConfigFile(); workflowRunner = await utils.initActiveWorkflowRunner(); }); beforeEach(async () => { await testDb.truncate( - ['SharedCredentials', 'SharedWorkflow', 'User', 'Workflow', 'Credentials'], + ['SharedCredentials', 'SharedWorkflow', 'User', 'Workflow', 'Credentials', 'Tag'], testDbName, ); diff --git a/packages/cli/test/integration/shared/constants.ts b/packages/cli/test/integration/shared/constants.ts index d7e165ab4a..33189089b5 100644 --- a/packages/cli/test/integration/shared/constants.ts +++ b/packages/cli/test/integration/shared/constants.ts @@ -48,6 +48,17 @@ export const ROUTES_REQUIRING_AUTHORIZATION: Readonly = [ 'POST /owner/skip-setup', ]; +/** + * Mapping tables link entities but, unlike `SharedWorkflow` and `SharedCredentials`, + * have no entity representation. Therefore, mapping tables must be cleared + * on truncation of any of the collections they link. + */ +export const MAPPING_TABLES_TO_CLEAR: Record = { + Workflow: ['workflows_tags'], + Tag: ['workflows_tags'], +}; + + /** * Name of the connection used for creating and dropping a Postgres DB * for each suite test run. @@ -64,3 +75,10 @@ export const BOOTSTRAP_MYSQL_CONNECTION_NAME: Readonly = 'n8n_bs_mysql'; * Timeout (in milliseconds) to account for fake SMTP service being slow to respond. */ export const SMTP_TEST_TIMEOUT = 30_000; + +/** + * Mapping tables having no entity representation. + */ +export const MAPPING_TABLES = { + WorkflowsTags: 'workflows_tags', +} as const; diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index e344961235..16d4b4f918 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -5,8 +5,13 @@ import { createConnection, getConnection, ConnectionOptions, Connection } from ' import { Credentials, UserSettings } from 'n8n-core'; import config from '../../../config'; -import { BOOTSTRAP_MYSQL_CONNECTION_NAME, BOOTSTRAP_POSTGRES_CONNECTION_NAME } from './constants'; -import { Db, ICredentialsDb, IDatabaseCollections } from '../../../src'; +import { + BOOTSTRAP_MYSQL_CONNECTION_NAME, + BOOTSTRAP_POSTGRES_CONNECTION_NAME, + MAPPING_TABLES, + MAPPING_TABLES_TO_CLEAR, +} from './constants'; +import { DatabaseType, Db, ICredentialsDb } from '../../../src'; import { randomApiKey, randomEmail, randomName, randomString, randomValidPassword } from './random'; import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity'; import { hashPassword } from '../../../src/UserManagement/UserManagementHelper'; @@ -19,7 +24,7 @@ import { createCredentiasFromCredentialsEntity } from '../../../src/CredentialsH import type { Role } from '../../../src/databases/entities/Role'; import { User } from '../../../src/databases/entities/User'; -import type { CollectionName, CredentialPayload } from './types'; +import type { CollectionName, CredentialPayload, MappingName } from './types'; import { WorkflowEntity } from '../../../src/databases/entities/WorkflowEntity'; import { ExecutionEntity } from '../../../src/databases/entities/ExecutionEntity'; import { TagEntity } from '../../../src/databases/entities/TagEntity'; @@ -127,32 +132,84 @@ export async function terminate(testDbName: string) { } } +async function truncateMappingTables( + dbType: DatabaseType, + collections: Array, + testDb: Connection, +) { + const mappingTables = collections.reduce((acc, collection) => { + const found = MAPPING_TABLES_TO_CLEAR[collection]; + + if (found) acc.push(...found); + + return acc; + }, []); + + if (dbType === 'sqlite') { + const promises = mappingTables.map((tableName) => + testDb.query(`DELETE FROM ${tableName}; DELETE FROM sqlite_sequence WHERE name=${tableName};`), + ); + + return Promise.all(promises); + } + + if (dbType === 'postgresdb') { + const promises = mappingTables.map((tableName) => { + const schema = config.getEnv('database.postgresdb.schema'); + const fullTableName = `${schema}.${tableName}`; + testDb.query(`TRUNCATE TABLE ${fullTableName} RESTART IDENTITY CASCADE;`); + }); + + return Promise.all(promises); + } + + // mysqldb, mariadb + + const promises = mappingTables.map((tableName) => + testDb.query(`DELETE FROM ${tableName}; ALTER TABLE ${tableName} AUTO_INCREMENT = 1;`), + ); + + return Promise.all(promises); +} + /** - * Truncate DB tables for collections. + * Truncate specific DB tables in a test DB. * * @param collections Array of entity names whose tables to truncate. * @param testDbName Name of the test DB to truncate tables in. */ -export async function truncate(collections: CollectionName[], testDbName: string) { +export async function truncate(collections: Array, testDbName: string) { const dbType = config.getEnv('database.type'); - const testDb = getConnection(testDbName); if (dbType === 'sqlite') { await testDb.query('PRAGMA foreign_keys=OFF'); - await Promise.all(collections.map((collection) => Db.collections[collection].clear())); + + const truncationPromises = collections.map((collection) => { + const tableName = toTableName(collection); + return testDb.query( + `DELETE FROM ${tableName}; DELETE FROM sqlite_sequence WHERE name=${tableName};`, + ); + }); + + truncationPromises.push(truncateMappingTables(dbType, collections, testDb)); + + await Promise.all(truncationPromises); + return testDb.query('PRAGMA foreign_keys=ON'); } if (dbType === 'postgresdb') { - return Promise.all( - collections.map((collection) => { - const schema = config.getEnv('database.postgresdb.schema'); - const fullTableName = `${schema}.${toTableName(collection)}`; + const truncationPromises = collections.map((collection) => { + const schema = config.getEnv('database.postgresdb.schema'); + const fullTableName = `${schema}.${toTableName(collection)}`; - testDb.query(`TRUNCATE TABLE ${fullTableName} RESTART IDENTITY CASCADE;`); - }), - ); + return testDb.query(`TRUNCATE TABLE ${fullTableName} RESTART IDENTITY CASCADE;`); + }); + + truncationPromises.push(truncateMappingTables(dbType, collections, testDb)); + + return Promise.all(truncationPromises); } /** @@ -167,11 +224,17 @@ export async function truncate(collections: CollectionName[], testDbName: string ); await truncateMySql(testDb, isShared); + await truncateMappingTables(dbType, collections, testDb); await truncateMySql(testDb, isNotShared); } } -function toTableName(collectionName: CollectionName) { +const isMapping = (collection: string): collection is MappingName => + Object.keys(MAPPING_TABLES).includes(collection); + +function toTableName(sourceName: CollectionName | MappingName) { + if (isMapping(sourceName)) return MAPPING_TABLES[sourceName]; + return { Credentials: 'credentials_entity', Workflow: 'workflow_entity', @@ -183,10 +246,10 @@ function toTableName(collectionName: CollectionName) { SharedCredentials: 'shared_credentials', SharedWorkflow: 'shared_workflow', Settings: 'settings', - }[collectionName]; + }[sourceName]; } -function truncateMySql(connection: Connection, collections: Array) { +function truncateMySql(connection: Connection, collections: CollectionName[]) { return Promise.all( collections.map(async (collection) => { const tableName = toTableName(collection); diff --git a/packages/cli/test/integration/shared/types.d.ts b/packages/cli/test/integration/shared/types.d.ts index c16a44d29f..a711c4b036 100644 --- a/packages/cli/test/integration/shared/types.d.ts +++ b/packages/cli/test/integration/shared/types.d.ts @@ -2,9 +2,12 @@ import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n- import type { ICredentialsDb, IDatabaseCollections } from '../../../src'; import type { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity'; import type { User } from '../../../src/databases/entities/User'; +import { MAPPING_TABLES } from './constants'; export type CollectionName = keyof IDatabaseCollections; +export type MappingName = keyof typeof MAPPING_TABLES; + export type SmtpTestAccount = { user: string; pass: string;