Merge pull request #19654 from backstage/rugvip/service-tester

backend-test-utils: add ServiceFactoryTester utility
This commit is contained in:
Patrik Oldsberg
2023-08-30 20:11:02 +02:00
committed by GitHub
10 changed files with 371 additions and 104 deletions
@@ -14,39 +14,29 @@
* limitations under the License.
*/
import {
ServiceFactoryTester,
mockServices,
} from '@backstage/backend-test-utils';
import { httpRouterServiceFactory } from './httpRouterServiceFactory';
describe('httpRouterFactory', () => {
it('should register plugin paths', async () => {
const rootHttpRouter = { use: jest.fn() };
const factory = httpRouterServiceFactory() as any;
const rootHttpRouter = mockServices.rootHttpRouter.mock();
const tester = ServiceFactoryTester.from(httpRouterServiceFactory, {
dependencies: [rootHttpRouter.factory],
});
const handler1 = () => {};
const router1 = await factory.factory(
{
rootHttpRouter,
plugin: { getId: () => 'test1' },
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
},
undefined,
);
router1.use(handler1);
const router1 = await tester.get('test1');
router1.use(() => {});
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
expect(rootHttpRouter.use).toHaveBeenCalledWith(
'/api/test1',
expect.any(Function),
);
const handler2 = () => {};
const router2 = await factory.factory(
{
rootHttpRouter,
plugin: { getId: () => 'test2' },
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
},
undefined,
);
router2.use(handler2);
const router2 = await tester.get('test2');
router2.use(() => {});
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
expect(rootHttpRouter.use).toHaveBeenCalledWith(
'/api/test2',
@@ -55,37 +45,24 @@ describe('httpRouterFactory', () => {
});
it('should use custom path generator', async () => {
const rootHttpRouter = { use: jest.fn() };
const factory = httpRouterServiceFactory({
getPath: id => `/some/${id}/path`,
}) as any;
const handler1 = () => {};
const router1 = await factory.factory(
{
rootHttpRouter,
plugin: { getId: () => 'test1' },
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
},
undefined,
const rootHttpRouter = mockServices.rootHttpRouter.mock();
const tester = ServiceFactoryTester.from(
httpRouterServiceFactory({
getPath: id => `/some/${id}/path`,
}),
{ dependencies: [rootHttpRouter.factory] },
);
router1.use(handler1);
const router1 = await tester.get('test1');
router1.use(() => {});
expect(rootHttpRouter.use).toHaveBeenCalledTimes(1);
expect(rootHttpRouter.use).toHaveBeenCalledWith(
'/some/test1/path',
expect.any(Function),
);
const handler2 = () => {};
const router2 = await factory.factory(
{
rootHttpRouter,
plugin: { getId: () => 'test2' },
lifecycle: { addStartupHook() {}, addShutdownHook() {} },
},
undefined,
);
router2.use(handler2);
const router2 = await tester.get('test2');
router2.use(() => {});
expect(rootHttpRouter.use).toHaveBeenCalledTimes(2);
expect(rootHttpRouter.use).toHaveBeenCalledWith(
'/some/test2/path',
@@ -14,54 +14,33 @@
* limitations under the License.
*/
import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { coreServices } from '@backstage/backend-plugin-api';
import { ServiceFactoryTester } from '@backstage/backend-test-utils';
import { schedulerServiceFactory } from './schedulerServiceFactory';
describe('schedulerFactory', () => {
it('creates sidecar database features', async () => {
expect.assertions(3);
const tester = ServiceFactoryTester.from(schedulerServiceFactory());
const subject = schedulerServiceFactory();
const plugin = createBackendPlugin({
pluginId: 'example',
register(reg) {
reg.registerInit({
deps: {
scheduler: subject.service,
database: coreServices.database,
},
init: async ({ scheduler, database }) => {
await scheduler.scheduleTask({
id: 'task1',
timeout: { seconds: 1 },
frequency: { seconds: 1 },
fn: async () => {},
});
const client = await database.getClient();
await expect(
client.from('backstage_backend_tasks__tasks').count(),
).resolves.toEqual([{ 'count(*)': 1 }]);
await expect(
client.from('backstage_backend_tasks__knex_migrations').count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
await expect(
client
.from('backstage_backend_tasks__knex_migrations_lock')
.count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
},
});
},
const scheduler = await tester.get();
await scheduler.scheduleTask({
id: 'task1',
timeout: { seconds: 1 },
frequency: { seconds: 1 },
fn: async () => {},
});
await startTestBackend({
features: [plugin(), subject],
});
const database = await tester.getService(coreServices.database);
const client = await database.getClient();
await expect(
client.from('backstage_backend_tasks__tasks').count(),
).resolves.toEqual([{ 'count(*)': 1 }]);
await expect(
client.from('backstage_backend_tasks__knex_migrations').count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
await expect(
client.from('backstage_backend_tasks__knex_migrations_lock').count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
});
});
@@ -58,9 +58,9 @@ const pluginMetadataServiceFactory = createServiceFactory(
);
export class ServiceRegistry implements EnumerableServiceHolder {
static create(factories: Array<ServiceFactory>): EnumerableServiceHolder {
static create(factories: Array<ServiceFactory>): ServiceRegistry {
const registry = new ServiceRegistry(factories);
registry.checkForCircularDeps();
registry.#checkForCircularDeps();
return registry;
}
@@ -80,7 +80,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
InternalServiceFactory,
Promise<unknown>
>();
readonly #dependencyGraph: DependencyGraph<string>;
private constructor(factories: Array<ServiceFactory>) {
this.#providedFactories = new Map(
@@ -88,15 +87,6 @@ export class ServiceRegistry implements EnumerableServiceHolder {
);
this.#loadedDefaultFactories = new Map();
this.#implementations = new Map();
this.#dependencyGraph = DependencyGraph.fromIterable(
Array.from(this.#providedFactories).map(
([serviceId, serviceFactory]) => ({
value: serviceId,
provides: [serviceId],
consumes: Object.values(serviceFactory.deps).map(d => d.id),
}),
),
);
}
#resolveFactory(
@@ -163,10 +153,17 @@ export class ServiceRegistry implements EnumerableServiceHolder {
}
}
checkForCircularDeps(): void {
const circularDependencies = Array.from(
this.#dependencyGraph.detectCircularDependencies(),
#checkForCircularDeps(): void {
const graph = DependencyGraph.fromIterable(
Array.from(this.#providedFactories).map(
([serviceId, serviceFactory]) => ({
value: serviceId,
provides: [serviceId],
consumes: Object.values(serviceFactory.deps).map(d => d.id),
}),
),
);
const circularDependencies = Array.from(graph.detectCircularDependencies());
if (circularDependencies.length) {
const cycles = circularDependencies