From ac460aa8414eec0f87138784a201284d48df50b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 25 Jan 2023 10:02:28 +0100 Subject: [PATCH] ci: Simplify DB truncate in tests (no-changelog) (#5243) --- packages/cli/test/integration/me.api.test.ts | 16 +- .../cli/test/integration/shared/constants.ts | 17 -- .../cli/test/integration/shared/testDb.ts | 157 ++---------------- .../cli/test/integration/shared/types.d.ts | 3 - packages/cli/test/integration/shared/utils.ts | 14 -- .../cli/test/unit/PermissionChecker.test.ts | 3 +- 6 files changed, 22 insertions(+), 188 deletions(-) diff --git a/packages/cli/test/integration/me.api.test.ts b/packages/cli/test/integration/me.api.test.ts index e7b7655119..27b3695101 100644 --- a/packages/cli/test/integration/me.api.test.ts +++ b/packages/cli/test/integration/me.api.test.ts @@ -35,15 +35,15 @@ beforeAll(async () => { utils.initTestTelemetry(); }); +beforeEach(async () => { + await testDb.truncate(['User']); +}); + afterAll(async () => { await testDb.terminate(); }); describe('Owner shell', () => { - beforeEach(async () => { - await testDb.truncate(['User']); - }); - test('GET /me should return sanitized owner shell', async () => { const ownerShell = await testDb.createUserShell(globalOwnerRole); @@ -238,10 +238,6 @@ describe('Member', () => { ); }); - afterEach(async () => { - await testDb.truncate(['User']); - }); - test('GET /me should return sanitized member', async () => { const member = await testDb.createUser({ globalRole: globalMemberRole }); @@ -441,10 +437,6 @@ describe('Owner', () => { config.set('userManagement.isInstanceOwnerSetUp', true); }); - afterEach(async () => { - await testDb.truncate(['User']); - }); - test('GET /me should return sanitized owner', async () => { const owner = await testDb.createUser({ globalRole: globalOwnerRole }); diff --git a/packages/cli/test/integration/shared/constants.ts b/packages/cli/test/integration/shared/constants.ts index 4196738777..b7850ab46a 100644 --- a/packages/cli/test/integration/shared/constants.ts +++ b/packages/cli/test/integration/shared/constants.ts @@ -47,16 +47,6 @@ 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'], -}; - export const COMMUNITY_PACKAGE_VERSION = { CURRENT: '0.1.0', UPDATED: '0.2.0', @@ -71,10 +61,3 @@ export const COMMUNITY_NODE_VERSION = { * Timeout (in milliseconds) to account for DB being slow to initialize. */ export const DB_INITIALIZATION_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 9f8c16db99..47fc7eff73 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -1,5 +1,9 @@ import { UserSettings } from 'n8n-core'; -import { DataSource as Connection, DataSourceOptions as ConnectionOptions } from 'typeorm'; +import { + DataSource as Connection, + DataSourceOptions as ConnectionOptions, + Repository, +} from 'typeorm'; import config from '@/config'; import * as Db from '@/Db'; @@ -11,25 +15,24 @@ import { postgresMigrations } from '@db/migrations/postgresdb'; import { sqliteMigrations } from '@db/migrations/sqlite'; import { hashPassword } from '@/UserManagement/UserManagementHelper'; import { AuthIdentity } from '@/databases/entities/AuthIdentity'; -import { ExecutionEntity } from '@db/entities/ExecutionEntity'; +import type { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { InstalledNodes } from '@db/entities/InstalledNodes'; import { InstalledPackages } from '@db/entities/InstalledPackages'; import type { Role } from '@db/entities/Role'; -import { TagEntity } from '@db/entities/TagEntity'; -import { User } from '@db/entities/User'; -import { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import type { TagEntity } from '@db/entities/TagEntity'; +import type { User } from '@db/entities/User'; +import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; +import { ICredentialsDb } from '@/Interfaces'; + +import { DB_INITIALIZATION_TIMEOUT } from './constants'; +import { randomApiKey, randomEmail, randomName, randomString, randomValidPassword } from './random'; +import { getPostgresSchemaSection } from './utils'; import type { CollectionName, CredentialPayload, InstalledNodePayload, InstalledPackagePayload, - MappingName, } from './types'; -import { DB_INITIALIZATION_TIMEOUT, MAPPING_TABLES, MAPPING_TABLES_TO_CLEAR } from './constants'; -import { randomApiKey, randomEmail, randomName, randomString, randomValidPassword } from './random'; -import { categorize, getPostgresSchemaSection } from './utils'; - -import type { DatabaseType, ICredentialsDb } from '@/Interfaces'; export type TestDBType = 'postgres' | 'mysql'; @@ -95,140 +98,14 @@ export async function terminate() { await Db.getConnection().destroy(); } -async function truncateMappingTables( - dbType: DatabaseType, - collections: CollectionName[], - 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(async (tableName) => - testDb.query( - `DELETE FROM ${tableName}; DELETE FROM sqlite_sequence WHERE name=${tableName};`, - ), - ); - - return Promise.all(promises); - } - - if (dbType === 'postgresdb') { - const schema = config.getEnv('database.postgresdb.schema'); - - // sequential TRUNCATEs to prevent race conditions - for (const tableName of mappingTables) { - const fullTableName = `${schema}.${tableName}`; - await testDb.query(`TRUNCATE TABLE ${fullTableName} RESTART IDENTITY CASCADE;`); - } - - return Promise.resolve([]); - } - - // mysqldb, mariadb - - const promises = mappingTables.flatMap((tableName) => [ - testDb.query(`DELETE FROM ${tableName};`), - testDb.query(`ALTER TABLE ${tableName} AUTO_INCREMENT = 1;`), - ]); - - return Promise.all(promises); -} - /** * 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[]) { - const dbType = config.getEnv('database.type'); - const testDb = Db.getConnection(); - - if (dbType === 'sqlite') { - await testDb.query('PRAGMA foreign_keys=OFF'); - - const truncationPromises = collections.map(async (collection) => { - const tableName = toTableName(collection); - // Db.collections[collection].clear(); - 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'); + for (const collection of collections) { + const repository: Repository = Db.collections[collection]; + await repository.delete({}); } - - if (dbType === 'postgresdb') { - const schema = config.getEnv('database.postgresdb.schema'); - - // sequential TRUNCATEs to prevent race conditions - for (const collection of collections) { - const fullTableName = `${schema}.${toTableName(collection)}`; - await testDb.query(`TRUNCATE TABLE ${fullTableName} RESTART IDENTITY CASCADE;`); - } - - return truncateMappingTables(dbType, collections, testDb); - } - - if (dbType === 'mysqldb') { - const { pass: sharedTables, fail: rest } = categorize(collections, (c: CollectionName) => - c.toLowerCase().startsWith('shared'), - ); - - // sequential DELETEs to prevent race conditions - // clear foreign-key tables first to avoid deadlocks on MySQL: https://stackoverflow.com/a/41174997 - for (const collection of [...sharedTables, ...rest]) { - const tableName = toTableName(collection); - - await testDb.query(`DELETE FROM ${tableName};`); - - const hasIdColumn = await testDb - .query(`SHOW COLUMNS FROM ${tableName}`) - .then((columns: Array<{ Field: string }>) => columns.find((c) => c.Field === 'id')); - - if (!hasIdColumn) continue; - - await testDb.query(`ALTER TABLE ${tableName} AUTO_INCREMENT = 1;`); - } - - return truncateMappingTables(dbType, collections, testDb); - } -} - -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 { - AuthIdentity: 'auth_identity', - AuthProviderSyncHistory: 'auth_provider_sync_history', - Credentials: 'credentials_entity', - Execution: 'execution_entity', - InstalledNodes: 'installed_nodes', - InstalledPackages: 'installed_packages', - Role: 'role', - Settings: 'settings', - SharedCredentials: 'shared_credentials', - SharedWorkflow: 'shared_workflow', - Tag: 'tag_entity', - User: 'user', - Webhook: 'webhook_entity', - Workflow: 'workflow_entity', - WorkflowStatistics: 'workflow_statistics', - EventDestinations: 'event_destinations', - }[sourceName]; } // ---------------------------------- diff --git a/packages/cli/test/integration/shared/types.d.ts b/packages/cli/test/integration/shared/types.d.ts index 0a23a7b617..bd73989758 100644 --- a/packages/cli/test/integration/shared/types.d.ts +++ b/packages/cli/test/integration/shared/types.d.ts @@ -4,12 +4,9 @@ import type { SuperAgentTest } from 'supertest'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { User } from '@db/entities/User'; import type { ICredentialsDb, IDatabaseCollections } from '@/Interfaces'; -import { MAPPING_TABLES } from './constants'; export type CollectionName = keyof IDatabaseCollections; -export type MappingName = keyof typeof MAPPING_TABLES; - export type ApiPath = 'internal' | 'public'; export type AuthAgent = (user: User) => SuperAgentTest; diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 17f57f6c7d..3a664b494e 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -675,20 +675,6 @@ export async function isInstanceOwnerSetUp() { // misc // ---------------------------------- -/** - * Categorize array items into two groups based on whether they pass a test. - */ -export const categorize = (arr: T[], test: (str: T) => boolean) => { - return arr.reduce<{ pass: T[]; fail: T[] }>( - (acc, cur) => { - test(cur) ? acc.pass.push(cur) : acc.fail.push(cur); - - return acc; - }, - { pass: [], fail: [] }, - ); -}; - export function getPostgresSchemaSection( schema = config.getSchema(), ): PostgresSchemaSection | null { diff --git a/packages/cli/test/unit/PermissionChecker.test.ts b/packages/cli/test/unit/PermissionChecker.test.ts index 2964210dcd..89a7b9f894 100644 --- a/packages/cli/test/unit/PermissionChecker.test.ts +++ b/packages/cli/test/unit/PermissionChecker.test.ts @@ -49,8 +49,7 @@ beforeAll(async () => { }); beforeEach(async () => { - await testDb.truncate(['SharedWorkflow', 'SharedCredentials']); - await testDb.truncate(['User', 'Workflow', 'Credentials']); + await testDb.truncate(['SharedWorkflow', 'SharedCredentials', 'Workflow', 'Credentials', 'User']); }); afterAll(async () => {