mirror of
https://github.com/n8n-io/n8n.git
synced 2024-09-20 06:47:32 -07:00
🧪 Truncate mapping tables
This commit is contained in:
parent
86b3cc6e15
commit
de55fdb625
|
@ -35,14 +35,14 @@ beforeAll(async () => {
|
||||||
|
|
||||||
utils.initTestTelemetry();
|
utils.initTestTelemetry();
|
||||||
utils.initTestLogger();
|
utils.initTestLogger();
|
||||||
|
utils.initConfigFile();
|
||||||
await utils.initNodeTypes();
|
await utils.initNodeTypes();
|
||||||
await utils.initConfigFile();
|
|
||||||
workflowRunner = await utils.initActiveWorkflowRunner();
|
workflowRunner = await utils.initActiveWorkflowRunner();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await testDb.truncate(
|
await testDb.truncate(
|
||||||
['SharedCredentials', 'SharedWorkflow', 'User', 'Workflow', 'Credentials'],
|
['SharedCredentials', 'SharedWorkflow', 'User', 'Workflow', 'Credentials', 'Tag'],
|
||||||
testDbName,
|
testDbName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,17 @@ export const ROUTES_REQUIRING_AUTHORIZATION: Readonly<string[]> = [
|
||||||
'POST /owner/skip-setup',
|
'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<string, string[] | undefined> = {
|
||||||
|
Workflow: ['workflows_tags'],
|
||||||
|
Tag: ['workflows_tags'],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the connection used for creating and dropping a Postgres DB
|
* Name of the connection used for creating and dropping a Postgres DB
|
||||||
* for each suite test run.
|
* for each suite test run.
|
||||||
|
@ -64,3 +75,10 @@ export const BOOTSTRAP_MYSQL_CONNECTION_NAME: Readonly<string> = 'n8n_bs_mysql';
|
||||||
* Timeout (in milliseconds) to account for fake SMTP service being slow to respond.
|
* Timeout (in milliseconds) to account for fake SMTP service being slow to respond.
|
||||||
*/
|
*/
|
||||||
export const SMTP_TEST_TIMEOUT = 30_000;
|
export const SMTP_TEST_TIMEOUT = 30_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping tables having no entity representation.
|
||||||
|
*/
|
||||||
|
export const MAPPING_TABLES = {
|
||||||
|
WorkflowsTags: 'workflows_tags',
|
||||||
|
} as const;
|
||||||
|
|
|
@ -5,8 +5,13 @@ import { createConnection, getConnection, ConnectionOptions, Connection } from '
|
||||||
import { Credentials, UserSettings } from 'n8n-core';
|
import { Credentials, UserSettings } from 'n8n-core';
|
||||||
|
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import { BOOTSTRAP_MYSQL_CONNECTION_NAME, BOOTSTRAP_POSTGRES_CONNECTION_NAME } from './constants';
|
import {
|
||||||
import { Db, ICredentialsDb, IDatabaseCollections } from '../../../src';
|
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 { randomApiKey, randomEmail, randomName, randomString, randomValidPassword } from './random';
|
||||||
import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
import { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
||||||
import { hashPassword } from '../../../src/UserManagement/UserManagementHelper';
|
import { hashPassword } from '../../../src/UserManagement/UserManagementHelper';
|
||||||
|
@ -19,7 +24,7 @@ import { createCredentiasFromCredentialsEntity } from '../../../src/CredentialsH
|
||||||
|
|
||||||
import type { Role } from '../../../src/databases/entities/Role';
|
import type { Role } from '../../../src/databases/entities/Role';
|
||||||
import { User } from '../../../src/databases/entities/User';
|
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 { WorkflowEntity } from '../../../src/databases/entities/WorkflowEntity';
|
||||||
import { ExecutionEntity } from '../../../src/databases/entities/ExecutionEntity';
|
import { ExecutionEntity } from '../../../src/databases/entities/ExecutionEntity';
|
||||||
import { TagEntity } from '../../../src/databases/entities/TagEntity';
|
import { TagEntity } from '../../../src/databases/entities/TagEntity';
|
||||||
|
@ -127,32 +132,84 @@ export async function terminate(testDbName: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function truncateMappingTables(
|
||||||
|
dbType: DatabaseType,
|
||||||
|
collections: Array<CollectionName>,
|
||||||
|
testDb: Connection,
|
||||||
|
) {
|
||||||
|
const mappingTables = collections.reduce<string[]>((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 collections Array of entity names whose tables to truncate.
|
||||||
* @param testDbName Name of the test DB to truncate tables in.
|
* @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<CollectionName>, testDbName: string) {
|
||||||
const dbType = config.getEnv('database.type');
|
const dbType = config.getEnv('database.type');
|
||||||
|
|
||||||
const testDb = getConnection(testDbName);
|
const testDb = getConnection(testDbName);
|
||||||
|
|
||||||
if (dbType === 'sqlite') {
|
if (dbType === 'sqlite') {
|
||||||
await testDb.query('PRAGMA foreign_keys=OFF');
|
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');
|
return testDb.query('PRAGMA foreign_keys=ON');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dbType === 'postgresdb') {
|
if (dbType === 'postgresdb') {
|
||||||
return Promise.all(
|
const truncationPromises = collections.map((collection) => {
|
||||||
collections.map((collection) => {
|
const schema = config.getEnv('database.postgresdb.schema');
|
||||||
const schema = config.getEnv('database.postgresdb.schema');
|
const fullTableName = `${schema}.${toTableName(collection)}`;
|
||||||
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 truncateMySql(testDb, isShared);
|
||||||
|
await truncateMappingTables(dbType, collections, testDb);
|
||||||
await truncateMySql(testDb, isNotShared);
|
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 {
|
return {
|
||||||
Credentials: 'credentials_entity',
|
Credentials: 'credentials_entity',
|
||||||
Workflow: 'workflow_entity',
|
Workflow: 'workflow_entity',
|
||||||
|
@ -183,10 +246,10 @@ function toTableName(collectionName: CollectionName) {
|
||||||
SharedCredentials: 'shared_credentials',
|
SharedCredentials: 'shared_credentials',
|
||||||
SharedWorkflow: 'shared_workflow',
|
SharedWorkflow: 'shared_workflow',
|
||||||
Settings: 'settings',
|
Settings: 'settings',
|
||||||
}[collectionName];
|
}[sourceName];
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateMySql(connection: Connection, collections: Array<keyof IDatabaseCollections>) {
|
function truncateMySql(connection: Connection, collections: CollectionName[]) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
collections.map(async (collection) => {
|
collections.map(async (collection) => {
|
||||||
const tableName = toTableName(collection);
|
const tableName = toTableName(collection);
|
||||||
|
|
|
@ -2,9 +2,12 @@ import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n-
|
||||||
import type { ICredentialsDb, IDatabaseCollections } from '../../../src';
|
import type { ICredentialsDb, IDatabaseCollections } from '../../../src';
|
||||||
import type { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
import type { CredentialsEntity } from '../../../src/databases/entities/CredentialsEntity';
|
||||||
import type { User } from '../../../src/databases/entities/User';
|
import type { User } from '../../../src/databases/entities/User';
|
||||||
|
import { MAPPING_TABLES } from './constants';
|
||||||
|
|
||||||
export type CollectionName = keyof IDatabaseCollections;
|
export type CollectionName = keyof IDatabaseCollections;
|
||||||
|
|
||||||
|
export type MappingName = keyof typeof MAPPING_TABLES;
|
||||||
|
|
||||||
export type SmtpTestAccount = {
|
export type SmtpTestAccount = {
|
||||||
user: string;
|
user: string;
|
||||||
pass: string;
|
pass: string;
|
||||||
|
|
Loading…
Reference in a new issue