UNPKG

@backstage/backend-test-utils

Version:

Test helpers library for Backstage backends

275 lines (269 loc) • 9.27 kB
'use strict'; var backendAppApi = require('@backstage/backend-app-api'); var backendPluginApi = require('@backstage/backend-plugin-api'); var mockServices = require('../services/mockServices.cjs.js'); require('../services/mockCredentials.cjs.js'); var config = require('@backstage/config'); var express = require('express'); var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter'); var discovery = require('@backstage/backend-defaults/discovery'); var ActionsRegistryServiceMock = require('../alpha/services/ActionsRegistryServiceMock.cjs.js'); require('@backstage/errors'); require('zod'); require('zod-to-json-schema'); var ActionsServiceMock = require('../alpha/services/ActionsServiceMock.cjs.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var express__default = /*#__PURE__*/_interopDefaultCompat(express); const defaultServiceFactories = [ mockServices.mockServices.auth.factory(), mockServices.mockServices.auditor.factory(), mockServices.mockServices.cache.factory(), mockServices.mockServices.rootConfig.factory(), mockServices.mockServices.database.factory(), mockServices.mockServices.httpAuth.factory(), mockServices.mockServices.httpRouter.factory(), mockServices.mockServices.lifecycle.factory(), mockServices.mockServices.logger.factory(), mockServices.mockServices.permissions.factory(), mockServices.mockServices.permissionsRegistry.factory(), mockServices.mockServices.rootHealth.factory(), mockServices.mockServices.rootLifecycle.factory(), mockServices.mockServices.rootLogger.factory(), mockServices.mockServices.scheduler.factory(), mockServices.mockServices.userInfo.factory(), mockServices.mockServices.urlReader.factory(), mockServices.mockServices.events.factory(), // Alpha services ActionsRegistryServiceMock.actionsRegistryServiceMock.factory(), ActionsServiceMock.actionsServiceMock.factory() ]; function createPluginsForOrphanModules(features) { const pluginIds = /* @__PURE__ */ new Set(); const modulePluginIds = /* @__PURE__ */ new Set(); for (const feature of features) { if (isInternalBackendRegistrations(feature)) { const registrations = feature.getRegistrations(); for (const registration of registrations) { if (registration.type === "plugin") { pluginIds.add(registration.pluginId); } else if (registration.type === "module") { modulePluginIds.add(registration.pluginId); } } } } for (const pluginId of pluginIds) { modulePluginIds.delete(pluginId); } return Array.from(modulePluginIds).map( (pluginId) => backendPluginApi.createBackendPlugin({ pluginId, register(reg) { reg.registerInit({ deps: {}, async init() { } }); } }) ); } function createExtensionPointTestModules(features, extensionPointTuples) { if (!extensionPointTuples) { return []; } const registrations = features.flatMap((feature) => { if (isInternalBackendRegistrations(feature)) { return feature.getRegistrations(); } return []; }); const extensionPointMap = new Map( extensionPointTuples.map((ep) => [ep[0].id, ep]) ); const extensionPointsToSort = new Set(extensionPointMap.keys()); const extensionPointsByPlugin = /* @__PURE__ */ new Map(); for (const registration of registrations) { if (registration.type === "module") { const testDep = Object.values(registration.init.deps).filter( (dep) => extensionPointsToSort.has(dep.id) ); if (testDep.length > 0) { let points = extensionPointsByPlugin.get(registration.pluginId); if (!points) { points = []; extensionPointsByPlugin.set(registration.pluginId, points); } for (const { id } of testDep) { points.push(id); extensionPointsToSort.delete(id); } } } } if (extensionPointsToSort.size > 0) { const list = Array.from(extensionPointsToSort).map((id) => `'${id}'`).join(", "); throw new Error( `Unable to determine the plugin ID of extension point(s) ${list}. Tested extension points must be depended on by one or more tested modules.` ); } const modules = []; for (const [pluginId, pluginExtensionPointIds] of extensionPointsByPlugin) { modules.push( backendPluginApi.createBackendModule({ pluginId, moduleId: "test-extension-point-registration", register(reg) { for (const id of pluginExtensionPointIds) { const tuple = extensionPointMap.get(id); reg.registerExtensionPoint(...tuple); } reg.registerInit({ deps: {}, async init() { } }); } }) ); } return modules; } function isPromise(value) { return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function"; } function unwrapFeature(feature) { if ("$$type" in feature) { return feature; } if ("default" in feature) { return feature.default; } return feature; } const backendInstancesToCleanUp = new Array(); async function startTestBackend(options) { const { extensionPoints, ...otherOptions } = options; const features = await Promise.all( options.features?.map(async (val) => { if (isPromise(val)) { const { default: feature } = await val; return unwrapFeature(feature); } return unwrapFeature(val); }) ?? [] ); let server; const rootHttpRouterFactory = backendPluginApi.createServiceFactory({ service: backendPluginApi.coreServices.rootHttpRouter, deps: { config: backendPluginApi.coreServices.rootConfig, lifecycle: backendPluginApi.coreServices.rootLifecycle, rootLogger: backendPluginApi.coreServices.rootLogger, health: backendPluginApi.coreServices.rootHealth }, async factory({ config, lifecycle, rootLogger, health }) { const router = rootHttpRouter.DefaultRootHttpRouter.create(); const logger = rootLogger.child({ service: "rootHttpRouter" }); const app = express__default.default(); const middleware = rootHttpRouter.MiddlewareFactory.create({ config, logger }); const healthRouter = rootHttpRouter.createHealthRouter({ config, health }); app.use(healthRouter); app.use(router.handler()); app.use(middleware.notFound()); app.use(middleware.error()); server = await rootHttpRouter.createHttpServer( app, { listen: { host: "", port: 0 } }, { logger } ); lifecycle.addShutdownHook(() => server.stop(), { logger }); await server.start(); return router; } }); const discoveryFactory = backendPluginApi.createServiceFactory({ service: backendPluginApi.coreServices.discovery, deps: { rootHttpRouter: backendPluginApi.coreServices.rootHttpRouter }, async factory() { if (!server) { throw new Error("Test server not started yet"); } const port = server.port(); const discovery$1 = discovery.HostDiscovery.fromConfig( new config.ConfigReader({ backend: { baseUrl: `http://localhost:${port}`, listen: { port } } }) ); return discovery$1; } }); const backend = backendAppApi.createSpecializedBackend({ ...otherOptions, defaultServiceFactories: [ ...defaultServiceFactories, rootHttpRouterFactory, discoveryFactory ] }); backendInstancesToCleanUp.push(backend); for (const m of createExtensionPointTestModules(features, extensionPoints)) { backend.add(m); } for (const p of createPluginsForOrphanModules(features)) { backend.add(p); } for (const feature of features) { backend.add(feature); } await backend.start(); return Object.assign(backend, { get server() { if (!server) { throw new Error("TestBackend server is not available"); } return server; } }); } let registered = false; function registerTestHooks() { if (typeof afterAll !== "function") { return; } if (registered) { return; } registered = true; afterAll(async () => { await Promise.all( backendInstancesToCleanUp.map(async (backend) => { try { await backend.stop(); } catch (error) { console.error(`Failed to stop backend after tests, ${error}`); } }) ); backendInstancesToCleanUp.length = 0; }); } registerTestHooks(); function toInternalBackendFeature(feature) { if (feature.$$type !== "@backstage/BackendFeature") { throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`); } const internal = feature; if (internal.version !== "v1") { throw new Error( `Invalid BackendFeature, bad version '${internal.version}'` ); } return internal; } function isInternalBackendRegistrations(feature) { const internal = toInternalBackendFeature(feature); if (internal.featureType === "registrations") { return true; } return "getRegistrations" in internal; } exports.defaultServiceFactories = defaultServiceFactories; exports.startTestBackend = startTestBackend; //# sourceMappingURL=TestBackend.cjs.js.map