Merge pull request #15530 from backstage/blam/add-mocks-test-backend

Add all `coreServices` to `startTestBackend`
This commit is contained in:
Ben Lambert
2023-01-12 14:20:51 +01:00
committed by GitHub
28 changed files with 466 additions and 106 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-tasks': patch
---
Added a new static `TaskScheduler.forPlugin` method.
+5
View File
@@ -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`.
+5
View File
@@ -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`.
+16
View File
@@ -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<PluginDatabaseManager>;
// @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,
+1
View File
@@ -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",
@@ -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());
};
@@ -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',
);
});
});
@@ -16,23 +16,59 @@
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, '/')}/`;
}
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<string>();
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) {
@@ -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';
@@ -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({
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { loggerToWinstonLogger } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
@@ -24,13 +25,17 @@ 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);
return async ({ plugin }) => {
return taskScheduler.forPlugin(plugin.getId());
async factory() {
return async ({ plugin, databaseManager, logger }) => {
return TaskScheduler.forPlugin({
pluginId: plugin.getId(),
databaseManager,
logger: loggerToWinstonLogger(logger),
});
};
},
});
+7
View File
@@ -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?: {
@@ -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);
}
}
+20 -1
View File
@@ -5,7 +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';
@@ -13,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<ConfigService>;
// @public
export function setupRequestMockHandlers(worker: {
listen: (t: any) => void;
@@ -24,7 +36,14 @@ export function setupRequestMockHandlers(worker: {
export function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<Backend>;
>(
options: TestBackendOptions<TServices, TExtensionPoints>,
): Promise<TestBackend>;
// @alpha (undocumented)
export interface TestBackend extends Backend {
readonly server: ExtendedHttpServer;
}
// @alpha (undocumented)
export interface TestBackendOptions<
+6 -1
View File
@@ -39,7 +39,10 @@
"@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",
"knex": "^2.0.0",
"msw": "^0.49.0",
"mysql2": "^2.2.5",
@@ -48,7 +51,9 @@
"uuid": "^8.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
"@backstage/cli": "workspace:^",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
"files": [
"dist",
@@ -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 { mockConfigFactory } from './mockConfigService';
@@ -0,0 +1,31 @@
/*
* 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 { ConfigReader } from '@backstage/config';
import { JsonObject } from '@backstage/types';
/** @public */
export const mockConfigFactory = createServiceFactory({
service: coreServices.config,
deps: {},
async factory(_, options?: { data?: JsonObject }) {
return new ConfigReader(options?.data, 'mock-config');
},
});
@@ -0,0 +1,41 @@
/*
* 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(token: string): Promise<void> {
if (token !== 'mock-token') {
throw new Error('Invalid token');
}
}
}
export const mockTokenManagerFactory = createServiceFactory({
service: coreServices.tokenManager,
deps: {},
async factory() {
return async () => {
return new TokenManagerMock();
};
},
});
@@ -15,3 +15,4 @@
*/
export * from './wiring';
export * from './implementations';
@@ -20,7 +20,11 @@ import {
createServiceFactory,
createServiceRef,
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { Router } from 'express';
import request from 'supertest';
import { startTestBackend } from './TestBackend';
// This bit makes sure that test backends are cleaned up properly
@@ -156,4 +160,66 @@ 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,
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,
tokenManager: coreServices.tokenManager,
urlReader: coreServices.urlReader,
},
async init(deps) {
expect(Object.keys(deps)).toHaveLength(15);
expect(Object.values(deps)).not.toContain(undefined);
},
});
},
});
await startTestBackend({
services: [],
features: [testPlugin()],
});
});
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' });
});
});
@@ -21,15 +21,32 @@ import {
rootLifecycleFactory,
loggerFactory,
rootLoggerFactory,
cacheFactory,
permissionsFactory,
schedulerFactory,
urlReaderFactory,
databaseFactory,
httpRouterFactory,
MiddlewareFactory,
createHttpServer,
ExtendedHttpServer,
DefaultRootHttpRouter,
} from '@backstage/backend-app-api';
import { SingleHostDiscovery } from '@backstage/backend-common';
import {
ServiceFactory,
ServiceRef,
createServiceFactory,
BackendFeature,
ExtensionPoint,
coreServices,
} from '@backstage/backend-plugin-api';
import { mockConfigFactory } from '../implementations/mockConfigService';
import { mockTokenManagerFactory } from '../implementations/mockTokenManagerService';
import { ConfigReader } from '@backstage/config';
import express from 'express';
/** @alpha */
export interface TestBackendOptions<
TServices extends any[],
@@ -54,11 +71,30 @@ 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 = [
rootLoggerFactory(),
loggerFactory(),
cacheFactory(),
databaseFactory(),
httpRouterFactory(),
lifecycleFactory(),
loggerFactory(),
mockConfigFactory(),
mockTokenManagerFactory(),
permissionsFactory(),
rootLifecycleFactory(),
rootLoggerFactory(),
schedulerFactory(),
urlReaderFactory(),
];
const backendInstancesToCleanUp = new Array<Backend>();
@@ -67,7 +103,9 @@ const backendInstancesToCleanUp = new Array<Backend>();
export async function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<Backend> {
>(
options: TestBackendOptions<TServices, TExtensionPoints>,
): Promise<TestBackend> {
const {
services = [],
extensionPoints = [],
@@ -75,6 +113,65 @@ export async function startTestBackend<
...otherOptions
} = options;
let server: ExtendedHttpServer;
const rootHttpRouterFactory = createServiceFactory({
service: coreServices.rootHttpRouter,
deps: {
config: coreServices.config,
lifecycle: coreServices.rootLifecycle,
rootLogger: coreServices.rootLogger,
},
async factory({ config, lifecycle, rootLogger }) {
const router = DefaultRootHttpRouter.create();
const logger = rootLogger.child({ service: 'rootHttpRouter' });
const app = express();
const middleware = MiddlewareFactory.create({ config, logger });
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 server.stop();
},
logger,
});
await server.start();
return router;
},
});
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.port();
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?
@@ -107,7 +204,7 @@ export async function startTestBackend<
const backend = createSpecializedBackend({
...otherOptions,
services: factories,
services: [...factories, rootHttpRouterFactory, discoveryFactory],
});
backendInstancesToCleanUp.push(backend);
@@ -129,7 +226,14 @@ export async function startTestBackend<
await backend.start();
return backend;
return Object.assign(backend, {
get server() {
if (!server) {
throw new Error('TestBackend server is not available');
}
return server;
},
});
}
let registered = false;
@@ -15,4 +15,4 @@
*/
export { startTestBackend } from './TestBackend';
export type { TestBackendOptions } from './TestBackend';
export type { TestBackend, TestBackendOptions } from './TestBackend';
-1
View File
@@ -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",
@@ -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');
});
});
@@ -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()],
});
@@ -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",
@@ -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()],
});
});
+6 -9
View File
@@ -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
@@ -3558,11 +3559,16 @@ __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
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
@@ -4513,7 +4519,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
@@ -5112,7 +5117,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
@@ -23809,13 +23813,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"