From bdd4b973e8ca12a46ba3e72b108db12fe0cf31e7 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 3 Jan 2023 15:27:02 +0100 Subject: [PATCH 01/14] chore: provide all core services to the `TestBackend` Signed-off-by: blam Signed-off-by: blam --- .../src/next/implementations/index.ts | 16 ++++++++ .../mockTokenManagerService.ts | 37 +++++++++++++++++++ .../src/next/wiring/TestBackend.test.ts | 37 +++++++++++++++++++ .../src/next/wiring/TestBackend.ts | 22 ++++++++++- 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 packages/backend-test-utils/src/next/implementations/index.ts create mode 100644 packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts new file mode 100644 index 0000000000..073e47f5e4 --- /dev/null +++ b/packages/backend-test-utils/src/next/implementations/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { mockTokenManagerFactory } from './mockTokenManagerService'; diff --git a/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts new file mode 100644 index 0000000000..f68e910e0c --- /dev/null +++ b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TokenManager } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; + +class TokenManagerMock implements TokenManager { + async getToken(): Promise<{ token: string }> { + return { token: 'mock-token' }; + } + async authenticate(): Promise {} +} + +export const mockTokenManagerFactory = createServiceFactory({ + service: coreServices.tokenManager, + deps: {}, + async factory() { + return async () => { + return new TokenManagerMock(); + }; + }, +}); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 5ea37e9106..9d71005e18 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -20,7 +20,9 @@ import { createServiceFactory, createServiceRef, coreServices, + createBackendPlugin, } from '@backstage/backend-plugin-api'; + import { startTestBackend } from './TestBackend'; // This bit makes sure that test backends are cleaned up properly @@ -156,4 +158,39 @@ describe('TestBackend', () => { await backend.stop(); expect(shutdownSpy).toHaveBeenCalled(); }); + + it('should provide a set of default services', async () => { + expect.assertions(2); + + const testPlugin = createBackendPlugin({ + id: 'test', + register(env) { + env.registerInit({ + deps: { + cache: coreServices.cache, + config: coreServices.config, + database: coreServices.database, + discovery: coreServices.discovery, + lifecycle: coreServices.lifecycle, + logger: coreServices.logger, + permissions: coreServices.permissions, + rootLifecycle: coreServices.rootLifecycle, + rootLogger: coreServices.rootLogger, + scheduler: coreServices.scheduler, + tokenManager: coreServices.tokenManager, + urlReader: coreServices.urlReader, + }, + async init(deps) { + expect(Object.keys(deps)).toHaveLength(12); + expect(Object.values(deps)).not.toContain(undefined); + }, + }); + }, + }); + + await startTestBackend({ + services: [], + features: [testPlugin()], + }).then(backend => backend.stop()); + }); }); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 0295332855..7b78ad3d95 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -21,7 +21,15 @@ import { rootLifecycleFactory, loggerFactory, rootLoggerFactory, + configFactory, + discoveryFactory, + cacheFactory, + databaseFactory, + permissionsFactory, + schedulerFactory, + urlReaderFactory, } from '@backstage/backend-app-api'; + import { ServiceFactory, ServiceRef, @@ -30,6 +38,8 @@ import { ExtensionPoint, } from '@backstage/backend-plugin-api'; +import { mockTokenManagerFactory } from '../implementations'; + /** @alpha */ export interface TestBackendOptions< TServices extends any[], @@ -55,10 +65,18 @@ export interface TestBackendOptions< } const defaultServiceFactories = [ - rootLoggerFactory(), - loggerFactory(), + cacheFactory(), + configFactory(), + databaseFactory(), + discoveryFactory(), lifecycleFactory(), + loggerFactory(), + permissionsFactory(), rootLifecycleFactory(), + rootLoggerFactory(), + schedulerFactory(), + mockTokenManagerFactory(), + urlReaderFactory(), ]; const backendInstancesToCleanUp = new Array(); From beed5864d0d94fb7660459ec3b6d70d9e5fee692 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 3 Jan 2023 17:02:35 +0100 Subject: [PATCH 02/14] chore: added some more mock defintions and refactoring the `TaskManager.forPlugin` Signed-off-by: blam --- .../scheduler/schedulerFactory.ts | 10 +++- .../backend-tasks/src/tasks/TaskScheduler.ts | 23 ++++++---- .../src/next/implementations/index.ts | 3 ++ .../next/implementations/mockConfigService.ts | 30 ++++++++++++ .../implementations/mockDatabaseService.ts | 46 +++++++++++++++++++ .../implementations/mockDiscoveryService.ts | 40 ++++++++++++++++ .../src/next/wiring/TestBackend.ts | 16 ++++--- 7 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 packages/backend-test-utils/src/next/implementations/mockConfigService.ts create mode 100644 packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts create mode 100644 packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts index a6edec868a..692f05f6d7 100644 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts +++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts @@ -17,6 +17,7 @@ import { coreServices, createServiceFactory, + loggerToWinstonLogger, } from '@backstage/backend-plugin-api'; import { TaskScheduler } from '@backstage/backend-tasks'; @@ -26,11 +27,16 @@ export const schedulerFactory = createServiceFactory({ deps: { config: coreServices.config, plugin: coreServices.pluginMetadata, + databaseManager: coreServices.database, + logger: coreServices.logger, }, async factory({ config }) { const taskScheduler = TaskScheduler.fromConfig(config); - return async ({ plugin }) => { - return taskScheduler.forPlugin(plugin.getId()); + return async ({ plugin, databaseManager, logger }) => { + return taskScheduler.forPlugin(plugin.getId(), { + databaseManager, + logger: loggerToWinstonLogger(logger), + }); }; }, }); diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index f14fb9fc2b..f35669d76e 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { DatabaseManager, getRootLogger } from '@backstage/backend-common'; +import { + DatabaseManager, + getRootLogger, + PluginDatabaseManager, +} from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { once } from 'lodash'; import { Duration } from 'luxon'; @@ -56,9 +60,15 @@ export class TaskScheduler { * @param pluginId - The unique ID of the plugin, for example "catalog" * @returns A {@link PluginTaskScheduler} instance */ - forPlugin(pluginId: string): PluginTaskScheduler { + forPlugin( + pluginId: string, + options?: { databaseManager?: PluginDatabaseManager; logger?: Logger }, + ): PluginTaskScheduler { + const databaseManager = + options?.databaseManager ?? this.databaseManager.forPlugin(pluginId); + const logger = options?.logger ?? this.logger.child({ plugin: pluginId }); + const databaseFactory = once(async () => { - const databaseManager = this.databaseManager.forPlugin(pluginId); const knex = await databaseManager.getClient(); if (!databaseManager.migrations?.skip) { @@ -68,16 +78,13 @@ export class TaskScheduler { const janitor = new PluginTaskSchedulerJanitor({ knex, waitBetweenRuns: Duration.fromObject({ minutes: 1 }), - logger: this.logger, + logger, }); janitor.start(); return knex; }); - return new PluginTaskSchedulerImpl( - databaseFactory, - this.logger.child({ plugin: pluginId }), - ); + return new PluginTaskSchedulerImpl(databaseFactory, logger); } } diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts index 073e47f5e4..ba8bb5fb5e 100644 --- a/packages/backend-test-utils/src/next/implementations/index.ts +++ b/packages/backend-test-utils/src/next/implementations/index.ts @@ -14,3 +14,6 @@ * limitations under the License. */ export { mockTokenManagerFactory } from './mockTokenManagerService'; +export { mockConfigFactory } from './mockConfigService'; +export { mockDatabaseFactory } from './mockDatabaseService'; +export { mockDiscoveryFactory } from './mockDiscoveryService'; diff --git a/packages/backend-test-utils/src/next/implementations/mockConfigService.ts b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts new file mode 100644 index 0000000000..976167b473 --- /dev/null +++ b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { AppConfig, ConfigReader } from '@backstage/config'; + +/** @public */ +export const mockConfigFactory = createServiceFactory({ + service: coreServices.config, + deps: {}, + async factory(_, options?: { config?: AppConfig }) { + return new ConfigReader(options?.config); + }, +}); diff --git a/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts b/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts new file mode 100644 index 0000000000..b0d67c1f1e --- /dev/null +++ b/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DatabaseManager } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { ConfigReader } from '@backstage/config'; + +/** @public */ +export const mockDatabaseFactory = createServiceFactory({ + service: coreServices.database, + deps: { + config: coreServices.config, + plugin: coreServices.pluginMetadata, + }, + async factory({ config }) { + const databaseManager = config.getOptional('backend.database') + ? DatabaseManager.fromConfig(config) + : DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { client: 'better-sqlite', connection: ':memory:' }, + }, + }), + ); + + return async ({ plugin }) => { + return databaseManager.forPlugin(plugin.getId()); + }; + }, +}); diff --git a/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts b/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts new file mode 100644 index 0000000000..3af69187e8 --- /dev/null +++ b/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SingleHostDiscovery } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { ConfigReader } from '@backstage/config'; + +/** @public */ +export const mockDiscoveryFactory = createServiceFactory({ + service: coreServices.discovery, + deps: {}, + async factory() { + // todo(blam): we want to grab the port from the httpRouter when that's available here + // to provide a better way to create our mockDiscoveryService. + const discovery = SingleHostDiscovery.fromConfig( + new ConfigReader({ + backend: { baseUrl: 'http://localhost:7000', listen: '0.0.0.0:7000' }, + }), + ); + + return async () => { + return discovery; + }; + }, +}); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 7b78ad3d95..a4ac8be7a2 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -21,10 +21,7 @@ import { rootLifecycleFactory, loggerFactory, rootLoggerFactory, - configFactory, - discoveryFactory, cacheFactory, - databaseFactory, permissionsFactory, schedulerFactory, urlReaderFactory, @@ -38,7 +35,12 @@ import { ExtensionPoint, } from '@backstage/backend-plugin-api'; -import { mockTokenManagerFactory } from '../implementations'; +import { + mockConfigFactory, + mockDatabaseFactory, + mockTokenManagerFactory, + mockDiscoveryFactory, +} from '../implementations'; /** @alpha */ export interface TestBackendOptions< @@ -66,9 +68,9 @@ export interface TestBackendOptions< const defaultServiceFactories = [ cacheFactory(), - configFactory(), - databaseFactory(), - discoveryFactory(), + mockConfigFactory(), + mockDatabaseFactory(), + mockDiscoveryFactory(), lifecycleFactory(), loggerFactory(), permissionsFactory(), From 9ec23f2d7c425a3eb11700e96daad89245768072 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 4 Jan 2023 16:23:03 +0100 Subject: [PATCH 03/14] chore: revert the TaskScheduler Signed-off-by: blam --- .../backend-tasks/src/tasks/TaskScheduler.ts | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index f35669d76e..f14fb9fc2b 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - DatabaseManager, - getRootLogger, - PluginDatabaseManager, -} from '@backstage/backend-common'; +import { DatabaseManager, getRootLogger } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { once } from 'lodash'; import { Duration } from 'luxon'; @@ -60,15 +56,9 @@ export class TaskScheduler { * @param pluginId - The unique ID of the plugin, for example "catalog" * @returns A {@link PluginTaskScheduler} instance */ - forPlugin( - pluginId: string, - options?: { databaseManager?: PluginDatabaseManager; logger?: Logger }, - ): PluginTaskScheduler { - const databaseManager = - options?.databaseManager ?? this.databaseManager.forPlugin(pluginId); - const logger = options?.logger ?? this.logger.child({ plugin: pluginId }); - + forPlugin(pluginId: string): PluginTaskScheduler { const databaseFactory = once(async () => { + const databaseManager = this.databaseManager.forPlugin(pluginId); const knex = await databaseManager.getClient(); if (!databaseManager.migrations?.skip) { @@ -78,13 +68,16 @@ export class TaskScheduler { const janitor = new PluginTaskSchedulerJanitor({ knex, waitBetweenRuns: Duration.fromObject({ minutes: 1 }), - logger, + logger: this.logger, }); janitor.start(); return knex; }); - return new PluginTaskSchedulerImpl(databaseFactory, logger); + return new PluginTaskSchedulerImpl( + databaseFactory, + this.logger.child({ plugin: pluginId }), + ); } } From 875cd848fdf22e4eae6473f13efe90403ad27aad Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 4 Jan 2023 16:31:29 +0100 Subject: [PATCH 04/14] chore: reworking how we do the TaskScheduler for now Signed-off-by: blam Signed-off-by: Patrik Oldsberg --- .../database/databaseFactory.ts | 12 ++++- .../scheduler/schedulerFactory.ts | 9 ++-- .../backend-tasks/src/tasks/TaskScheduler.ts | 32 +++++++++---- .../src/next/implementations/index.ts | 1 - .../implementations/mockDatabaseService.ts | 46 ------------------- .../implementations/mockDiscoveryService.ts | 2 +- .../src/next/wiring/TestBackend.test.ts | 5 +- .../src/next/wiring/TestBackend.ts | 14 ++++-- 8 files changed, 51 insertions(+), 70 deletions(-) delete mode 100644 packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts diff --git a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts index f33e825c9b..52cecdbc4d 100644 --- a/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts +++ b/packages/backend-app-api/src/services/implementations/database/databaseFactory.ts @@ -19,6 +19,7 @@ import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; +import { ConfigReader } from '@backstage/config'; /** @public */ export const databaseFactory = createServiceFactory({ @@ -28,7 +29,16 @@ export const databaseFactory = createServiceFactory({ plugin: coreServices.pluginMetadata, }, async factory({ config }) { - const databaseManager = DatabaseManager.fromConfig(config); + const databaseManager = config.getOptional('backend.database') + ? DatabaseManager.fromConfig(config) + : DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { client: 'better-sqlite3', connection: ':memory:' }, + }, + }), + ); + return async ({ plugin }) => { return databaseManager.forPlugin(plugin.getId()); }; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts index 692f05f6d7..4e472b78f4 100644 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts +++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerFactory.ts @@ -14,10 +14,10 @@ * limitations under the License. */ +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { coreServices, createServiceFactory, - loggerToWinstonLogger, } from '@backstage/backend-plugin-api'; import { TaskScheduler } from '@backstage/backend-tasks'; @@ -25,15 +25,14 @@ import { TaskScheduler } from '@backstage/backend-tasks'; export const schedulerFactory = createServiceFactory({ service: coreServices.scheduler, deps: { - config: coreServices.config, plugin: coreServices.pluginMetadata, databaseManager: coreServices.database, logger: coreServices.logger, }, - async factory({ config }) { - const taskScheduler = TaskScheduler.fromConfig(config); + async factory() { return async ({ plugin, databaseManager, logger }) => { - return taskScheduler.forPlugin(plugin.getId(), { + return TaskScheduler.forPlugin({ + pluginId: plugin.getId(), databaseManager, logger: loggerToWinstonLogger(logger), }); diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index f14fb9fc2b..fe81a0054e 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { DatabaseManager, getRootLogger } from '@backstage/backend-common'; +import { + DatabaseManager, + getRootLogger, + PluginDatabaseManager, +} from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { once } from 'lodash'; import { Duration } from 'luxon'; @@ -57,27 +61,35 @@ export class TaskScheduler { * @returns A {@link PluginTaskScheduler} instance */ forPlugin(pluginId: string): PluginTaskScheduler { - const databaseFactory = once(async () => { - const databaseManager = this.databaseManager.forPlugin(pluginId); - const knex = await databaseManager.getClient(); + return TaskScheduler.forPlugin({ + pluginId, + databaseManager: this.databaseManager.forPlugin(pluginId), + logger: this.logger, + }); + } - if (!databaseManager.migrations?.skip) { + static forPlugin(opts: { + pluginId: string; + databaseManager: PluginDatabaseManager; + logger: Logger; + }): PluginTaskScheduler { + const databaseFactory = once(async () => { + const knex = await opts.databaseManager.getClient(); + + if (!opts.databaseManager.migrations?.skip) { await migrateBackendTasks(knex); } const janitor = new PluginTaskSchedulerJanitor({ knex, waitBetweenRuns: Duration.fromObject({ minutes: 1 }), - logger: this.logger, + logger: opts.logger, }); janitor.start(); return knex; }); - return new PluginTaskSchedulerImpl( - databaseFactory, - this.logger.child({ plugin: pluginId }), - ); + return new PluginTaskSchedulerImpl(databaseFactory, opts.logger); } } diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts index ba8bb5fb5e..c248502307 100644 --- a/packages/backend-test-utils/src/next/implementations/index.ts +++ b/packages/backend-test-utils/src/next/implementations/index.ts @@ -15,5 +15,4 @@ */ export { mockTokenManagerFactory } from './mockTokenManagerService'; export { mockConfigFactory } from './mockConfigService'; -export { mockDatabaseFactory } from './mockDatabaseService'; export { mockDiscoveryFactory } from './mockDiscoveryService'; diff --git a/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts b/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts deleted file mode 100644 index b0d67c1f1e..0000000000 --- a/packages/backend-test-utils/src/next/implementations/mockDatabaseService.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatabaseManager } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; - -/** @public */ -export const mockDatabaseFactory = createServiceFactory({ - service: coreServices.database, - deps: { - config: coreServices.config, - plugin: coreServices.pluginMetadata, - }, - async factory({ config }) { - const databaseManager = config.getOptional('backend.database') - ? DatabaseManager.fromConfig(config) - : DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite', connection: ':memory:' }, - }, - }), - ); - - return async ({ plugin }) => { - return databaseManager.forPlugin(plugin.getId()); - }; - }, -}); diff --git a/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts b/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts index 3af69187e8..e382d23cbd 100644 --- a/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts +++ b/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts @@ -29,7 +29,7 @@ export const mockDiscoveryFactory = createServiceFactory({ // to provide a better way to create our mockDiscoveryService. const discovery = SingleHostDiscovery.fromConfig( new ConfigReader({ - backend: { baseUrl: 'http://localhost:7000', listen: '0.0.0.0:7000' }, + backend: { baseUrl: 'http://localhost:7007', listen: '0.0.0.0' }, }), ); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 9d71005e18..4c7362880e 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -171,9 +171,12 @@ describe('TestBackend', () => { config: coreServices.config, database: coreServices.database, discovery: coreServices.discovery, + httpRouter: coreServices.httpRouter, lifecycle: coreServices.lifecycle, logger: coreServices.logger, permissions: coreServices.permissions, + pluginMetadata: coreServices.pluginMetadata, + rootHttpRouter: coreServices.rootHttpRouter, rootLifecycle: coreServices.rootLifecycle, rootLogger: coreServices.rootLogger, scheduler: coreServices.scheduler, @@ -181,7 +184,7 @@ describe('TestBackend', () => { urlReader: coreServices.urlReader, }, async init(deps) { - expect(Object.keys(deps)).toHaveLength(12); + expect(Object.keys(deps)).toHaveLength(14); expect(Object.values(deps)).not.toContain(undefined); }, }); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index a4ac8be7a2..35f138091d 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -25,6 +25,9 @@ import { permissionsFactory, schedulerFactory, urlReaderFactory, + databaseFactory, + rootHttpRouterFactory, + httpRouterFactory, } from '@backstage/backend-app-api'; import { @@ -37,7 +40,6 @@ import { import { mockConfigFactory, - mockDatabaseFactory, mockTokenManagerFactory, mockDiscoveryFactory, } from '../implementations'; @@ -68,16 +70,18 @@ export interface TestBackendOptions< const defaultServiceFactories = [ cacheFactory(), - mockConfigFactory(), - mockDatabaseFactory(), - mockDiscoveryFactory(), + databaseFactory(), + httpRouterFactory(), lifecycleFactory(), loggerFactory(), + mockConfigFactory(), + mockDiscoveryFactory(), + mockTokenManagerFactory(), permissionsFactory(), + rootHttpRouterFactory(), rootLifecycleFactory(), rootLoggerFactory(), schedulerFactory(), - mockTokenManagerFactory(), urlReaderFactory(), ]; From 8f9d0c8fca689455305ba802bd727a8568c59fb4 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 4 Jan 2023 17:16:11 +0100 Subject: [PATCH 05/14] chore: explore some of the options for the httpRouter and fix the shutdown in mutliple tests Co-authored-by: Patrik Oldsberg Co-authored-by: Johan Haals Signed-off-by: blam --- packages/backend-test-utils/package.json | 6 +- .../src/next/implementations/index.ts | 1 - .../implementations/mockDiscoveryService.ts | 40 --------- .../src/next/wiring/TestBackend.test.ts | 30 ++++++- .../src/next/wiring/TestBackend.ts | 89 ++++++++++++++++--- yarn.lock | 4 + 6 files changed, 115 insertions(+), 55 deletions(-) delete mode 100644 packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 38db9d6413..ae96f17b8e 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -40,6 +40,8 @@ "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", "better-sqlite3": "^8.0.0", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", "knex": "^2.0.0", "msw": "^0.49.0", "mysql2": "^2.2.5", @@ -48,7 +50,9 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "workspace:^" + "@backstage/cli": "workspace:^", + "@types/supertest": "^2.0.8", + "supertest": "^6.1.3" }, "files": [ "dist", diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts index c248502307..2068aebec5 100644 --- a/packages/backend-test-utils/src/next/implementations/index.ts +++ b/packages/backend-test-utils/src/next/implementations/index.ts @@ -15,4 +15,3 @@ */ export { mockTokenManagerFactory } from './mockTokenManagerService'; export { mockConfigFactory } from './mockConfigService'; -export { mockDiscoveryFactory } from './mockDiscoveryService'; diff --git a/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts b/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts deleted file mode 100644 index e382d23cbd..0000000000 --- a/packages/backend-test-utils/src/next/implementations/mockDiscoveryService.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { SingleHostDiscovery } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; - -/** @public */ -export const mockDiscoveryFactory = createServiceFactory({ - service: coreServices.discovery, - deps: {}, - async factory() { - // todo(blam): we want to grab the port from the httpRouter when that's available here - // to provide a better way to create our mockDiscoveryService. - const discovery = SingleHostDiscovery.fromConfig( - new ConfigReader({ - backend: { baseUrl: 'http://localhost:7007', listen: '0.0.0.0' }, - }), - ); - - return async () => { - return discovery; - }; - }, -}); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 4c7362880e..6358051c34 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -22,6 +22,8 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; +import { Router } from 'express'; +import request from 'supertest'; import { startTestBackend } from './TestBackend'; @@ -184,7 +186,7 @@ describe('TestBackend', () => { urlReader: coreServices.urlReader, }, async init(deps) { - expect(Object.keys(deps)).toHaveLength(14); + expect(Object.keys(deps)).toHaveLength(15); expect(Object.values(deps)).not.toContain(undefined); }, }); @@ -194,6 +196,30 @@ describe('TestBackend', () => { await startTestBackend({ services: [], features: [testPlugin()], - }).then(backend => backend.stop()); + }); + }); + + it('should allow making requests via supertest', async () => { + const testPlugin = createBackendPlugin({ + id: 'test', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + }, + async init({ httpRouter }) { + const router = Router(); + router.use('/ping-me', (_, res) => res.json({ message: 'pong' })); + httpRouter.use(router); + }, + }); + }, + }); + + const { server } = await startTestBackend({ features: [testPlugin()] }); + + const res = await request(server).get('/api/test/ping-me'); + expect(res.status).toEqual(200); + expect(res.body).toEqual({ message: 'pong' }); }); }); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 35f138091d..933257426e 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -26,23 +26,27 @@ import { schedulerFactory, urlReaderFactory, databaseFactory, - rootHttpRouterFactory, httpRouterFactory, } from '@backstage/backend-app-api'; - +import { + createServiceBuilder, + SingleHostDiscovery, +} from '@backstage/backend-common'; +import { Handler } from 'express'; +import * as http from 'http'; +import Router from 'express-promise-router'; import { ServiceFactory, ServiceRef, createServiceFactory, BackendFeature, ExtensionPoint, + coreServices, } from '@backstage/backend-plugin-api'; -import { - mockConfigFactory, - mockTokenManagerFactory, - mockDiscoveryFactory, -} from '../implementations'; +import { mockConfigFactory, mockTokenManagerFactory } from '../implementations'; +import { AddressInfo } from 'net'; +import { ConfigReader } from '@backstage/config'; /** @alpha */ export interface TestBackendOptions< @@ -75,10 +79,8 @@ const defaultServiceFactories = [ lifecycleFactory(), loggerFactory(), mockConfigFactory(), - mockDiscoveryFactory(), mockTokenManagerFactory(), permissionsFactory(), - rootHttpRouterFactory(), rootLifecycleFactory(), rootLoggerFactory(), schedulerFactory(), @@ -99,6 +101,71 @@ export async function startTestBackend< ...otherOptions } = options; + let server: http.Server; + + const rootHttpRouterFactory = createServiceFactory({ + service: coreServices.rootHttpRouter, + deps: { + config: coreServices.config, + lifecycle: coreServices.rootLifecycle, + }, + async factory({ config, lifecycle }) { + const router = Router(); + + const service = createServiceBuilder(module) + .loadConfig(config) + .setPort(0); + + service.addRouter('', router); + + server = await service.start(); + // Stop method isn't part of the public API, let's fix that once we move the implementation here. + const stoppableServer = server as typeof server & { + stop: (cb: (error?: Error) => void) => void; + }; + + lifecycle.addShutdownHook({ + async fn() { + await new Promise((resolve, reject) => { + stoppableServer.stop((error?: Error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + }, + labels: { service: 'rootHttpRouter' }, + }); + + return { + use: (path: string, handler: Handler) => { + router.use(path, handler); + }, + }; + }, + }); + + const discoveryFactory = createServiceFactory({ + service: coreServices.discovery, + deps: { + rootHttpRouter: coreServices.rootHttpRouter, + }, + async factory() { + if (!server) { + throw new Error('Test server not started yet'); + } + const { port } = server.address() as AddressInfo; + const discovery = SingleHostDiscovery.fromConfig( + new ConfigReader({ + backend: { baseUrl: `http://localhost:${port}`, listen: { port } }, + }), + ); + return async () => discovery; + }, + }); + const factories = services.map(serviceDef => { if (Array.isArray(serviceDef)) { // if type is ExtensionPoint? @@ -131,7 +198,7 @@ export async function startTestBackend< const backend = createSpecializedBackend({ ...otherOptions, - services: factories, + services: [...factories, rootHttpRouterFactory, discoveryFactory], }); backendInstancesToCleanUp.push(backend); @@ -153,7 +220,7 @@ export async function startTestBackend< await backend.start(); - return backend; + return Object.assign(backend, { server: server! }) as Backend; } let registered = false; diff --git a/yarn.lock b/yarn.lock index 693b5cc3dd..f8957bfc35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3558,11 +3558,15 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@types/supertest": ^2.0.8 better-sqlite3: ^8.0.0 + express: ^4.17.1 + express-promise-router: ^4.1.0 knex: ^2.0.0 msw: ^0.49.0 mysql2: ^2.2.5 pg: ^8.3.0 + supertest: ^6.1.3 testcontainers: ^8.1.2 uuid: ^8.0.0 languageName: unknown From df6a5a9264868bebc39d5cb329d06685ac190d46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:06:04 +0100 Subject: [PATCH 06/14] backend-app-api: refactor RestrictedIndexedRouter -> DefaultRootHttpRouter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-app-api/api-report.md | 16 +++++++ ....test.ts => DefaultRootHttpRouter.test.ts} | 14 ++++-- ...exedRouter.ts => DefaultRootHttpRouter.ts} | 43 +++++++++++++++++-- .../implementations/rootHttpRouter/index.ts | 12 ++++-- .../rootHttpRouter/rootHttpRouterFactory.ts | 8 ++-- 5 files changed, 77 insertions(+), 16 deletions(-) rename packages/backend-app-api/src/services/implementations/rootHttpRouter/{RestrictedIndexedRouter.test.ts => DefaultRootHttpRouter.test.ts} (76%) rename packages/backend-app-api/src/services/implementations/rootHttpRouter/{RestrictedIndexedRouter.ts => DefaultRootHttpRouter.ts} (62%) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index a8f077b760..b1356e93e3 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -12,6 +12,7 @@ import { CorsOptions } from 'cors'; import { ErrorRequestHandler } from 'express'; import { Express as Express_2 } from 'express'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { Handler } from 'express'; import { HelmetOptions } from 'helmet'; import * as http from 'http'; import { HttpRouterService } from '@backstage/backend-plugin-api'; @@ -79,6 +80,21 @@ export const databaseFactory: ( options?: undefined, ) => ServiceFactory; +// @public +export class DefaultRootHttpRouter implements RootHttpRouterService { + // (undocumented) + static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter; + // (undocumented) + handler(): Handler; + // (undocumented) + use(path: string, handler: Handler): void; +} + +// @public +export interface DefaultRootHttpRouterOptions { + indexPath?: string | false; +} + // @public (undocumented) export const discoveryFactory: ( options?: undefined, diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts similarity index 76% rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts index b72e87f77b..b72b30e02e 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.test.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { RestrictedIndexedRouter } from './RestrictedIndexedRouter'; +import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; -describe('RestrictedIndexedRouter', () => { +describe('DefaultRootHttpRouter', () => { it.each([ [['/b'], '/a'], [['/a'], '/aa/b'], @@ -25,7 +25,7 @@ describe('RestrictedIndexedRouter', () => { [['/b/a'], '/a'], [['/a'], '/aa'], ])(`with existing paths %s, adds %s without conflict`, (existing, added) => { - const router = new RestrictedIndexedRouter(false); + const router = DefaultRootHttpRouter.create(); for (const path of existing) { router.use(path, () => {}); } @@ -39,7 +39,7 @@ describe('RestrictedIndexedRouter', () => { ])( `find conflict when existing paths %s, adds %s`, (existing, added, conflict) => { - const router = new RestrictedIndexedRouter(false); + const router = DefaultRootHttpRouter.create(); for (const path of existing) { router.use(path, () => {}); } @@ -48,4 +48,10 @@ describe('RestrictedIndexedRouter', () => { ); }, ); + + it('should not be possible to supply an empty indexPath', () => { + expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow( + 'indexPath option may not be an empty string', + ); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts similarity index 62% rename from packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts rename to packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts index 961277f34d..2acbb3833a 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/RestrictedIndexedRouter.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts @@ -21,18 +21,53 @@ function normalizePath(path: string): string { return path.replace(/\/*$/, '/'); } -export class RestrictedIndexedRouter implements RootHttpRouterService { - #indexPath?: false | string; +/** + * Options for the {@link DefaultRootHttpRouter} class. + * + * @public + */ +export interface DefaultRootHttpRouterOptions { + /** + * The path to forward all unmatched requests to. Defaults to '/api/app' if + * not given. Disables index path behavior if false is given. + */ + indexPath?: string | false; +} + +/** + * The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for + * {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}. + * + * @public + */ +export class DefaultRootHttpRouter implements RootHttpRouterService { + #indexPath?: string; #router = Router(); #namedRoutes = Router(); #indexRouter = Router(); #existingPaths = new Array(); - constructor(indexPath?: false | string) { + static create(options?: DefaultRootHttpRouterOptions) { + let indexPath; + if (options?.indexPath === false) { + indexPath = undefined; + } else if (options?.indexPath === undefined) { + indexPath = '/api/app'; + } else if (options?.indexPath === '') { + throw new Error('indexPath option may not be an empty string'); + } else { + indexPath = options.indexPath; + } + return new DefaultRootHttpRouter(indexPath); + } + + private constructor(indexPath?: string) { this.#indexPath = indexPath; this.#router.use(this.#namedRoutes); - this.#router.use(this.#indexRouter); + if (this.#indexPath) { + this.#router.use(this.#indexRouter); + } } use(path: string, handler: Handler) { diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts index 1dfd72273d..e24662df05 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -export { rootHttpRouterFactory } from './rootHttpRouterFactory'; -export type { - RootHttpRouterFactoryOptions, - RootHttpRouterConfigureOptions, +export { + rootHttpRouterFactory, + type RootHttpRouterFactoryOptions, + type RootHttpRouterConfigureOptions, } from './rootHttpRouterFactory'; +export { + DefaultRootHttpRouter, + type DefaultRootHttpRouterOptions, +} from './DefaultRootHttpRouter'; diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts index 45cb826b4d..58f8cc2db1 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterFactory.ts @@ -27,7 +27,7 @@ import { MiddlewareFactory, readHttpServerOptions, } from '../../../http'; -import { RestrictedIndexedRouter } from './RestrictedIndexedRouter'; +import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; /** * @public @@ -46,7 +46,8 @@ export interface RootHttpRouterConfigureOptions { */ export type RootHttpRouterFactoryOptions = { /** - * The path to forward all unmatched requests to. Defaults to '/api/app' + * The path to forward all unmatched requests to. Defaults to '/api/app' if + * not given. Disables index path behavior if false is given. */ indexPath?: string | false; @@ -82,11 +83,10 @@ export const rootHttpRouterFactory = createServiceFactory({ configure = defaultConfigure, }: RootHttpRouterFactoryOptions = {}, ) { - const router = new RestrictedIndexedRouter(indexPath ?? '/api/app'); const logger = rootLogger.child({ service: 'rootHttpRouter' }); - const app = express(); + const router = DefaultRootHttpRouter.create({ indexPath }); const middleware = MiddlewareFactory.create({ config, logger }); configure({ From b4b1bd66433e10a7c8e750d1f5cd2f35e055bcd2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:09:30 +0100 Subject: [PATCH 07/14] backend-test-utils: refactor startTestBackend to use new http implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-test-utils/api-report.md | 10 ++- .../src/next/wiring/TestBackend.ts | 88 +++++++++++-------- .../src/next/wiring/index.ts | 2 +- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 3df3f3cb21..ad7ecc04dd 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -5,6 +5,7 @@ ```ts import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; import { ServiceFactory } from '@backstage/backend-plugin-api'; @@ -24,7 +25,14 @@ export function setupRequestMockHandlers(worker: { export function startTestBackend< TServices extends any[], TExtensionPoints extends any[], ->(options: TestBackendOptions): Promise; +>( + options: TestBackendOptions, +): Promise; + +// @alpha (undocumented) +export interface TestBackend extends Backend { + readonly server: ExtendedHttpServer; +} // @alpha (undocumented) export interface TestBackendOptions< diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 933257426e..00c38240a3 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -27,14 +27,12 @@ import { urlReaderFactory, databaseFactory, httpRouterFactory, + MiddlewareFactory, + createHttpServer, + ExtendedHttpServer, + DefaultRootHttpRouter, } from '@backstage/backend-app-api'; -import { - createServiceBuilder, - SingleHostDiscovery, -} from '@backstage/backend-common'; -import { Handler } from 'express'; -import * as http from 'http'; -import Router from 'express-promise-router'; +import { SingleHostDiscovery } from '@backstage/backend-common'; import { ServiceFactory, ServiceRef, @@ -45,8 +43,8 @@ import { } from '@backstage/backend-plugin-api'; import { mockConfigFactory, mockTokenManagerFactory } from '../implementations'; -import { AddressInfo } from 'net'; import { ConfigReader } from '@backstage/config'; +import express from 'express'; /** @alpha */ export interface TestBackendOptions< @@ -72,6 +70,17 @@ export interface TestBackendOptions< features?: BackendFeature[]; } +/** @alpha */ +export interface TestBackend extends Backend { + /** + * Provides access to the underling HTTP server for use with utilities + * such as `supertest`. + * + * If the root http router service has been replaced, this will throw an error. + */ + readonly server: ExtendedHttpServer; +} + const defaultServiceFactories = [ cacheFactory(), databaseFactory(), @@ -93,7 +102,9 @@ const backendInstancesToCleanUp = new Array(); export async function startTestBackend< TServices extends any[], TExtensionPoints extends any[], ->(options: TestBackendOptions): Promise { +>( + options: TestBackendOptions, +): Promise { const { services = [], extensionPoints = [], @@ -101,49 +112,43 @@ export async function startTestBackend< ...otherOptions } = options; - let server: http.Server; + let server: ExtendedHttpServer; const rootHttpRouterFactory = createServiceFactory({ service: coreServices.rootHttpRouter, deps: { config: coreServices.config, lifecycle: coreServices.rootLifecycle, + rootLogger: coreServices.rootLogger, }, - async factory({ config, lifecycle }) { - const router = Router(); + async factory({ config, lifecycle, rootLogger }) { + const router = DefaultRootHttpRouter.create(); + const logger = rootLogger.child({ service: 'rootHttpRouter' }); - const service = createServiceBuilder(module) - .loadConfig(config) - .setPort(0); + const app = express(); - service.addRouter('', router); + const middleware = MiddlewareFactory.create({ config, logger }); - server = await service.start(); - // Stop method isn't part of the public API, let's fix that once we move the implementation here. - const stoppableServer = server as typeof server & { - stop: (cb: (error?: Error) => void) => void; - }; + app.use(router.handler()); + app.use(middleware.notFound()); + app.use(middleware.error()); + + server = await createHttpServer( + app, + { listen: { host: '', port: 0 } }, + { logger }, + ); lifecycle.addShutdownHook({ async fn() { - await new Promise((resolve, reject) => { - stoppableServer.stop((error?: Error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); + await server.stop(); }, - labels: { service: 'rootHttpRouter' }, + logger, }); - return { - use: (path: string, handler: Handler) => { - router.use(path, handler); - }, - }; + await server.start(); + + return router; }, }); @@ -156,7 +161,7 @@ export async function startTestBackend< if (!server) { throw new Error('Test server not started yet'); } - const { port } = server.address() as AddressInfo; + const port = server.port(); const discovery = SingleHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: `http://localhost:${port}`, listen: { port } }, @@ -220,7 +225,14 @@ export async function startTestBackend< await backend.start(); - return Object.assign(backend, { server: server! }) as Backend; + return Object.assign(backend, { + get server() { + if (!server) { + throw new Error('TestBackend server is not available'); + } + return server; + }, + }); } let registered = false; diff --git a/packages/backend-test-utils/src/next/wiring/index.ts b/packages/backend-test-utils/src/next/wiring/index.ts index eb7b773e33..7c39474d4c 100644 --- a/packages/backend-test-utils/src/next/wiring/index.ts +++ b/packages/backend-test-utils/src/next/wiring/index.ts @@ -15,4 +15,4 @@ */ export { startTestBackend } from './TestBackend'; -export type { TestBackendOptions } from './TestBackend'; +export type { TestBackend, TestBackendOptions } from './TestBackend'; From 3fad4ed40a7a710d67fd10f4794d02f09bd6f1fc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:13:01 +0100 Subject: [PATCH 08/14] backend-tasks: update API report + changeset for forPlugin addition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .changeset/hungry-weeks-flash.md | 5 +++++ packages/backend-tasks/api-report.md | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/hungry-weeks-flash.md diff --git a/.changeset/hungry-weeks-flash.md b/.changeset/hungry-weeks-flash.md new file mode 100644 index 0000000000..19cb6cede8 --- /dev/null +++ b/.changeset/hungry-weeks-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Added a new static `TaskScheduler.forPlugin` method. diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index f7b4782263..44ec4addbe 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -8,6 +8,7 @@ import { DatabaseManager } from '@backstage/backend-common'; import { Duration } from 'luxon'; import { HumanDuration as HumanDuration_2 } from '@backstage/types'; import { Logger } from 'winston'; +import { PluginDatabaseManager } from '@backstage/backend-common'; // @public @deprecated export type HumanDuration = HumanDuration_2; @@ -74,6 +75,12 @@ export class TaskScheduler { constructor(databaseManager: DatabaseManager, logger: Logger); forPlugin(pluginId: string): PluginTaskScheduler; // (undocumented) + static forPlugin(opts: { + pluginId: string; + databaseManager: PluginDatabaseManager; + logger: Logger; + }): PluginTaskScheduler; + // (undocumented) static fromConfig( config: Config, options?: { From 51b7a7ed070ce1ebbf444aafcb5d77fb4d43db30 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:15:11 +0100 Subject: [PATCH 09/14] changesets: added changesets for default implementations for test backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .changeset/proud-cobras-chew.md | 5 +++++ .changeset/shaggy-apricots-camp.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/proud-cobras-chew.md create mode 100644 .changeset/shaggy-apricots-camp.md diff --git a/.changeset/proud-cobras-chew.md b/.changeset/proud-cobras-chew.md new file mode 100644 index 0000000000..ae08378715 --- /dev/null +++ b/.changeset/proud-cobras-chew.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +The backend started by `startTestBackend` now has default implementations of all core services. It now also returns a `TestBackend` instance, which provides access to the underlying `server` that can be used with testing libraries such as `supertest`. diff --git a/.changeset/shaggy-apricots-camp.md b/.changeset/shaggy-apricots-camp.md new file mode 100644 index 0000000000..705dcef0f0 --- /dev/null +++ b/.changeset/shaggy-apricots-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Exported the default root HTTP router implementation as `DefaultRootHttpRouter`. It only implements the routing layer and needs to be exposed via an HTTP server similar to the built-in setup in the `rootHttpRouterFactory`. From ac993deb18d2dda8668bc89e15ebf60313630bb9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:17:05 +0100 Subject: [PATCH 10/14] backend-test-utils: tweak mockConfigFactory options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- packages/backend-test-utils/package.json | 1 + .../src/next/implementations/mockConfigService.ts | 7 ++++--- yarn.lock | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index ae96f17b8e..725be0d905 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -39,6 +39,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/types": "workspace:^", "better-sqlite3": "^8.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/packages/backend-test-utils/src/next/implementations/mockConfigService.ts b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts index 976167b473..78a670585d 100644 --- a/packages/backend-test-utils/src/next/implementations/mockConfigService.ts +++ b/packages/backend-test-utils/src/next/implementations/mockConfigService.ts @@ -18,13 +18,14 @@ import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { AppConfig, ConfigReader } from '@backstage/config'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; /** @public */ export const mockConfigFactory = createServiceFactory({ service: coreServices.config, deps: {}, - async factory(_, options?: { config?: AppConfig }) { - return new ConfigReader(options?.config); + async factory(_, options?: { data?: JsonObject }) { + return new ConfigReader(options?.data, 'mock-config'); }, }); diff --git a/yarn.lock b/yarn.lock index f8957bfc35..f973dee489 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3558,6 +3558,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/types": "workspace:^" "@types/supertest": ^2.0.8 better-sqlite3: ^8.0.0 express: ^4.17.1 From 5541715237ccba49e44321c375c257a1d92e59d3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 10 Jan 2023 15:20:11 +0100 Subject: [PATCH 11/14] backend-test-utils: make mock token manager check for mock tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: blam Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- .../src/next/implementations/mockTokenManagerService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts index f68e910e0c..dd1d6b16f7 100644 --- a/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts +++ b/packages/backend-test-utils/src/next/implementations/mockTokenManagerService.ts @@ -23,7 +23,11 @@ class TokenManagerMock implements TokenManager { async getToken(): Promise<{ token: string }> { return { token: 'mock-token' }; } - async authenticate(): Promise {} + async authenticate(token: string): Promise { + if (token !== 'mock-token') { + throw new Error('Invalid token'); + } + } } export const mockTokenManagerFactory = createServiceFactory({ From 54632b830434e26a093161205da1bd337808e6b6 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 11 Jan 2023 14:06:45 +0100 Subject: [PATCH 12/14] chore: fix some of the tests by exporting the `mockConfigFactory` Signed-off-by: blam Signed-off-by: blam --- packages/backend-test-utils/api-report.md | 11 +++++ .../src/next/implementations/index.ts | 1 - packages/backend-test-utils/src/next/index.ts | 1 + .../src/next/wiring/TestBackend.ts | 3 +- ...etCloudEntityProviderCatalogModule.test.ts | 46 ++++++++----------- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index ad7ecc04dd..85cdb201ef 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -5,8 +5,10 @@ ```ts import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ConfigService } from '@backstage/backend-plugin-api'; import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; @@ -14,6 +16,15 @@ import { ServiceRef } from '@backstage/backend-plugin-api'; // @public (undocumented) export function isDockerDisabledForTests(): boolean; +// @public (undocumented) +export const mockConfigFactory: ( + options?: + | { + data?: JsonObject | undefined; + } + | undefined, +) => ServiceFactory; + // @public export function setupRequestMockHandlers(worker: { listen: (t: any) => void; diff --git a/packages/backend-test-utils/src/next/implementations/index.ts b/packages/backend-test-utils/src/next/implementations/index.ts index 2068aebec5..55f417f8df 100644 --- a/packages/backend-test-utils/src/next/implementations/index.ts +++ b/packages/backend-test-utils/src/next/implementations/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { mockTokenManagerFactory } from './mockTokenManagerService'; export { mockConfigFactory } from './mockConfigService'; diff --git a/packages/backend-test-utils/src/next/index.ts b/packages/backend-test-utils/src/next/index.ts index 9bb5431772..9f9edfd837 100644 --- a/packages/backend-test-utils/src/next/index.ts +++ b/packages/backend-test-utils/src/next/index.ts @@ -15,3 +15,4 @@ */ export * from './wiring'; +export * from './implementations'; diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 00c38240a3..8fe489750a 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -42,7 +42,8 @@ import { coreServices, } from '@backstage/backend-plugin-api'; -import { mockConfigFactory, mockTokenManagerFactory } from '../implementations'; +import { mockConfigFactory } from '../implementations/mockConfigService'; +import { mockTokenManagerFactory } from '../implementations/mockTokenManagerService'; import { ConfigReader } from '@backstage/config'; import express from 'express'; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts index 83e657bcfe..d44281600c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.test.ts @@ -14,18 +14,15 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; -import { - getVoidLogger, - PluginEndpointDiscovery, - TokenManager, -} from '@backstage/backend-common'; import { coreServices } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler, TaskScheduleDefinition, } from '@backstage/backend-tasks'; -import { startTestBackend } from '@backstage/backend-test-utils'; +import { + startTestBackend, + mockConfigFactory, +} from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; import { eventsExtensionPoint } from '@backstage/plugin-events-node'; import { Duration } from 'luxon'; @@ -55,22 +52,6 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => { return runner; }, } as unknown as PluginTaskScheduler; - const discovery = jest.fn() as any as PluginEndpointDiscovery; - const tokenManager = jest.fn() as any as TokenManager; - - const config = new ConfigReader({ - catalog: { - providers: { - bitbucketCloud: { - schedule: { - frequency: 'P1M', - timeout: 'PT3M', - }, - workspace: 'test-ws', - }, - }, - }, - }); await startTestBackend({ extensionPoints: [ @@ -78,11 +59,22 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => { [eventsExtensionPoint, eventsExtensionPointImpl], ], services: [ - [coreServices.config, config], - [coreServices.discovery, discovery], - [coreServices.logger, getVoidLogger()], + mockConfigFactory({ + data: { + catalog: { + providers: { + bitbucketCloud: { + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + workspace: 'test-ws', + }, + }, + }, + }, + }), [coreServices.scheduler, scheduler], - [coreServices.tokenManager, tokenManager], ], features: [bitbucketCloudEntityProviderCatalogModule()], }); From 8c19b84088176069622ae43511cd267007679129 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 11 Jan 2023 14:07:02 +0100 Subject: [PATCH 13/14] chore: fix other tests Signed-off-by: blam --- plugins/app-backend/package.json | 1 - .../app-backend/src/service/appPlugin.test.ts | 21 +++---------------- .../package.json | 3 +-- .../catalog-node/src/catalogService.test.ts | 9 +------- yarn.lock | 9 -------- 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 1f58f64796..d5fbf9f239 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -56,7 +56,6 @@ "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/supertest": "^2.0.8", - "get-port": "^6.1.2", "mock-fs": "^5.1.0", "msw": "^0.49.0", "node-fetch": "^2.6.7", diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts index 80408694d6..7c57a5d685 100644 --- a/plugins/app-backend/src/service/appPlugin.test.ts +++ b/plugins/app-backend/src/service/appPlugin.test.ts @@ -17,18 +17,14 @@ import mockFs from 'mock-fs'; import { resolve as resolvePath } from 'path'; import fetch from 'node-fetch'; -import { coreServices } from '@backstage/backend-plugin-api'; import { startTestBackend } from '@backstage/backend-test-utils'; import { appPlugin } from './appPlugin'; import { databaseFactory, httpRouterFactory, - rootHttpRouterFactory, loggerFactory, rootLoggerFactory, } from '@backstage/backend-app-api'; -import { ConfigReader } from '@backstage/config'; -import getPort from 'get-port'; describe('appPlugin', () => { beforeEach(() => { @@ -48,23 +44,12 @@ describe('appPlugin', () => { }); it('boots', async () => { - const port = await getPort(); - await startTestBackend({ + const { server } = await startTestBackend({ services: [ - [ - coreServices.config, - new ConfigReader({ - backend: { - listen: { port }, - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ], loggerFactory(), rootLoggerFactory(), databaseFactory(), httpRouterFactory(), - rootHttpRouterFactory(), ], features: [ appPlugin({ @@ -75,12 +60,12 @@ describe('appPlugin', () => { }); await expect( - fetch(`http://localhost:${port}/api/app/derp.html`).then(res => + fetch(`http://localhost:${server.port()}/api/app/derp.html`).then(res => res.text(), ), ).resolves.toBe('winning'); await expect( - fetch(`http://localhost:${port}`).then(res => res.text()), + fetch(`http://localhost:${server.port()}`).then(res => res.text()), ).resolves.toBe('winning'); }); }); diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 06a0ab1c89..a86e27fae4 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -56,8 +56,7 @@ "devDependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-catalog-backend": "workspace:^", - "get-port": "^6.1.2" + "@backstage/plugin-catalog-backend": "workspace:^" }, "files": [ "alpha", diff --git a/plugins/catalog-node/src/catalogService.test.ts b/plugins/catalog-node/src/catalogService.test.ts index 230af79a88..ec3e5ccf1b 100644 --- a/plugins/catalog-node/src/catalogService.test.ts +++ b/plugins/catalog-node/src/catalogService.test.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { - createBackendModule, - coreServices, -} from '@backstage/backend-plugin-api'; +import { createBackendModule } from '@backstage/backend-plugin-api'; import { startTestBackend } from '@backstage/backend-test-utils'; import { CatalogClient } from '@backstage/catalog-client'; import { catalogServiceRef } from './catalogService'; @@ -42,9 +38,6 @@ describe('catalogServiceRef', () => { }); await startTestBackend({ - services: [ - [coreServices.discovery, {} as unknown as PluginEndpointDiscovery], - ], features: [testModule()], }); }); diff --git a/yarn.lock b/yarn.lock index f973dee489..0890ba4386 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4518,7 +4518,6 @@ __metadata: express: ^4.17.1 express-promise-router: ^4.1.0 fs-extra: 10.1.0 - get-port: ^6.1.2 globby: ^11.0.0 helmet: ^6.0.0 knex: ^2.0.0 @@ -5117,7 +5116,6 @@ __metadata: "@types/luxon": ^3.0.0 express: ^4.17.1 express-promise-router: ^4.1.0 - get-port: ^6.1.2 knex: ^2.0.0 lodash: ^4.17.21 luxon: ^3.0.0 @@ -23753,13 +23751,6 @@ __metadata: languageName: node linkType: hard -"get-port@npm:^6.1.2": - version: 6.1.2 - resolution: "get-port@npm:6.1.2" - checksum: e3c3d591492a11393455ef220f24c812a28f7da56ec3e4a2512d931a1f196d42850b50ac6138349a44622eda6dc3c0ccd8495cd91376d968e2d9e6f6f849e0a9 - languageName: node - linkType: hard - "get-stdin@npm:^8.0.0": version: 8.0.0 resolution: "get-stdin@npm:8.0.0" From 69e1e5bb6354029b4e557513583e7ad3cd041bb1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 12 Jan 2023 13:38:00 +0100 Subject: [PATCH 14/14] chore: trim slashes with lodash Signed-off-by: Johan Haals --- packages/backend-app-api/package.json | 1 + .../implementations/rootHttpRouter/DefaultRootHttpRouter.ts | 3 ++- yarn.lock | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index a6ecb5f455..01eaeaecaf 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -48,6 +48,7 @@ "express-promise-router": "^4.1.0", "fs-extra": "10.1.0", "helmet": "^6.0.0", + "lodash": "^4.17.21", "minimatch": "^5.0.0", "morgan": "^1.10.0", "node-forge": "^1.3.1", diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts index 2acbb3833a..0a144af494 100644 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts +++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts @@ -16,9 +16,10 @@ import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { Handler, Router } from 'express'; +import trimEnd from 'lodash/trimEnd'; function normalizePath(path: string): string { - return path.replace(/\/*$/, '/'); + return `${trimEnd(path, '/')}/`; } /** diff --git a/yarn.lock b/yarn.lock index 0890ba4386..cdb2142850 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3400,6 +3400,7 @@ __metadata: fs-extra: 10.1.0 helmet: ^6.0.0 http-errors: ^2.0.0 + lodash: ^4.17.21 minimatch: ^5.0.0 morgan: ^1.10.0 node-forge: ^1.3.1