From db744f7a5195581434185650279608b57a94e701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 29 Apr 2024 09:52:46 +0200 Subject: [PATCH] clean up manager tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/database/DatabaseManager.test.ts | 808 +----------------- 1 file changed, 16 insertions(+), 792 deletions(-) diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index ba911b68b9..347ec742c0 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -96,804 +96,28 @@ describe('DatabaseManagerImpl', () => { expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined); }); - // eslint-disable-next-line jest/no-commented-out-tests - /* - it('accesses the backend.database key', () => { - const config = new ConfigReader(backendConfig); - const getConfigSpy = jest.spyOn(config, 'getConfig'); - DatabaseManager.fromConfig(config); + it('retains the migration skip info', async () => { + const connector = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; - expect(getConfigSpy).toHaveBeenCalledWith('backend.database'); + const impl1 = new DatabaseManagerImpl(new ConfigReader({ client: 'pg' }), { + pg: connector, }); - it('handles default options', () => { - const config = new ConfigReader(backendConfig); - const database = DatabaseManager.fromConfig(config); - const client = database.forPlugin('test'); + const impl2 = new DatabaseManagerImpl( + new ConfigReader({ client: 'pg' }), + { pg: connector }, + { migrations: { skip: true } }, + ); - expect(client.migrations?.skip).toBe(false); + expect((await impl1.forPlugin('plugin1')).migrations).toEqual({ + skip: false, }); - it('handles migrations options', () => { - const config = new ConfigReader(backendConfig); - const database = DatabaseManager.fromConfig(config, { - migrations: { skip: true }, - }); - const client = database.forPlugin('test'); - - expect(client.migrations?.skip).toBe(true); + expect((await impl2.forPlugin('plugin1')).migrations).toEqual({ + skip: true, }); }); - - describe('DatabaseManager.forPlugin', () => { - const config = { - backend: { - database: { - client: 'pg', - prefix: 'test_prefix_', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - plugin: { - testdbname: { - connection: { - database: 'database_name_overridden', - }, - }, - differentclient: { - client: 'better-sqlite3', - connection: { - filename: 'plugin_with_different_client', - }, - }, - differentclientconnstring: { - client: 'better-sqlite3', - connection: ':memory:', - }, - stringoverride: { - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }, - }, - }, - }, - }; - let manager: DatabaseManager; - - beforeEach(() => { - manager = DatabaseManager.fromConfig(new ConfigReader(config)); - }); - - it('connects to a plugin database using default config', async () => { - const pluginId = 'pluginwithoutconfig'; - - await manager.forPlugin(pluginId).getClient(); - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // default config should be passed through to underlying connector - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: omit(config.backend.database.connection, ['database']), - }); - - // override using database name generated from pluginId and prefix - expect(overrides).toMatchObject({ - connection: { - database: `${config.backend.database.prefix}${pluginId}`, - }, - }); - }); - - it('provides a plugin db which uses components from top level connection string', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: 'postgresql://foo:bar@acme:5432/foodb', - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // parsed connection string **without** db name should be passed through - expect(baseConfig.get()).toMatchObject({ - connection: { - host: 'acme', - user: 'foo', - password: 'bar', - port: '5432', - application_name: 'backstage_plugin_pluginwithoutconfig', - }, - }); - - // we expect a pg database name override with ${prefix} followed by pluginId - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('pluginwithoutconfig'), - ); - }); - - it('provides an inmemory sqlite database if top level is also inmemory and plugin config is not present', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - expect.stringContaining(':memory:'), - ); - }); - - it('throws if top level sqlite filename is provided', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: 'some-file-path', - }, - }, - }), - ); - - await expect( - testManager.forPlugin('pluginwithoutconfig').getClient(), - ).rejects.toBeInstanceOf(Error); - }); - - it('creates plugin-specific sqlite files when plugin config is not present', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('sqlite-files', 'pluginwithoutconfig.sqlite'), - ); - }); - - it('uses sqlite directory from top level config and filename from plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - plugin: { - test: { - connection: { - filename: 'other.sqlite', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('test').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('sqlite-files', 'other.sqlite'), - ); - }); - - it('uses sqlite directory and filename from plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - plugin: { - test: { - connection: { - directory: 'custom-sqlite-files', - filename: 'other.sqlite', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('test').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('custom-sqlite-files', 'other.sqlite'), - ); - }); - - it('connects to a plugin database using a specific database name', async () => { - // testdbname.connection.database is set in config - await manager.forPlugin('testdbname').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - // simple case where only database name is overridden - expect(overrides).toMatchObject({ - connection: { - database: 'database_name_overridden', - }, - }); - }); - - it('ensure plugin specific database is created', async () => { - const pluginId = 'testdbname'; - // testdbname.connection.database is set in config - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1); - const [_, dbname] = mockCalls[0]; - - expect(dbname).toEqual( - config.backend.database.plugin[pluginId].connection.database, - ); - }); - - it('provides different plugins with their own databases', async () => { - await manager.forPlugin('plugin1').getClient(); - await manager.forPlugin('plugin2').getClient(); - - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(2); - - const mockCalls = mocked(createDatabaseClient).mock.calls; - const [plugin1CallArgs, plugin2CallArgs] = mockCalls; - - // database name overrides should be different - expect(plugin1CallArgs[1].connection.database).not.toEqual( - plugin2CallArgs[1].connection.database, - ); - }); - - it('returns the same client for the same pluginId', async () => { - const [client1, client2] = await Promise.all([ - manager.forPlugin('plugin1').getClient(), - manager.forPlugin('plugin1').getClient(), - ]); - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); - - expect(client1).toBe(client2); - }); - - it('uses plugin connection as base if default client is different from plugin client', async () => { - const pluginId = 'differentclient'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _overrides] = mockCalls[0]; - - // plugin connection should be used as base config, client is different - expect(baseConfig.get()).toMatchObject({ - client: 'better-sqlite3', - connection: config.backend.database.plugin[pluginId].connection, - }); - }); - - it('provides database client specific base and override when client set under plugin', async () => { - const pluginId = 'differentclient'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // plugin client should be better-sqlite3 - expect(baseConfig.get().client).toEqual('better-sqlite3'); - - // SQLite uses 'filename' instead of 'database' - expect(overrides).toHaveProperty( - 'connection.filename', - 'plugin_with_different_client', - ); - }); - - it('provides database client specific base from plugin connection string when client set under plugin', async () => { - const pluginId = 'differentclientconnstring'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get().client).toEqual('better-sqlite3'); - - expect(overrides).toHaveProperty('connection.filename', ':memory:'); - }); - - it('generates a database name override when prefix is not explicitly set', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('backstage_plugin_'), - ); - }); - - it('generates a database name override when prefix is not explicitly set for mysql', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'mysql', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('backstage_plugin_'), - ); - }); - - it('uses values from plugin connection string if top level client should be used', async () => { - const pluginId = 'stringoverride'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // plugin client should be pg - expect(baseConfig.get().client).toEqual('pg'); - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('userdbname'), - ); - }); - - it('plugin sets schema override for pg client', async () => { - const overrideConfig = { - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }; - const testManager = DatabaseManager.fromConfig( - new ConfigReader(overrideConfig), - ); - const pluginId = 'schemaoverride'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: config.backend.database.connection, - }); - - expect(overrides).toMatchObject({ - searchPath: [pluginId], - }); - }); - - it('plugin does not provide schema override for non pg client', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'any-plugin'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'better-sqlite3', - connection: config.backend.database.connection, - }); - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('plugin does not provide schema override if pluginDivisionMode is set to database', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'database', - connection: 'some-file-path', - }, - }, - }), - ); - - const pluginId = 'any-plugin'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('plugin does not provide schema override if pluginDivisionMode is not set', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - const pluginId = 'schemaoverride'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('pluginDivisionMode ensures that each plugin schema exists', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(ensureSchemaExists).mock.calls; - const [_, schemaName] = mockCalls[0]; - - expect(schemaName).toEqual('testdbname'); - }); - - it('pluginDivisionMode allows connection overrides for plugins', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - plugin: { - testdbname: { - connection: { - database: 'database_name_overridden', - host: 'newhost', - }, - }, - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: { - database: 'database_name_overridden', - host: 'newhost', - user: 'foo', - password: 'bar', - }, - }); - expect(overrides).toHaveProperty('searchPath', ['testdbname']); - expect(overrides).toHaveProperty( - 'connection.database', - 'database_name_overridden', - ); - }); - - it('ensureExists does not create database or schema when false', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - ensureExists: false, - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - expect(mocked(ensureDatabaseExists)).toHaveBeenCalledTimes(0); - expect(mocked(ensureSchemaExists)).toHaveBeenCalledTimes(0); - }); - - it('fetches and merges additional knex config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - knexConfig: { - something: false, - }, - plugin: { - testdbname: { - knexConfig: { - debug: true, - }, - }, - }, - }, - }, - }), - ); - await testManager.forPlugin('testdbname').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data).toEqual( - expect.objectContaining({ - debug: true, - something: false, - }), - ); - }); - - it('sets the owner config for plugin using default config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - role: 'backstage', - plugin: { - testowner: {}, - }, - }, - }, - }), - ); - await testManager.forPlugin('testowner').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data.role).toEqual('backstage'); - }); - - it('sets the owner config for plugin using plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - role: 'backstage', - plugin: { - testowner: { - role: 'backstage-plugin', - }, - }, - }, - }, - }), - ); - await testManager.forPlugin('testowner').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data.role).toEqual('backstage-plugin'); - }); - - it('Defaults the application_name for postgres clients', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: {}, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _] = mockCalls[0]; - expect(baseConfig.get()).toMatchObject({ - connection: { - application_name: 'backstage_plugin_testplugin', - }, - }); - }); - - it('Allows manually setting the application_name for postgres clients', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - application_name: 'backstage_custom_app_name', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - expect(baseConfig.get().connection.application_name).toBe( - 'backstage_custom_app_name', - ); - expect(overrides.connection.application_name).toBeUndefined(); - }); - - it('Allows manually setting the application_name for individual plugin client', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - application_name: 'backstage_custom_app_name', - }, - plugin: { - overrideplugin: { - connection: { - application_name: 'custom_plugin', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('overrideplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _] = mockCalls[0]; - expect(baseConfig.get().connection.application_name).toBe( - 'custom_plugin', - ); - }); - }); - */ });