Refactor backend-test-utils

Co-authored-by: Fredrik Adelöw <freben@users.noreply.github.com>
Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>

Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2022-08-12 15:30:24 +02:00
parent 0ef2719b11
commit 0d2e6197d8
9 changed files with 89 additions and 41 deletions
@@ -19,15 +19,17 @@ import {
ExtensionPoint,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { BackendRegisterInit, ServiceHolder } from './types';
type ServiceOrExtensionPoint = ExtensionPoint<unknown> | ServiceRef<unknown>;
import {
BackendRegisterInit,
ServiceHolder,
ServiceOrExtensionPoint,
} from './types';
export class BackendInitializer {
#started = false;
#features = new Map<BackendFeature, unknown>();
#registerInits = new Array<BackendRegisterInit>();
#extensionPoints = new Map<ServiceOrExtensionPoint, unknown>();
#extensionPoints = new Map<ExtensionPoint<unknown>, unknown>();
#serviceHolder: ServiceHolder;
constructor(serviceHolder: ServiceHolder) {
@@ -42,7 +44,7 @@ export class BackendInitializer {
await Promise.all(
Object.entries(deps).map(async ([name, ref]) => [
name,
this.#extensionPoints.get(ref) ||
this.#extensionPoints.get(ref as ExtensionPoint<unknown>) ||
(await this.#serviceHolder.get(ref as ServiceRef<unknown>)!(
pluginId,
)),
@@ -66,7 +68,7 @@ export class BackendInitializer {
this.#started = true;
for (const [feature] of this.#features) {
const provides = new Set<ServiceRef<unknown>>();
const provides = new Set<ExtensionPoint<unknown>>();
let registerInit: BackendRegisterInit | undefined = undefined;
@@ -129,13 +131,13 @@ export class BackendInitializer {
for (const registerInit of registerInitsToOrder) {
const unInitializedDependents = [];
for (const serviceRef of registerInit.provides) {
for (const provided of registerInit.provides) {
if (
registerInitsToOrder.some(
init => init !== registerInit && init.consumes.has(serviceRef),
init => init !== registerInit && init.consumes.has(provided),
)
) {
unInitializedDependents.push(serviceRef);
unInitializedDependents.push(provided);
}
}
@@ -148,6 +150,7 @@ export class BackendInitializer {
registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r));
}
return orderedRegisterInits;
}
}
+5 -1
View File
@@ -14,5 +14,9 @@
* limitations under the License.
*/
export type { Backend, CreateSpecializedBackendOptions } from './types';
export type {
Backend,
CreateSpecializedBackendOptions,
ServiceOrExtensionPoint,
} from './types';
export { createSpecializedBackend } from './types';
+11 -3
View File
@@ -17,6 +17,7 @@
import {
AnyServiceFactory,
BackendFeature,
ExtensionPoint,
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
@@ -32,9 +33,9 @@ export interface Backend {
export interface BackendRegisterInit {
id: string;
consumes: Set<ServiceRef<unknown>>;
provides: Set<ServiceRef<unknown>>;
deps: { [name: string]: ServiceRef<unknown> };
consumes: Set<ServiceOrExtensionPoint>;
provides: Set<ServiceOrExtensionPoint>;
deps: { [name: string]: ServiceOrExtensionPoint };
init: (deps: { [name: string]: unknown }) => Promise<void>;
}
@@ -57,3 +58,10 @@ export function createSpecializedBackend(
): Backend {
return new BackstageBackend(options.services);
}
/**
* @public
*/
export type ServiceOrExtensionPoint<T = unknown> =
| ExtensionPoint<T>
| ServiceRef<T>;
@@ -38,12 +38,14 @@ export type ExtensionPoint<T> = {
/** @public */
export interface BackendRegistrationPoints {
registerExtensionPoint<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerInit<Deps extends { [name in string]: unknown }>(options: {
deps: { [name in keyof Deps]: ServiceRef<Deps[name]> };
init: (deps: Deps) => Promise<void>;
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]> | ExtensionPoint<Deps[name]>;
};
init(deps: Deps): Promise<void>;
}): void;
}
@@ -19,12 +19,12 @@ import {
createServiceFactory,
createServiceRef,
} from '@backstage/backend-plugin-api';
import { createTestBackend, startTestBackend } from './TestBackend';
import { startTestBackend } from './TestBackend';
describe('TestBackend', () => {
it('should get a type error if service implementation does not match', () => {
const serviceRef = createServiceRef<{ a: string; b: string }>({ id: 'a' });
const backend = createTestBackend({
const backend = startTestBackend({
services: [
[serviceRef, { a: 'a' }],
[serviceRef, { a: 'a', b: 'b' }],
@@ -68,7 +68,7 @@ describe('TestBackend', () => {
await startTestBackend({
services: [sf],
registrables: [testModule({})],
features: [testModule({})],
});
expect(testFn).toBeCalledWith('winning');
@@ -14,31 +14,54 @@
* limitations under the License.
*/
import { Backend, createSpecializedBackend } from '@backstage/backend-app-api';
import { createSpecializedBackend } from '@backstage/backend-app-api';
import {
AnyServiceFactory,
ServiceRef,
createServiceFactory,
BackendFeature,
ExtensionPoint,
} from '@backstage/backend-plugin-api';
/** @alpha */
export interface TestBackendOptions<TServices extends any[]> {
services: readonly [
export interface TestBackendOptions<
TServices extends any[],
TExtensionPoints extends any[],
> {
services?: readonly [
...{
[index in keyof TServices]:
| AnyServiceFactory
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
},
];
extensionPoints?: readonly [
...{
[index in keyof TExtensionPoints]: [
ExtensionPoint<TExtensionPoints[index]>,
Partial<TExtensionPoints[index]>,
];
},
];
features?: BackendFeature[];
}
/** @alpha */
export function createTestBackend<TServices extends any[]>(
options: TestBackendOptions<TServices>,
): Backend {
const factories = options.services?.map(serviceDef => {
export async function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<void> {
const {
services = [],
extensionPoints = [],
features = [],
...otherOptions
} = options;
const factories = services.map(serviceDef => {
if (Array.isArray(serviceDef)) {
// if type is ExtensionPoint?
// do something differently?
return createServiceFactory({
service: serviceDef[0],
deps: {},
@@ -47,19 +70,26 @@ export function createTestBackend<TServices extends any[]>(
}
return serviceDef as AnyServiceFactory;
});
return createSpecializedBackend({ services: factories ?? [] });
}
/** @alpha */
export async function startTestBackend<TServices extends any[]>(
options: TestBackendOptions<TServices> & {
features?: BackendFeature[];
},
): Promise<void> {
const { features = [], ...otherOptions } = options;
const backend = createTestBackend(otherOptions);
const backend = createSpecializedBackend({
...otherOptions,
services: factories,
});
backend.add({
id: `---test-extension-point-registrar`,
register(reg) {
for (const [ref, impl] of extensionPoints) {
reg.registerExtensionPoint(ref, impl);
}
reg.registerInit({ deps: {}, async init() {} });
},
});
for (const feature of features) {
backend.add(feature);
}
await backend.start();
}
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { createTestBackend, startTestBackend } from './TestBackend';
export { startTestBackend } from './TestBackend';
export type { TestBackendOptions } from './TestBackend';
+2 -2
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createServiceRef } from '@backstage/backend-plugin-api';
import { createExtensionPoint } from '@backstage/backend-plugin-api';
import { EntityProvider } from './api';
import { CatalogProcessor } from './api/processor';
@@ -29,6 +29,6 @@ export interface CatalogProcessingExtensionPoint {
* @alpha
*/
export const catalogProcessingExtensionPoint =
createServiceRef<CatalogProcessingExtensionPoint>({
createExtensionPoint<CatalogProcessingExtensionPoint>({
id: 'catalog.processing',
});
@@ -23,8 +23,9 @@ describe('ScaffolderCatalogModule', () => {
it('should register the extension point', async () => {
const extensionPoint = { addProcessor: jest.fn() };
await startTestBackend({
services: [[catalogProcessingExtensionPoint, extensionPoint]],
registrables: [scaffolderCatalogModule({})],
services: [],
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
features: [scaffolderCatalogModule({})],
});
expect(extensionPoint.addProcessor).toHaveBeenCalledWith(