Merge pull request #19334 from backstage/service-factories-become-backend-features

Service factories become backend features
This commit is contained in:
Patrik Oldsberg
2023-08-15 18:22:24 +02:00
committed by GitHub
51 changed files with 538 additions and 484 deletions
+19
View File
@@ -0,0 +1,19 @@
---
'@backstage/backend-plugin-api': patch
'@backstage/backend-test-utils': minor
'@backstage/backend-defaults': minor
'@backstage/backend-app-api': minor
---
**BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated:
```ts
const backend = createBackend({ services: [myCustomServiceFactory] });
```
To instead pass the service factory via `backend.add(...)`:
```ts
const backend = createBackend();
backend.add(customRootLoggerServiceFactory);
```
+2 -2
View File
@@ -43,7 +43,7 @@ import { UrlReader } from '@backstage/backend-common';
// @public (undocumented)
export interface Backend {
// (undocumented)
add(feature: BackendFeature): void;
add(feature: BackendFeature | (() => BackendFeature)): void;
// (undocumented)
start(): Promise<void>;
// (undocumented)
@@ -81,7 +81,7 @@ export function createSpecializedBackend(
// @public (undocumented)
export interface CreateSpecializedBackendOptions {
// (undocumented)
services: ServiceFactoryOrFunction[];
defaultServiceFactories: ServiceFactoryOrFunction[];
}
// @public (undocumented)
@@ -95,7 +95,7 @@ describe('featureDiscoveryServiceFactory', () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
services: [
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
@@ -61,8 +61,7 @@ describe('schedulerFactory', () => {
});
await startTestBackend({
features: [plugin()],
services: [subject],
features: [plugin(), subject],
});
});
});
@@ -23,7 +23,7 @@ import {
createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { ServiceRegistry } from './ServiceRegistry';
import {
lifecycleServiceFactory,
loggerServiceFactory,
@@ -65,7 +65,7 @@ describe('BackendInitializer', () => {
const rootFactory = jest.fn();
const pluginFactory = jest.fn();
const registry = new ServiceRegistry([
const services = [
createServiceFactory({
service: rootRef,
deps: {},
@@ -82,9 +82,9 @@ describe('BackendInitializer', () => {
deps: {},
factory: () => new MockLogger(),
})(),
]);
];
const init = new BackendInitializer(registry);
const init = new BackendInitializer(services);
await init.start();
expect(rootFactory).toHaveBeenCalled();
@@ -97,7 +97,7 @@ describe('BackendInitializer', () => {
const extensionPoint = createExtensionPoint<{ values: string[] }>({
id: 'a',
});
const init = new BackendInitializer(new ServiceRegistry(baseFactories));
const init = new BackendInitializer(baseFactories);
init.add(
createBackendModule({
@@ -151,7 +151,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when plugins fail to start', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -171,7 +171,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when modules fail to start', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -192,7 +192,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate plugins', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -221,7 +221,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate modules', async () => {
const init = new BackendInitializer(new ServiceRegistry([]));
const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -254,16 +254,14 @@ describe('BackendInitializer', () => {
it('should reject modules with circular dependencies', async () => {
const extA = createExtensionPoint<string>({ id: 'a' });
const extB = createExtensionPoint<string>({ id: 'b' });
const init = new BackendInitializer(
new ServiceRegistry([
rootLifecycleServiceFactory(),
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
})(),
]),
);
const init = new BackendInitializer([
rootLifecycleServiceFactory(),
createServiceFactory({
service: coreServices.rootLogger,
deps: {},
factory: () => new MockLogger(),
})(),
]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -296,7 +294,7 @@ describe('BackendInitializer', () => {
});
it('should reject modules that depend on extension points other plugins', async () => {
const init = new BackendInitializer(new ServiceRegistry(baseFactories));
const init = new BackendInitializer(baseFactories);
const extA = createExtensionPoint<string>({ id: 'a' });
init.add(
createBackendPlugin({
@@ -19,6 +19,7 @@ import {
ExtensionPoint,
coreServices,
ServiceRef,
ServiceFactory,
} from '@backstage/backend-plugin-api';
import { BackendLifecycleImpl } from '../services/implementations/rootLifecycle/rootLifecycleServiceFactory';
import { BackendPluginLifecycleImpl } from '../services/implementations/lifecycle/lifecycleServiceFactory';
@@ -29,6 +30,7 @@ import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring
import { ForwardedError, ConflictError } from '@backstage/errors';
import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha';
import { DependencyGraph } from '../lib/DependencyGraph';
import { ServiceRegistry } from './ServiceRegistry';
export interface BackendRegisterInit {
consumes: Set<ServiceOrExtensionPoint>;
@@ -46,10 +48,12 @@ export class BackendInitializer {
ExtensionPoint<unknown>,
{ impl: unknown; pluginId: string }
>();
#serviceHolder: EnumerableServiceHolder;
#serviceHolder: EnumerableServiceHolder | undefined;
#providedServiceFactories = new Array<ServiceFactory>();
#defaultApiFactories: ServiceFactory[];
constructor(serviceHolder: EnumerableServiceHolder) {
this.#serviceHolder = serviceHolder;
constructor(defaultApiFactories: ServiceFactory[]) {
this.#defaultApiFactories = defaultApiFactories;
}
async #getInitDeps(
@@ -69,7 +73,7 @@ export class BackendInitializer {
}
result.set(name, ep.impl);
} else {
const impl = await this.#serviceHolder.get(
const impl = await this.#serviceHolder!.get(
ref as ServiceRef<unknown>,
pluginId,
);
@@ -104,13 +108,35 @@ export class BackendInitializer {
`Failed to add feature, invalid type '${feature.$$type}'`,
);
}
const internalFeature = feature as InternalBackendFeature;
if (internalFeature.version !== 'v1') {
if (isServiceFactory(feature)) {
if (feature.service.id === coreServices.pluginMetadata.id) {
throw new Error(
`The ${coreServices.pluginMetadata.id} service cannot be overridden`,
);
}
if (
this.#providedServiceFactories.find(
sf => sf.service.id === feature.service.id,
)
) {
throw new Error(
`Duplicate service implementations provided for ${feature.service.id}`,
);
}
this.#providedServiceFactories.push(feature);
} else if (isInternalBackendFeature(feature)) {
if (feature.version !== 'v1') {
throw new Error(
`Failed to add feature, invalid version '${feature.version}'`,
);
}
this.#features.push(feature);
} else {
throw new Error(
`Failed to add feature, invalid version '${internalFeature.version}'`,
`Failed to add feature, invalid feature ${JSON.stringify(feature)}`,
);
}
this.#features.push(internalFeature);
}
async start(): Promise<void> {
@@ -141,6 +167,11 @@ export class BackendInitializer {
}
async #doStart(): Promise<void> {
this.#serviceHolder = new ServiceRegistry([
...this.#defaultApiFactories,
...this.#providedServiceFactories,
]);
const featureDiscovery = await this.#serviceHolder.get(
featureDiscoveryServiceRef,
'root',
@@ -319,7 +350,7 @@ export class BackendInitializer {
// Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this
async #getRootLifecycleImpl(): Promise<BackendLifecycleImpl> {
const lifecycleService = await this.#serviceHolder.get(
const lifecycleService = await this.#serviceHolder!.get(
coreServices.rootLifecycle,
'root',
);
@@ -332,7 +363,7 @@ export class BackendInitializer {
async #getPluginLifecycleImpl(
pluginId: string,
): Promise<BackendPluginLifecycleImpl> {
const lifecycleService = await this.#serviceHolder.get(
const lifecycleService = await this.#serviceHolder!.get(
coreServices.lifecycle,
pluginId,
);
@@ -342,3 +373,15 @@ export class BackendInitializer {
throw new Error('Unexpected plugin lifecycle service implementation');
}
}
function isServiceFactory(feature: BackendFeature): feature is ServiceFactory {
return !!(feature as ServiceFactory).service;
}
function isInternalBackendFeature(
feature: BackendFeature,
): feature is InternalBackendFeature {
return (
typeof (feature as InternalBackendFeature).getRegistrations === 'function'
);
}
@@ -14,22 +14,19 @@
* limitations under the License.
*/
import { ServiceFactory, BackendFeature } from '@backstage/backend-plugin-api';
import { BackendFeature, ServiceFactory } from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
import { ServiceRegistry } from './ServiceRegistry';
import { Backend } from './types';
export class BackstageBackend implements Backend {
#services: ServiceRegistry;
#initializer: BackendInitializer;
constructor(apiFactories: ServiceFactory[]) {
this.#services = new ServiceRegistry(apiFactories);
this.#initializer = new BackendInitializer(this.#services);
constructor(defaultServiceFactories: ServiceFactory[]) {
this.#initializer = new BackendInitializer(defaultServiceFactories);
}
add(feature: BackendFeature): void {
this.#initializer.add(feature);
add(feature: BackendFeature | (() => BackendFeature)): void {
this.#initializer.add(typeof feature === 'function' ? feature() : feature);
}
async start(): Promise<void> {
@@ -31,7 +31,7 @@ const sf1 = createServiceFactory({
async factory() {
return { x: 1 };
},
})();
});
const ref2 = createServiceRef<{ x: number }>({
scope: 'root',
@@ -43,14 +43,14 @@ const sf2 = createServiceFactory({
async factory() {
return { x: 2 };
},
})();
});
const sf2b = createServiceFactory({
service: ref2,
deps: {},
async factory() {
return { x: 22 };
},
})();
});
const refDefault1 = createServiceRef<{ x: number }>({
id: '1',
@@ -61,7 +61,7 @@ const refDefault1 = createServiceRef<{ x: number }>({
async factory() {
return { x: 10 };
},
})(),
}),
});
const refDefault2a = createServiceRef<{ x: number }>({
@@ -95,7 +95,7 @@ describe('ServiceRegistry', () => {
});
it('should return an implementation for a registered ref', async () => {
const registry = new ServiceRegistry([sf1]);
const registry = new ServiceRegistry([sf1()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 1 });
await expect(registry.get(ref1, 'scaffolder')).resolves.toEqual({ x: 1 });
expect(await registry.get(ref1, 'catalog')).toBe(
@@ -110,7 +110,7 @@ describe('ServiceRegistry', () => {
});
it('should handle multiple factories with different serviceRefs', async () => {
const registry = new ServiceRegistry([sf1, sf2]);
const registry = new ServiceRegistry([sf1(), sf2()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({
x: 1,
@@ -131,7 +131,7 @@ describe('ServiceRegistry', () => {
return { x: 2 };
},
});
const registry = new ServiceRegistry([factory(), sf1]);
const registry = new ServiceRegistry([factory(), sf1()]);
await expect(registry.get(ref2, 'catalog')).rejects.toThrow(
"Failed to instantiate 'root' scoped service '2' because it depends on 'plugin' scoped service '1'.",
);
@@ -145,7 +145,7 @@ describe('ServiceRegistry', () => {
return { x: rootDep.x };
},
});
const registry = new ServiceRegistry([factory(), sf2]);
const registry = new ServiceRegistry([factory(), sf2()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({
x: 2,
});
@@ -160,7 +160,7 @@ describe('ServiceRegistry', () => {
return { x: rootDep.x };
},
});
const registry = new ServiceRegistry([factory(), sf2]);
const registry = new ServiceRegistry([factory(), sf2()]);
await expect(registry.get(ref, 'catalog')).resolves.toEqual({
x: 2,
});
@@ -182,7 +182,7 @@ describe('ServiceRegistry', () => {
});
it('should use the last factory for each ref', async () => {
const registry = new ServiceRegistry([sf2, sf2b]);
const registry = new ServiceRegistry([sf2(), sf2b()]);
await expect(registry.get(ref2, 'catalog')).resolves.toEqual({
x: 22,
});
@@ -196,7 +196,7 @@ describe('ServiceRegistry', () => {
});
it('should not use the defaultFactory from the ref if provided to the registry', async () => {
const registry = new ServiceRegistry([sf1]);
const registry = new ServiceRegistry([sf1()]);
await expect(registry.get(refDefault1, 'catalog')).resolves.toEqual({
x: 1,
});
@@ -39,7 +39,7 @@ function toInternalServiceFactory<TService, TScope extends 'plugin' | 'root'>(
factory: ServiceFactory<TService, TScope>,
): InternalServiceFactory<TService, TScope> {
const f = factory as InternalServiceFactory<TService, TScope>;
if (f.$$type !== '@backstage/ServiceFactory') {
if (f.$$type !== '@backstage/BackendFeature') {
throw new Error(`Invalid service factory, bad type '${f.$$type}'`);
}
if (f.version !== 'v1') {
@@ -22,13 +22,15 @@ import { createSpecializedBackend } from './createSpecializedBackend';
describe('createSpecializedBackend', () => {
it('should create a backend without services', () => {
expect(() => createSpecializedBackend({ services: [] })).not.toThrow();
expect(() =>
createSpecializedBackend({ defaultServiceFactories: [] }),
).not.toThrow();
});
it('should throw on duplicate service implementations', () => {
expect(() =>
createSpecializedBackend({
services: [
defaultServiceFactories: [
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
@@ -55,7 +57,7 @@ describe('createSpecializedBackend', () => {
it('should throw when providing a plugin metadata service implementation', () => {
expect(() =>
createSpecializedBackend({
services: [
defaultServiceFactories: [
createServiceFactory({
service: coreServices.pluginMetadata,
deps: {},
@@ -24,7 +24,7 @@ import { Backend, CreateSpecializedBackendOptions } from './types';
export function createSpecializedBackend(
options: CreateSpecializedBackendOptions,
): Backend {
const services = options.services.map(sf =>
const services = options.defaultServiceFactories.map(sf =>
typeof sf === 'function' ? sf() : sf,
);
+2 -2
View File
@@ -25,7 +25,7 @@ import {
* @public
*/
export interface Backend {
add(feature: BackendFeature): void;
add(feature: BackendFeature | (() => BackendFeature)): void;
start(): Promise<void>;
stop(): Promise<void>;
}
@@ -34,7 +34,7 @@ export interface Backend {
* @public
*/
export interface CreateSpecializedBackendOptions {
services: ServiceFactoryOrFunction[];
defaultServiceFactories: ServiceFactoryOrFunction[];
}
export interface ServiceHolder {
+1 -8
View File
@@ -4,14 +4,7 @@
```ts
import { Backend } from '@backstage/backend-app-api';
import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api';
// @public (undocumented)
export function createBackend(options?: CreateBackendOptions): Backend;
// @public (undocumented)
export interface CreateBackendOptions {
// (undocumented)
services?: ServiceFactoryOrFunction[];
}
export function createBackend(): Backend;
```
@@ -22,60 +22,63 @@ import { createBackend } from './CreateBackend';
describe('createBackend', () => {
it('should not throw when overriding a default service implementation', () => {
expect(() =>
createBackend({
services: [
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
const backend = createBackend();
expect(() => {
backend.add(
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
],
}),
).not.toThrow();
}),
);
}).not.toThrow();
});
it('should throw on duplicate service implementations', () => {
expect(() =>
createBackend({
services: [
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
}),
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
}),
],
const backend = createBackend();
backend.add(
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
}),
).toThrow(
);
expect(() => {
backend.add(
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
factory: async () => ({
addStartupHook: () => {},
addShutdownHook: () => {},
}),
}),
);
}).toThrow(
'Duplicate service implementations provided for core.rootLifecycle',
);
});
it('should throw when providing a plugin metadata service implementation', () => {
const backend = createBackend();
expect(() =>
createBackend({
services: [
createServiceFactory({
service: coreServices.pluginMetadata,
deps: {},
factory: async () => ({ getId: () => 'test' }),
}),
],
}),
backend.add(
createServiceFactory({
service: coreServices.pluginMetadata,
deps: {},
factory: async () => ({ getId: () => 'test' }),
}),
),
).toThrow('The core.pluginMetadata service cannot be overridden');
});
});
+2 -27
View File
@@ -33,10 +33,6 @@ import {
urlReaderServiceFactory,
identityServiceFactory,
} from '@backstage/backend-app-api';
import {
ServiceFactory,
ServiceFactoryOrFunction,
} from '@backstage/backend-plugin-api';
export const defaultServiceFactories = [
cacheServiceFactory(),
@@ -59,27 +55,6 @@ export const defaultServiceFactories = [
/**
* @public
*/
export interface CreateBackendOptions {
services?: ServiceFactoryOrFunction[];
}
/**
* @public
*/
export function createBackend(options?: CreateBackendOptions): Backend {
const services = new Array<ServiceFactory>();
// Highest priority: Services passed directly to createBackend
const providedServices = (options?.services ?? []).map(sf =>
typeof sf === 'function' ? sf() : sf,
);
services.push(...providedServices);
// Lowest priority: Default services that are not already provided by environment or directly to createBackend
const defaultServices = defaultServiceFactories.filter(
sf => !services.some(({ service }) => service.id === sf.service.id),
);
services.push(...defaultServices);
return createSpecializedBackend({ services });
export function createBackend(): Backend {
return createSpecializedBackend({ defaultServiceFactories });
}
-1
View File
@@ -25,5 +25,4 @@
// TODO(Rugvip): Remove this once backend-common is no longer used by backend-app-api
import '@backstage/backend-common';
export type { CreateBackendOptions } from './CreateBackend';
export { createBackend } from './CreateBackend';
+1 -3
View File
@@ -409,9 +409,7 @@ export type SearchResponseFile = {
export interface ServiceFactory<
TService = unknown,
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
> {
// (undocumented)
$$type: '@backstage/ServiceFactory';
> extends BackendFeature {
// (undocumented)
service: ServiceRef<TService, TScope>;
}
+1
View File
@@ -21,4 +21,5 @@
*/
export * from './services';
export type { BackendFeature } from './types';
export * from './wiring';
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { BackendFeature } from '../../types';
/**
* TODO
*
@@ -51,9 +53,7 @@ export type ServiceRef<
export interface ServiceFactory<
TService = unknown,
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
> {
$$type: '@backstage/ServiceFactory';
> extends BackendFeature {
service: ServiceRef<TService, TScope>;
}
@@ -248,7 +248,7 @@ export function createServiceFactory<
if (anyConf.service.scope === 'root') {
const c = anyConf as RootServiceFactoryConfig<TService, TImpl, TDeps>;
return {
$$type: '@backstage/ServiceFactory',
$$type: '@backstage/BackendFeature',
version: 'v1',
service: c.service,
deps: c.deps,
@@ -262,7 +262,7 @@ export function createServiceFactory<
TDeps
>;
return {
$$type: '@backstage/ServiceFactory',
$$type: '@backstage/BackendFeature',
version: 'v1',
service: c.service,
...('createRootContext' in c
+29
View File
@@ -0,0 +1,29 @@
/*
* 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.
*/
/** @internal */
export interface BackendFeatureFactory<
TOptions extends [options?: object] = [],
> {
(...options: TOptions): BackendFeature;
$$type: '@backstage/BackendFeatureFactory';
}
/** @public */
export interface BackendFeature {
// NOTE: This type is opaque in order to simplify future API evolution.
$$type: '@backstage/BackendFeature';
}
@@ -14,14 +14,13 @@
* limitations under the License.
*/
import { BackendFeature, BackendFeatureFactory } from '../types';
import {
BackendModuleRegistrationPoints,
BackendPluginRegistrationPoints,
ExtensionPoint,
InternalBackendModuleRegistration,
InternalBackendPluginRegistration,
BackendFeatureFactory,
BackendFeature,
} from './types';
/**
@@ -27,6 +27,5 @@ export {
export type {
BackendModuleRegistrationPoints,
BackendPluginRegistrationPoints,
BackendFeature,
ExtensionPoint,
} from './types';
@@ -15,6 +15,7 @@
*/
import { ServiceRef } from '../services/system/types';
import { BackendFeature } from '../types';
/**
* TODO
@@ -71,18 +72,6 @@ export interface BackendModuleRegistrationPoints {
}): void;
}
/** @internal */
export interface BackendFeatureFactory {
(): BackendFeature;
$$type: '@backstage/BackendFeatureFactory';
}
/** @public */
export interface BackendFeature {
// NOTE: This type is opaque in order to simplify future API evolution.
$$type: '@backstage/BackendFeature';
}
/** @internal */
export interface InternalBackendFeature extends BackendFeature {
version: 'v1';
+4 -20
View File
@@ -21,7 +21,6 @@ import { RootConfigService } from '@backstage/backend-plugin-api';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
import { SchedulerService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
import { TokenManagerService } from '@backstage/backend-plugin-api';
import { UrlReaderService } from '@backstage/backend-plugin-api';
@@ -127,11 +126,8 @@ export function setupRequestMockHandlers(worker: {
}): void;
// @public (undocumented)
export function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(
options: TestBackendOptions<TServices, TExtensionPoints>,
export function startTestBackend<TExtensionPoints extends any[]>(
options: TestBackendOptions<TExtensionPoints>,
): Promise<TestBackend>;
// @public (undocumented)
@@ -140,10 +136,7 @@ export interface TestBackend extends Backend {
}
// @public (undocumented)
export interface TestBackendOptions<
TServices extends any[],
TExtensionPoints extends any[],
> {
export interface TestBackendOptions<TExtensionPoints extends any[]> {
// (undocumented)
extensionPoints?: readonly [
...{
@@ -154,16 +147,7 @@ export interface TestBackendOptions<
},
];
// (undocumented)
features?: BackendFeature[];
// (undocumented)
services?: readonly [
...{
[index in keyof TServices]:
| ServiceFactory<TServices[index]>
| (() => ServiceFactory<TServices[index]>)
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
},
];
features?: Array<BackendFeature | (() => BackendFeature)>;
}
// @public
@@ -31,7 +31,6 @@ import { startTestBackend } from './TestBackend';
let globalTestBackendHasBeenStopped = false;
beforeAll(async () => {
await startTestBackend({
services: [],
features: [
createBackendModule({
moduleId: 'test.module',
@@ -46,7 +45,7 @@ beforeAll(async () => {
},
});
},
})(),
}),
],
});
});
@@ -68,17 +67,37 @@ describe('TestBackend', () => {
const extensionPoint5 = createExtensionPoint<Obj>({ id: 'b5' });
await expect(
startTestBackend({
services: [
features: [
// @ts-expect-error
[extensionPoint1, { a: 'a' }],
[serviceRef, { a: 'a' }],
[serviceRef, { a: 'a', b: 'b' }],
// @ts-expect-error
[serviceRef, { c: 'c' }],
// @ts-expect-error
[serviceRef, { a: 'a', c: 'c' }],
// @ts-expect-error
[serviceRef, { a: 'a', b: 'b', c: 'c' }],
createServiceFactory(() => ({
service: serviceRef,
deps: {},
// @ts-expect-error
factory: async () => ({ a: 'a' }),
})),
createServiceFactory(() => ({
service: serviceRef,
deps: {},
factory: async () => ({ a: 'a', b: 'b' }),
})),
createServiceFactory(() => ({
service: serviceRef,
deps: {},
// @ts-expect-error
factory: async () => ({ c: 'c' }),
})),
createServiceFactory(() => ({
service: serviceRef,
deps: {},
// @ts-expect-error
factory: async () => ({ a: 'a', c: 'c' }),
})),
createServiceFactory(() => ({
service: serviceRef,
deps: {},
factory: async () => ({ a: 'a', b: 'b', c: 'c' }),
})),
],
extensionPoints: [
// @ts-expect-error
@@ -124,8 +143,7 @@ describe('TestBackend', () => {
});
await startTestBackend({
services: [sf],
features: [testModule()],
features: [testModule(), sf()],
});
expect(testFn).toHaveBeenCalledWith('winning');
@@ -150,7 +168,6 @@ describe('TestBackend', () => {
});
const backend = await startTestBackend({
services: [],
features: [testModule()],
});
@@ -192,7 +209,6 @@ describe('TestBackend', () => {
});
await startTestBackend({
services: [],
features: [testPlugin()],
});
});
@@ -244,7 +260,7 @@ describe('TestBackend', () => {
},
});
},
})(),
}),
createBackendModule({
pluginId: 'testB',
moduleId: 'test',
@@ -256,7 +272,7 @@ describe('TestBackend', () => {
},
});
},
})(),
}),
],
}),
).resolves.not.toBeUndefined();
@@ -277,7 +293,7 @@ describe('TestBackend', () => {
async init() {},
});
},
})(),
}),
createBackendModule({
pluginId: 'testB',
moduleId: 'test',
@@ -287,7 +303,7 @@ describe('TestBackend', () => {
async init() {},
});
},
})(),
}),
],
}),
).rejects.toThrow(
@@ -24,8 +24,6 @@ import {
} from '@backstage/backend-app-api';
import { HostDiscovery } from '@backstage/backend-common';
import {
ServiceFactory,
ServiceRef,
createServiceFactory,
BackendFeature,
ExtensionPoint,
@@ -40,18 +38,7 @@ import express from 'express';
import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring/types';
/** @public */
export interface TestBackendOptions<
TServices extends any[],
TExtensionPoints extends any[],
> {
services?: readonly [
...{
[index in keyof TServices]:
| ServiceFactory<TServices[index]>
| (() => ServiceFactory<TServices[index]>)
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
},
];
export interface TestBackendOptions<TExtensionPoints extends any[]> {
extensionPoints?: readonly [
...{
[index in keyof TExtensionPoints]: [
@@ -60,7 +47,7 @@ export interface TestBackendOptions<
];
},
];
features?: BackendFeature[];
features?: Array<BackendFeature | (() => BackendFeature)>;
}
/** @public */
@@ -95,29 +82,37 @@ const defaultServiceFactories = [
* @returns
*/
function createExtensionPointTestModules(
features: BackendFeature[],
features: Array<BackendFeature | (() => BackendFeature)>,
extensionPointTuples?: readonly [
ref: ExtensionPoint<unknown>,
impl: unknown,
][],
): BackendFeature[] {
): Array<() => BackendFeature> {
if (!extensionPointTuples) {
return [];
}
const registrations = features.flatMap(feature => {
const registrations = features.flatMap(featureOrFunction => {
const feature =
typeof featureOrFunction === 'function'
? featureOrFunction()
: featureOrFunction;
if (feature.$$type !== '@backstage/BackendFeature') {
throw new Error(
`Failed to add feature, invalid type '${feature.$$type}'`,
);
}
const internalFeature = feature as InternalBackendFeature;
if (internalFeature.version !== 'v1') {
throw new Error(
`Failed to add feature, invalid version '${internalFeature.version}'`,
);
if (isInternalBackendFeature(feature)) {
if (feature.version !== 'v1') {
throw new Error(
`Failed to add feature, invalid version '${feature.version}'`,
);
}
return feature.getRegistrations();
}
return internalFeature.getRegistrations();
return [];
});
const extensionPointMap = new Map(
@@ -170,7 +165,7 @@ function createExtensionPointTestModules(
reg.registerInit({ deps: {}, async init() {} });
},
})(),
}),
);
}
@@ -180,18 +175,10 @@ function createExtensionPointTestModules(
const backendInstancesToCleanUp = new Array<Backend>();
/** @public */
export async function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(
options: TestBackendOptions<TServices, TExtensionPoints>,
export async function startTestBackend<TExtensionPoints extends any[]>(
options: TestBackendOptions<TExtensionPoints>,
): Promise<TestBackend> {
const {
services = [],
extensionPoints,
features = [],
...otherOptions
} = options;
const { extensionPoints, features = [], ...otherOptions } = options;
let server: ExtendedHttpServer;
@@ -247,39 +234,13 @@ export async function startTestBackend<
},
});
const factories = services.map(serviceDef => {
if (Array.isArray(serviceDef)) {
// if type is ExtensionPoint?
// do something differently?
const [ref, impl] = serviceDef;
if (ref.scope === 'plugin') {
return createServiceFactory({
service: ref as ServiceRef<unknown, 'plugin'>,
deps: {},
factory: async () => impl,
})();
}
return createServiceFactory({
service: ref as ServiceRef<unknown, 'root'>,
deps: {},
factory: async () => impl,
})();
}
if (typeof serviceDef === 'function') {
return serviceDef();
}
return serviceDef as ServiceFactory;
});
for (const factory of defaultServiceFactories) {
if (!factories.some(f => f.service.id === factory.service.id)) {
factories.push(factory);
}
}
const backend = createSpecializedBackend({
...otherOptions,
services: [...factories, rootHttpRouterFactory, discoveryFactory],
defaultServiceFactories: [
...defaultServiceFactories,
rootHttpRouterFactory,
discoveryFactory,
],
});
backendInstancesToCleanUp.push(backend);
@@ -329,3 +290,11 @@ function registerTestHooks() {
}
registerTestHooks();
function isInternalBackendFeature(
feature: BackendFeature,
): feature is InternalBackendFeature {
return (
typeof (feature as InternalBackendFeature).getRegistrations === 'function'
);
}
@@ -39,8 +39,8 @@ describe('appPlugin', () => {
it('boots', async () => {
const { server } = await startTestBackend({
features: [appPlugin()],
services: [
features: [
appPlugin(),
mockServices.rootConfig.factory({
data: {
app: {
@@ -15,13 +15,15 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleAwsS3EntityProvider } from './catalogModuleAwsS3EntityProvider';
@@ -45,7 +47,7 @@ describe('catalogModuleAwsS3EntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
awsS3: {
@@ -57,16 +59,24 @@ describe('catalogModuleAwsS3EntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleAwsS3EntityProvider(),
mockServices.rootConfig.factory({ data: config }),
createServiceFactory(() => ({
service: coreServices.logger,
deps: {},
factory: getVoidLogger,
}))(),
createServiceFactory(() => ({
service: coreServices.scheduler,
deps: {},
factory: () => scheduler,
}))(),
],
features: [catalogModuleAwsS3EntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleAzureDevOpsEntityProvider } from './catalogModuleAzureDevOpsEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleAzureDevOpsEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
azureDevOps: {
@@ -60,16 +61,20 @@ describe('catalogModuleAzureDevOpsEntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleAzureDevOpsEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
mockServices.logger.factory(),
createServiceFactory(() => ({
deps: {},
service: coreServices.scheduler,
factory: async () => scheduler,
})),
],
features: [catalogModuleAzureDevOpsEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
@@ -55,7 +58,8 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => {
[catalogProcessingExtensionPoint, catalogExtensionPointImpl],
[eventsExtensionPoint, eventsExtensionPointImpl],
],
services: [
features: [
catalogModuleBitbucketCloudEntityProvider(),
mockServices.rootConfig.factory({
data: {
catalog: {
@@ -71,9 +75,12 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => {
},
},
}),
[coreServices.scheduler, scheduler],
createServiceFactory({
service: coreServices.scheduler,
deps: {},
factory: async () => scheduler,
}),
],
features: [catalogModuleBitbucketCloudEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogModuleBitbucketServerEntityProvider } from './catalogModuleBitbucketServerEntityProvider';
import { Duration } from 'luxon';
@@ -45,7 +46,7 @@ describe('catalogModuleBitbucketServerEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
bitbucketServer: {
@@ -64,16 +65,20 @@ describe('catalogModuleBitbucketServerEntityProvider', () => {
},
],
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleBitbucketServerEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
mockServices.logger.factory(),
createServiceFactory({
service: coreServices.scheduler,
deps: {},
factory: async () => scheduler,
}),
],
features: [catalogModuleBitbucketServerEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGerritEntityProvider } from './catalogModuleGerritEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleGerritEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
gerrit: {
@@ -70,16 +71,20 @@ describe('catalogModuleGerritEntityProvider', () => {
},
],
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleGerritEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
mockServices.logger.factory(),
createServiceFactory({
service: coreServices.scheduler,
deps: {},
factory: async () => scheduler,
}),
],
features: [catalogModuleGerritEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,18 +14,19 @@
* limitations under the License.
*/
import { getVoidLogger } 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 { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGithubEntityProvider } from './catalogModuleGithubEntityProvider';
import { GithubEntityProvider } from '../providers/GithubEntityProvider';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
describe('catalogModuleGithubEntityProvider', () => {
it('should register provider at the catalog extension point', async () => {
@@ -45,7 +46,7 @@ describe('catalogModuleGithubEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
github: {
@@ -57,16 +58,19 @@ describe('catalogModuleGithubEntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleGithubEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
createServiceFactory({
service: coreServices.scheduler,
deps: {},
factory: async () => scheduler,
}),
],
features: [catalogModuleGithubEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGitlabDiscoveryEntityProvider } from './catalogModuleGitlabDiscoveryEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
integrations: {
gitlab: [
{
@@ -69,16 +70,20 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleGitlabDiscoveryEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
mockServices.logger.factory(),
createServiceFactory({
deps: {},
service: coreServices.scheduler,
factory: async () => scheduler,
}),
],
features: [catalogModuleGitlabDiscoveryEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
@@ -14,13 +14,12 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import {
coreServices,
createBackendModule,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { IncrementalEntityProvider } from '../types';
import {
@@ -43,26 +42,16 @@ describe('catalogModuleIncrementalIngestionEntityProvider', () => {
const addEntityProvider = jest.fn();
const httpRouterUse = jest.fn();
const scheduler = {};
const database = {
getClient: jest.fn(),
};
const httpRouter = {
use: httpRouterUse,
};
await startTestBackend({
extensionPoints: [
[catalogProcessingExtensionPoint, { addEntityProvider }],
],
services: [
[coreServices.rootConfig, new ConfigReader({})],
[coreServices.database, database],
[coreServices.httpRouter, httpRouter],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
],
features: [
createServiceFactory({
service: coreServices.httpRouter,
deps: {},
factory: () => ({ use: httpRouterUse }),
}),
catalogModuleIncrementalIngestionEntityProvider(),
createBackendModule({
pluginId: 'catalog',
@@ -82,7 +71,7 @@ describe('catalogModuleIncrementalIngestionEntityProvider', () => {
},
});
},
})(),
}),
],
});
@@ -55,16 +55,15 @@ async function main() {
},
};
const backend = createBackend({
services: [
createServiceFactory({
service: coreServices.rootConfig,
deps: {},
factory: () => new ConfigReader(config),
}),
],
});
const backend = createBackend();
backend.add(
createServiceFactory({
service: coreServices.rootConfig,
deps: {},
factory: () => new ConfigReader(config),
}),
);
backend.add(catalogPlugin());
backend.add(catalogModuleIncrementalIngestionEntityProvider());
backend.add(
@@ -86,7 +85,7 @@ async function main() {
},
});
},
})(),
}),
);
await backend.start();
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
microsoftGraphOrg: {
@@ -62,16 +63,19 @@ describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModuleMicrosoftGraphOrgEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
createServiceFactory(() => ({
deps: {},
service: coreServices.scheduler,
factory: async () => scheduler,
})),
],
features: [catalogModuleMicrosoftGraphOrgEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M'));
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogModulePuppetDbEntityProvider } from './catalogModulePuppetDbEntityProvider';
import { PuppetDbEntityProvider } from '../providers/PuppetDbEntityProvider';
@@ -44,7 +45,7 @@ describe('catalogModulePuppetDbEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
const config = {
catalog: {
providers: {
puppetdb: {
@@ -56,16 +57,19 @@ describe('catalogModulePuppetDbEntityProvider', () => {
},
},
},
});
};
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
catalogModulePuppetDbEntityProvider(),
mockServices.rootConfig.factory({ data: config }),
createServiceFactory(() => ({
deps: {},
service: coreServices.scheduler,
factory: async () => scheduler,
})),
],
features: [catalogModulePuppetDbEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual({ minutes: 10 });
@@ -180,7 +180,6 @@ describePerformanceTest('stitchingPerformance', () => {
const tracker = new Tracker(knex, load);
const backend = await startTestBackend({
services: [staticDatabase(knex)],
features: [
catalogPlugin(),
createBackendModule({
@@ -201,7 +200,8 @@ describePerformanceTest('stitchingPerformance', () => {
},
});
},
})(),
}),
staticDatabase(knex),
],
});
@@ -14,10 +14,12 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import {
coreServices,
createServiceFactory,
SchedulerService,
} from '@backstage/backend-plugin-api';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { TestEventBroker } from '@backstage/plugin-events-backend-test-utils';
import { eventsModuleAwsSqsConsumingEventPublisher } from './eventsModuleAwsSqsConsumingEventPublisher';
@@ -25,31 +27,6 @@ import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEvent
describe('eventsModuleAwsSqsConsumingEventPublisher', () => {
it('should be correctly wired and set up', async () => {
const config = new ConfigReader({
events: {
modules: {
awsSqs: {
awsSqsConsumingEventPublisher: {
topics: {
fake1: {
queue: {
region: 'eu-west-1',
url: 'https://fake1.queue.url',
},
},
fake2: {
queue: {
region: 'us-east-1',
url: 'https://fake2.queue.url',
},
},
},
},
},
},
},
});
let addedPublishers: AwsSqsConsumingEventPublisher[] | undefined;
const extensionPoint = {
addPublishers: (publishers: any) => {
@@ -59,16 +36,44 @@ describe('eventsModuleAwsSqsConsumingEventPublisher', () => {
const scheduler = {
scheduleTask: jest.fn(),
};
} as unknown as SchedulerService;
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [
[coreServices.rootConfig, config],
[coreServices.logger, getVoidLogger()],
[coreServices.scheduler, scheduler],
features: [
eventsModuleAwsSqsConsumingEventPublisher(),
mockServices.rootConfig.factory({
data: {
events: {
modules: {
awsSqs: {
awsSqsConsumingEventPublisher: {
topics: {
fake1: {
queue: {
region: 'eu-west-1',
url: 'https://fake1.queue.url',
},
},
fake2: {
queue: {
region: 'us-east-1',
url: 'https://fake2.queue.url',
},
},
},
},
},
},
},
},
}),
createServiceFactory({
service: coreServices.scheduler,
deps: {},
factory: () => scheduler,
}),
],
features: [eventsModuleAwsSqsConsumingEventPublisher()],
});
expect(addedPublishers).not.toBeUndefined();
@@ -34,7 +34,6 @@ describe('eventsModuleAzureDevOpsEventRouter', () => {
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [],
features: [eventsModuleAzureDevOpsEventRouter()],
});
@@ -34,7 +34,6 @@ describe('eventsModuleBitbucketCloudEventRouter', () => {
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [],
features: [eventsModuleBitbucketCloudEventRouter()],
});
@@ -34,7 +34,6 @@ describe('eventsModuleGerritEventRouter', () => {
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [],
features: [eventsModuleGerritEventRouter()],
});
@@ -34,7 +34,6 @@ describe('eventsModuleGithubEventRouter', () => {
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [],
features: [eventsModuleGithubEventRouter()],
});
@@ -14,9 +14,7 @@
* limitations under the License.
*/
import { coreServices } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import {
HttpPostIngressOptions,
@@ -47,20 +45,22 @@ describe('eventsModuleGithubWebhook', () => {
},
};
const config = new ConfigReader({
events: {
modules: {
github: {
webhookSecret: secret,
},
},
},
});
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [[coreServices.rootConfig, config]],
features: [eventsModuleGithubWebhook()],
features: [
eventsModuleGithubWebhook(),
mockServices.rootConfig.factory({
data: {
events: {
modules: {
github: {
webhookSecret: secret,
},
},
},
},
}),
],
});
expect(addedIngress).not.toBeUndefined();
@@ -34,7 +34,6 @@ describe('eventsModuleGitlabEventRouter', () => {
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [],
features: [eventsModuleGitlabEventRouter()],
});
@@ -14,9 +14,7 @@
* limitations under the License.
*/
import { coreServices } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { ConfigReader } from '@backstage/config';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import {
HttpPostIngressOptions,
@@ -42,20 +40,22 @@ describe('gitlabWebhookEventsModule', () => {
},
};
const config = new ConfigReader({
events: {
modules: {
gitlab: {
webhookSecret: 'test-secret',
},
},
},
});
await startTestBackend({
extensionPoints: [[eventsExtensionPoint, extensionPoint]],
services: [[coreServices.rootConfig, config]],
features: [eventsModuleGitlabWebhook()],
features: [
eventsModuleGitlabWebhook(),
mockServices.rootConfig.factory({
data: {
events: {
modules: {
gitlab: {
webhookSecret: 'test-secret',
},
},
},
},
}),
],
});
expect(addedIngress).not.toBeUndefined();
@@ -14,13 +14,13 @@
* limitations under the License.
*/
import { errorHandler, getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { errorHandler } from '@backstage/backend-common';
import {
coreServices,
createBackendModule,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import {
TestEventBroker,
@@ -38,14 +38,6 @@ describe('eventPlugin', () => {
const publisher = new TestEventPublisher();
const subscriber = new TestEventSubscriber('sub', ['fake']);
const config = new ConfigReader({
events: {
http: {
topics: ['fake'],
},
},
});
const httpRouter = Router();
httpRouter.use(express.json());
httpRouter.use(errorHandler());
@@ -70,12 +62,25 @@ describe('eventPlugin', () => {
await startTestBackend({
extensionPoints: [],
services: [
[coreServices.rootConfig, config],
[coreServices.httpRouter, httpRouter],
[coreServices.logger, getVoidLogger()],
features: [
eventsPlugin(),
testModule(),
mockServices.logger.factory(),
mockServices.rootConfig.factory({
data: {
events: {
http: {
topics: ['fake'],
},
},
},
}),
createServiceFactory({
service: coreServices.httpRouter,
deps: {},
factory: async () => httpRouter,
}),
],
features: [eventsPlugin(), testModule()],
});
expect(publisher.eventBroker).toBe(eventBroker);
@@ -49,8 +49,8 @@ describe('searchModuleCatalogCollator', () => {
},
],
],
features: [searchModuleCatalogCollator()],
services: [
features: [
searchModuleCatalogCollator(),
mockServices.rootConfig.factory({
data: {
search: {
@@ -34,8 +34,8 @@ describe('searchModuleExploreCollator', () => {
extensionPoints: [
[searchIndexRegistryExtensionPoint, extensionPointMock],
],
features: [searchModuleExploreCollator()],
services: [
features: [
searchModuleExploreCollator(),
mockServices.rootConfig.factory({
data: {
search: {
@@ -34,8 +34,8 @@ describe('searchModuleTechDocsCollator', () => {
extensionPoints: [
[searchIndexRegistryExtensionPoint, extensionPointMock],
],
features: [searchModuleTechDocsCollator()],
services: [
features: [
searchModuleTechDocsCollator(),
mockServices.rootConfig.factory({
data: {
search: {