backend-test-utils: drop database in shutdown
Signed-off-by: zcmander <zcmander@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': minor
|
||||
'@backstage/backend-common': minor
|
||||
---
|
||||
|
||||
drop databases after unit tests if the database instance is not running in docker
|
||||
@@ -276,6 +276,12 @@ export class DockerContainerRunner implements ContainerRunner {
|
||||
runContainer(options: RunContainerOptions): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function dropDatabase(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
): Promise<void>;
|
||||
|
||||
// @public
|
||||
export function ensureDatabaseExists(
|
||||
dbConfig: Config,
|
||||
|
||||
@@ -19,10 +19,11 @@ import {
|
||||
createDatabaseClient,
|
||||
createNameOverride,
|
||||
createSchemaOverride,
|
||||
dropDatabase,
|
||||
ensureSchemaExists,
|
||||
parseConnectionString,
|
||||
} from './connection';
|
||||
import { pgConnector } from './connectors';
|
||||
import { mysqlConnector, pgConnector } from './connectors';
|
||||
|
||||
const mocked = (f: Function) => f as jest.Mock;
|
||||
|
||||
@@ -30,8 +31,13 @@ jest.mock('./connectors', () => {
|
||||
const connectors = jest.requireActual('./connectors');
|
||||
return {
|
||||
...connectors,
|
||||
mysqlConnector: {
|
||||
...connectors.mysqlConnector,
|
||||
dropDatabase: jest.fn(),
|
||||
},
|
||||
pgConnector: {
|
||||
...connectors.pgConnector,
|
||||
dropDatabase: jest.fn(),
|
||||
ensureSchemaExists: jest.fn(),
|
||||
},
|
||||
};
|
||||
@@ -229,4 +235,74 @@ describe('database connection', () => {
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dropDatabase', () => {
|
||||
it('returns successfully with pg client', async () => {
|
||||
await dropDatabase(
|
||||
new ConfigReader({
|
||||
client: 'pg',
|
||||
schema: 'catalog',
|
||||
connection: 'postgresql://testuser:testpass@acme:5432/userdbname',
|
||||
}),
|
||||
'backstage_plugin_foobar',
|
||||
);
|
||||
|
||||
const mockCalls = mocked(
|
||||
pgConnector.dropDatabase as Function,
|
||||
).mock.calls.splice(-1);
|
||||
const [baseConfig, databaseName] = mockCalls[0];
|
||||
|
||||
expect(baseConfig.get()).toMatchObject({
|
||||
client: 'pg',
|
||||
connection: 'postgresql://testuser:testpass@acme:5432/userdbname',
|
||||
});
|
||||
|
||||
expect(databaseName).toEqual('backstage_plugin_foobar');
|
||||
});
|
||||
|
||||
it('returns successfully with mysql client', async () => {
|
||||
await dropDatabase(
|
||||
new ConfigReader({
|
||||
client: 'mysql2',
|
||||
connection: {
|
||||
host: '127.0.0.1',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'dbname',
|
||||
},
|
||||
}),
|
||||
'backstage_plugin_foobar',
|
||||
);
|
||||
|
||||
const mockCalls = mocked(
|
||||
mysqlConnector.dropDatabase as Function,
|
||||
).mock.calls.splice(-1);
|
||||
const [baseConfig, databaseName] = mockCalls[0];
|
||||
|
||||
expect(baseConfig.get()).toMatchObject({
|
||||
client: 'mysql2',
|
||||
connection: {
|
||||
host: '127.0.0.1',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'dbname',
|
||||
},
|
||||
});
|
||||
|
||||
expect(databaseName).toEqual('backstage_plugin_foobar');
|
||||
});
|
||||
|
||||
it('does nothing in other database drivers', () => {
|
||||
return expect(
|
||||
dropDatabase(
|
||||
new ConfigReader({
|
||||
client: 'better-sqlite3',
|
||||
schema: 'catalog',
|
||||
connection: ':memory:',
|
||||
}),
|
||||
'catalog',
|
||||
),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,6 +95,22 @@ export async function ensureDatabaseExists(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the given databases.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export async function dropDatabase(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
): Promise<void> {
|
||||
const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
return await ddlLimiter(() =>
|
||||
ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the given schemas all exist, creating them if they do not.
|
||||
*
|
||||
|
||||
@@ -183,6 +183,40 @@ export async function ensureMysqlDatabaseExists(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the given mysql databases.
|
||||
*
|
||||
* @param dbConfig - The database config
|
||||
* @param databases - The names of the databases to create
|
||||
*/
|
||||
export async function dropMysqlDatabase(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
) {
|
||||
const admin = createMysqlDatabaseClient(dbConfig, {
|
||||
connection: {
|
||||
database: null as unknown as string,
|
||||
},
|
||||
pool: {
|
||||
min: 0,
|
||||
acquireTimeoutMillis: 10000,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const dropDatabase = async (database: string) => {
|
||||
await admin.raw(`DROP DATABASE ??`, [database]);
|
||||
};
|
||||
await Promise.all(
|
||||
databases.map(async database => {
|
||||
return await dropDatabase(database);
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await admin.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL database connector.
|
||||
*
|
||||
@@ -193,4 +227,5 @@ export const mysqlConnector: DatabaseConnector = Object.freeze({
|
||||
ensureDatabaseExists: ensureMysqlDatabaseExists,
|
||||
createNameOverride: defaultNameOverride,
|
||||
parseConnectionString: parseMysqlConnectionString,
|
||||
dropDatabase: dropMysqlDatabase,
|
||||
});
|
||||
|
||||
@@ -199,6 +199,24 @@ export async function ensurePgSchemaExists(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the Postgres databases.
|
||||
*
|
||||
* @param dbConfig - The database config
|
||||
* @param databases - The name of the databases to drop
|
||||
*/
|
||||
export async function dropPgDatabase(
|
||||
dbConfig: Config,
|
||||
...databases: Array<string>
|
||||
) {
|
||||
const admin = createPgDatabaseClient(dbConfig);
|
||||
await Promise.all(
|
||||
databases.map(async database => {
|
||||
await admin.raw(`DROP DATABASE ??`, [database]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL database connector.
|
||||
*
|
||||
@@ -211,4 +229,5 @@ export const pgConnector: DatabaseConnector = Object.freeze({
|
||||
createNameOverride: defaultNameOverride,
|
||||
createSchemaOverride: defaultSchemaOverride,
|
||||
parseConnectionString: parsePgConnectionString,
|
||||
dropDatabase: dropPgDatabase,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,11 @@ export * from './DatabaseManager';
|
||||
* Undocumented API surface from connection is being reduced for future deprecation.
|
||||
* Avoid exporting additional symbols.
|
||||
*/
|
||||
export { createDatabaseClient, ensureDatabaseExists } from './connection';
|
||||
export {
|
||||
createDatabaseClient,
|
||||
ensureDatabaseExists,
|
||||
dropDatabase,
|
||||
} from './connection';
|
||||
|
||||
export type { PluginDatabaseManager } from './types';
|
||||
export { isDatabaseConflictError } from './util';
|
||||
|
||||
@@ -79,4 +79,6 @@ export interface DatabaseConnector {
|
||||
dbConfig: Config,
|
||||
...schemas: Array<string>
|
||||
): Promise<void>;
|
||||
|
||||
dropDatabase?(dbConfig: Config, ...databases: Array<string>): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DatabaseManager } from '@backstage/backend-common';
|
||||
import { DatabaseManager, dropDatabase } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Knex } from 'knex';
|
||||
@@ -157,11 +157,13 @@ export class TestDatabases {
|
||||
}
|
||||
|
||||
// Ensure that a unique logical database is created in the instance
|
||||
const databaseName = `db${randomBytes(16).toString('hex')}`;
|
||||
const connection = await instance.databaseManager
|
||||
.forPlugin(`db${randomBytes(16).toString('hex')}`)
|
||||
.forPlugin(databaseName)
|
||||
.getClient();
|
||||
|
||||
instance.connections.push(connection);
|
||||
instance.databaseNames.push(databaseName);
|
||||
|
||||
return connection;
|
||||
}
|
||||
@@ -173,21 +175,30 @@ export class TestDatabases {
|
||||
if (envVarName) {
|
||||
const connectionString = process.env[envVarName];
|
||||
if (connectionString) {
|
||||
const databaseManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
knexConfig: properties.driver.includes('sqlite')
|
||||
? {}
|
||||
: LARGER_POOL_CONFIG,
|
||||
client: properties.driver,
|
||||
connection: connectionString,
|
||||
},
|
||||
const config = new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
knexConfig: properties.driver.includes('sqlite')
|
||||
? {}
|
||||
: LARGER_POOL_CONFIG,
|
||||
client: properties.driver,
|
||||
connection: connectionString,
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
const databaseManager = DatabaseManager.fromConfig(config);
|
||||
const databaseNames: Array<string> = [];
|
||||
return {
|
||||
dropDatabases: async () => {
|
||||
await dropDatabase(
|
||||
config.getConfig('backend.database'),
|
||||
...databaseNames.map(
|
||||
databaseName => `backstage_plugin_${databaseName}`,
|
||||
),
|
||||
);
|
||||
},
|
||||
databaseManager,
|
||||
databaseNames,
|
||||
connections: [],
|
||||
};
|
||||
}
|
||||
@@ -226,10 +237,10 @@ export class TestDatabases {
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
stopContainer: stop,
|
||||
databaseManager,
|
||||
databaseNames: [],
|
||||
connections: [],
|
||||
};
|
||||
}
|
||||
@@ -256,6 +267,7 @@ export class TestDatabases {
|
||||
return {
|
||||
stopContainer: stop,
|
||||
databaseManager,
|
||||
databaseNames: [],
|
||||
connections: [],
|
||||
};
|
||||
}
|
||||
@@ -273,9 +285,9 @@ export class TestDatabases {
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
databaseManager,
|
||||
databaseNames: [],
|
||||
connections: [],
|
||||
};
|
||||
}
|
||||
@@ -284,7 +296,12 @@ export class TestDatabases {
|
||||
const instances = [...this.instanceById.values()];
|
||||
this.instanceById.clear();
|
||||
|
||||
for (const { stopContainer, connections, databaseManager } of instances) {
|
||||
for (const {
|
||||
stopContainer,
|
||||
dropDatabases,
|
||||
connections,
|
||||
databaseManager,
|
||||
} of instances) {
|
||||
for (const connection of connections) {
|
||||
try {
|
||||
await connection.destroy();
|
||||
@@ -296,6 +313,15 @@ export class TestDatabases {
|
||||
}
|
||||
}
|
||||
|
||||
// If the database is not running in docker then drop the databases
|
||||
try {
|
||||
await dropDatabases?.();
|
||||
} catch (error) {
|
||||
console.warn(`TestDatabases: Failed to drop databases`, {
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await stopContainer?.();
|
||||
} catch (error) {
|
||||
|
||||
@@ -43,8 +43,10 @@ export type TestDatabaseProperties = {
|
||||
|
||||
export type Instance = {
|
||||
stopContainer?: () => Promise<void>;
|
||||
dropDatabases?: () => Promise<void>;
|
||||
databaseManager: DatabaseManager;
|
||||
connections: Array<Knex>;
|
||||
databaseNames: Array<string>;
|
||||
};
|
||||
|
||||
export const allDatabases: Record<TestDatabaseId, TestDatabaseProperties> =
|
||||
|
||||
Reference in New Issue
Block a user