From 95676ce51aa11d9c861d56b2cfab23506c15b59e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 2 Aug 2023 13:38:07 +0200 Subject: [PATCH] add featureDiscoveryServiceFactory test Co-authored-by: Patrik Oldsberg Signed-off-by: Vincenzo Scamporlino --- packages/backend-app-api/package.json | 5 +- packages/backend-app-api/src/alpha.ts | 17 --- .../featureDiscoveryServiceFactory.test.ts | 114 ++++++++++++++++++ .../alpha/featureDiscoveryServiceFactory.ts | 8 +- packages/backend-plugin-api/src/alpha.ts | 5 +- yarn.lock | 1 + 6 files changed, 127 insertions(+), 23 deletions(-) delete mode 100644 packages/backend-app-api/src/alpha.ts create mode 100644 packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 75890b4370..bc3318fce5 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -12,13 +12,13 @@ }, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha/index.ts" ], "package.json": [ "package.json" @@ -87,6 +87,7 @@ "@types/node-forge": "^1.3.0", "@types/stoppable": "^1.1.0", "http-errors": "^2.0.0", + "mock-fs": "^5.2.0", "supertest": "^6.1.3" }, "files": [ diff --git a/packages/backend-app-api/src/alpha.ts b/packages/backend-app-api/src/alpha.ts deleted file mode 100644 index d9068b8f82..0000000000 --- a/packages/backend-app-api/src/alpha.ts +++ /dev/null @@ -1,17 +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. - */ - -export * from './alpha'; diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts new file mode 100644 index 0000000000..5a5917dd3b --- /dev/null +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts @@ -0,0 +1,114 @@ +/* + * 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 mockFs from 'mock-fs'; +import { resolve as resolvePath, dirname } from 'path'; +import { startTestBackend, mockServices } from '@backstage/backend-test-utils'; +import { featureDiscoveryServiceFactory } from './featureDiscoveryServiceFactory'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; + +const rootDir = dirname(process.argv[1]); + +describe('featureDiscoveryServiceFactory', () => { + beforeEach(() => { + mockFs({ + [rootDir]: { + 'package.json': JSON.stringify({ + name: 'example-app', + dependencies: { + 'detected-plugin': '0.0.0', + 'detected-module': '0.0.0', + }, + }), + }, + [resolvePath(rootDir, 'node_modules/detected-plugin')]: { + 'package.json': JSON.stringify({ + name: 'detected-plugin', + main: 'index.js', + backstage: { + role: 'backend-plugin', + }, + }), + 'index.js': ` + const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api'); + exports.detectedPlugin = createBackendPlugin({ + pluginId: 'detected', + register(env) { + env.registerInit({ + deps: { identity: coreServices.identity }, + async init({ identity }) { + identity.getIdentity('detected-plugin'); + }, + }); + }, + }); + `, + }, + [resolvePath(rootDir, 'node_modules/detected-module')]: { + 'package.json': JSON.stringify({ + name: 'detected-module', + main: 'index.js', + backstage: { + role: 'backend-module', + }, + }), + 'index.js': ` + const { createBackendModule, coreServices } = require('@backstage/backend-plugin-api'); + exports.detectedModuleDerp = createBackendModule({ + pluginId: 'detected', + moduleId: 'derp', + register(env) { + env.registerInit({ + deps: { identity: coreServices.identity }, + async init({ identity }) { + identity.getIdentity('detected-module'); + }, + }); + }, + }); + `, + }, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should detect plugin and module packages', async () => { + const fn = jest.fn().mockResolvedValue({}); + + await startTestBackend({ + services: [ + createServiceFactory({ + service: coreServices.identity, + deps: {}, + factory: () => ({ getIdentity: fn }), + }), + featureDiscoveryServiceFactory(), + mockServices.rootConfig.factory({ + data: { backend: { packages: 'all' } }, + }), + ], + }); + + expect(fn).toHaveBeenCalledWith('detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-module'); + }); +}); diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts index ee1c2aa729..7062448eb1 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts @@ -79,11 +79,13 @@ class PackageDiscoveryService implements FeatureDiscoveryService { const features: BackendFeature[] = []; for (const name of dependencyNames) { - const depPkg = require(`${name}/package.json`) as BackstagePackageJson; + const depPkg = require(require.resolve(`${name}/package.json`, { + paths: [packageDir], + })) as BackstagePackageJson; if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) { continue; } - const depModule = require(name); + const depModule = require(require.resolve(name, { paths: [packageDir] })); for (const exportValue of Object.values(depModule)) { if (isBackendFeature(exportValue)) { features.push(exportValue); @@ -122,7 +124,7 @@ function isBackendFeatureFactory( ): value is BackendFeatureFactory { return ( !!value && - typeof value === 'object' && + typeof value === 'function' && (value as BackendFeatureFactory).$$type === '@backstage/BackendFeatureFactory' ); diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha.ts index 6db9b009cf..1030b55d95 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha.ts @@ -22,7 +22,10 @@ export interface FeatureDiscoveryService { getBackendFeatures(): Promise<{ features: Array }>; } -/** @alpha */ +/** + * An optional service that can be used to dynamically load in additional BackendFeatures at runtime. + * @alpha + */ export const featureDiscoveryServiceRef = createServiceRef({ id: 'core.featureDiscovery', diff --git a/yarn.lock b/yarn.lock index 07c182f207..fcdc40a97a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3320,6 +3320,7 @@ __metadata: logform: ^2.3.2 minimatch: ^5.0.0 minimist: ^1.2.5 + mock-fs: ^5.2.0 morgan: ^1.10.0 node-forge: ^1.3.1 selfsigned: ^2.0.0