From ba53aa668e00030544503af0113ed0212e1f2789 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Mon, 16 Dec 2024 16:45:26 -0600 Subject: [PATCH] update mockServices Signed-off-by: Paul Schultz --- packages/backend-test-utils/report.api.md | 8 +- .../next/services/MockAuditorService.test.ts | 143 +++++------------- .../src/next/services/MockAuditorService.ts | 119 ++++++++------- .../src/next/services/mockServices.ts | 33 ++-- 4 files changed, 114 insertions(+), 189 deletions(-) diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md index 4f6998e1ce..9ebf02d5b8 100644 --- a/packages/backend-test-utils/report.api.md +++ b/packages/backend-test-utils/report.api.md @@ -3,12 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// /// /// -/// -import type { AuditorOptions } from '@backstage/backend-defaults/auditor'; import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; @@ -37,6 +34,7 @@ import { ParamsDictionary } from 'express-serve-static-core'; import { ParsedQs } from 'qs'; import { PermissionsRegistryService } from '@backstage/backend-plugin-api'; import { PermissionsService } from '@backstage/backend-plugin-api'; +import type { RootAuditorOptions } from '@backstage/backend-defaults/auditor'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootHealthService } from '@backstage/backend-plugin-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; @@ -156,14 +154,14 @@ export function mockErrorHandler(): ErrorRequestHandler< // @public export namespace mockServices { export function auditor( - options?: Omit & { + options?: auditor.Options & { pluginId?: string; }, ): AuditorService; // (undocumented) export namespace auditor { // (undocumented) - export type Options = Omit; + export type Options = RootAuditorOptions; const // (undocumented) factory: ( options?: auditor.Options, diff --git a/packages/backend-test-utils/src/next/services/MockAuditorService.test.ts b/packages/backend-test-utils/src/next/services/MockAuditorService.test.ts index 5dfbe560e2..b11f633ea8 100644 --- a/packages/backend-test-utils/src/next/services/MockAuditorService.test.ts +++ b/packages/backend-test-utils/src/next/services/MockAuditorService.test.ts @@ -15,7 +15,7 @@ */ import type { ErrorLike } from '@backstage/errors'; -import { MockAuditorService } from './MockAuditorService'; +import { MockRootAuditorService } from './MockAuditorService'; import { MockAuthService } from './MockAuthService'; import { MockHttpAuthService } from './MockHttpAuthService'; import { mockCredentials } from './mockCredentials'; @@ -25,81 +25,21 @@ describe('MockAuditorService', () => { jest.resetAllMocks(); }); - it('should error without plugin service', async () => { - const auditor = MockAuditorService.create(); - await expect( - auditor.createEvent({ - eventId: 'test-event', - }), - ).rejects.toThrow( - `The core service 'plugin' was not provided during the auditor's instantiation`, - ); - }); - - it('should error without auth service', async () => { - const pluginId = 'test-plugin'; - - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - }, - ); - - await expect( - auditor.createEvent({ - eventId: 'test-event', - }), - ).rejects.toThrow( - `The core service 'auth' was not provided during the auditor's instantiation`, - ); - }); - - it('should error without httpAuth service', async () => { - const pluginId = 'test-plugin'; - - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - auth: new MockAuthService({ - pluginId, - disableDefaultAuthPolicy: false, - }), - }, - ); - - await expect( - auditor.createEvent({ - eventId: 'test-event', - }), - ).rejects.toThrow( - `The core service 'httpAuth' was not provided during the auditor's instantiation`, - ); - }); - it('should log', async () => { jest.spyOn(console, 'log').mockImplementation(() => {}); const pluginId = 'test-plugin'; - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - auth: new MockAuthService({ - pluginId, - disableDefaultAuthPolicy: false, - }), - httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + const auditor = MockRootAuditorService.create().forPlugin({ + plugin: { + getId: () => pluginId, }, - ); + auth: new MockAuthService({ + pluginId, + disableDefaultAuthPolicy: false, + }), + httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + }); await auditor.createEvent({ eventId: 'test-event', @@ -113,19 +53,16 @@ describe('MockAuditorService', () => { const pluginId = 'test-plugin'; - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - auth: new MockAuthService({ - pluginId, - disableDefaultAuthPolicy: false, - }), - httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + const auditor = MockRootAuditorService.create().forPlugin({ + plugin: { + getId: () => pluginId, }, - ); + auth: new MockAuthService({ + pluginId, + disableDefaultAuthPolicy: false, + }), + httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + }); await auditor.createEvent({ eventId: 'test-event', @@ -139,19 +76,16 @@ describe('MockAuditorService', () => { const pluginId = 'test-plugin'; - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - auth: new MockAuthService({ - pluginId, - disableDefaultAuthPolicy: false, - }), - httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + const auditor = MockRootAuditorService.create().forPlugin({ + plugin: { + getId: () => pluginId, }, - ); + auth: new MockAuthService({ + pluginId, + disableDefaultAuthPolicy: false, + }), + httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + }); const auditorEvent = await auditor.createEvent({ eventId: 'test-event', @@ -167,19 +101,16 @@ describe('MockAuditorService', () => { const pluginId = 'test-plugin'; - const auditor = MockAuditorService.create().child( - {}, - { - plugin: { - getId: () => pluginId, - }, - auth: new MockAuthService({ - pluginId, - disableDefaultAuthPolicy: false, - }), - httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + const auditor = MockRootAuditorService.create().forPlugin({ + plugin: { + getId: () => pluginId, }, - ); + auth: new MockAuthService({ + pluginId, + disableDefaultAuthPolicy: false, + }), + httpAuth: new MockHttpAuthService(pluginId, mockCredentials.user()), + }); const auditorEvent = await auditor.createEvent({ eventId: 'test-event', diff --git a/packages/backend-test-utils/src/next/services/MockAuditorService.ts b/packages/backend-test-utils/src/next/services/MockAuditorService.ts index 3e99986758..2d34ffae9e 100644 --- a/packages/backend-test-utils/src/next/services/MockAuditorService.ts +++ b/packages/backend-test-utils/src/next/services/MockAuditorService.ts @@ -26,27 +26,47 @@ import type { HttpAuthService, PluginMetadataService, } from '@backstage/backend-plugin-api'; -import { ForwardedError, ServiceUnavailableError } from '@backstage/errors'; +import { ForwardedError } from '@backstage/errors'; import type { JsonObject } from '@backstage/types'; import type { Request } from 'express'; import type { mockServices } from './mockServices'; export class MockAuditorService implements AuditorService { - readonly #options: mockServices.auditor.Options & { - auth?: AuthService; - httpAuth?: HttpAuthService; - plugin?: PluginMetadataService; - }; + private readonly impl: MockRootAuditorService; + private readonly auth: AuthService; + private readonly httpAuth: HttpAuthService; + private readonly plugin: PluginMetadataService; - static create(options?: mockServices.auditor.Options): MockAuditorService { - return new MockAuditorService(options ?? {}); + private constructor( + impl: MockRootAuditorService, + deps: { + auth: AuthService; + httpAuth: HttpAuthService; + plugin: PluginMetadataService; + }, + ) { + this.impl = impl; + this.auth = deps.auth; + this.httpAuth = deps.httpAuth; + this.plugin = deps.plugin; + } + + static create( + impl: MockRootAuditorService, + deps: { + auth: AuthService; + httpAuth: HttpAuthService; + plugin: PluginMetadataService; + }, + ): AuditorService { + return new MockAuditorService(impl, deps); } private async log( options: AuditorEventOptions, ): Promise { const auditEvent = await this.reshapeAuditorEvent(options); - this.#log(...auditEvent); + this.impl.log(auditEvent); } async createEvent( @@ -89,57 +109,25 @@ export class MockAuditorService implements AuditorService { }; } - child( - meta: JsonObject, - deps?: { - auth?: AuthService; - httpAuth?: HttpAuthService; - plugin?: PluginMetadataService; - }, - ): AuditorService { - return new MockAuditorService({ - ...this.#options, - auth: deps?.auth, - httpAuth: deps?.httpAuth, - plugin: deps?.plugin, - meta: { - ...this.#options.meta, - ...meta, - }, - }); - } - private async getActorId( request?: Request, ): Promise { - if (!this.#options.auth) { - throw new ServiceUnavailableError( - `The core service 'auth' was not provided during the auditor's instantiation`, - ); - } - - if (!this.#options.httpAuth) { - throw new ServiceUnavailableError( - `The core service 'httpAuth' was not provided during the auditor's instantiation`, - ); - } - let credentials: BackstageCredentials = - await this.#options.auth.getOwnServiceCredentials(); + await this.auth.getOwnServiceCredentials(); if (request) { try { - credentials = await this.#options.httpAuth.credentials(request); + credentials = await this.httpAuth.credentials(request); } catch (error) { throw new ForwardedError('Could not resolve credentials', error); } } - if (this.#options.auth.isPrincipal(credentials, 'user')) { + if (this.auth.isPrincipal(credentials, 'user')) { return credentials.principal.userEntityRef; } - if (this.#options.auth.isPrincipal(credentials, 'service')) { + if (this.auth.isPrincipal(credentials, 'service')) { return credentials.principal.subject; } @@ -151,14 +139,8 @@ export class MockAuditorService implements AuditorService { ): Promise { const { eventId, severityLevel = 'low', request, ...rest } = options; - if (!this.#options.plugin) { - throw new ServiceUnavailableError( - `The core service 'plugin' was not provided during the auditor's instantiation`, - ); - } - const auditEvent: AuditorEvent = [ - `${this.#options.plugin.getId()}.${eventId}`, + `${this.plugin.getId()}.${eventId}`, { severityLevel, actor: { @@ -179,15 +161,32 @@ export class MockAuditorService implements AuditorService { return auditEvent; } +} - private constructor( - options: mockServices.auditor.Options & { - auth?: AuthService; - httpAuth?: HttpAuthService; - plugin?: PluginMetadataService; - }, - ) { - this.#options = options; +export class MockRootAuditorService { + private readonly options: mockServices.auditor.Options; + + private constructor(options?: mockServices.auditor.Options) { + this.options = options ?? {}; + } + + static create( + options?: mockServices.auditor.Options, + ): MockRootAuditorService { + return new MockRootAuditorService(options); + } + + async log(auditEvent: AuditorEvent): Promise { + this.#log(...auditEvent); + } + + forPlugin(deps: { + auth: AuthService; + httpAuth: HttpAuthService; + plugin: PluginMetadataService; + }): AuditorService { + const impl = new MockRootAuditorService(this.options); + return MockAuditorService.create(impl, deps); } #log(message: string, meta?: AuditorEvent[1]) { diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index ad1ee24e5b..e5c8db185b 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -51,7 +51,7 @@ import { } from '@backstage/plugin-events-node'; import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; -import { MockAuditorService } from './MockAuditorService'; +import { MockRootAuditorService } from './MockAuditorService'; import { MockAuthService } from './MockAuthService'; import { MockHttpAuthService } from './MockHttpAuthService'; import { MockRootLoggerService } from './MockRootLoggerService'; @@ -228,7 +228,7 @@ export namespace mockServices { * Creates a mock implementation of the `AuditorService`. */ export function auditor( - options?: Omit & { + options?: auditor.Options & { pluginId?: string; }, ): AuditorService { @@ -247,18 +247,19 @@ export namespace mockServices { getId: () => pluginId, }; - const auditorMock = MockAuditorService.create({ + const auditorMock = MockRootAuditorService.create({ meta: options?.meta ? { ...options.meta, service } : { service }, }); - return auditorMock.child( - { pluginId }, - { auth: mockAuth, httpAuth: mockHttpAuth, plugin: mockPlugin }, - ); + return auditorMock.forPlugin({ + auth: mockAuth, + httpAuth: mockHttpAuth, + plugin: mockPlugin, + }); } export namespace auditor { - export type Options = Omit; + export type Options = RootAuditorOptions; export const factory = (options?: auditor.Options) => createServiceFactory({ @@ -271,23 +272,19 @@ export namespace mockServices { createRootContext() { const service = 'backstage'; - return MockAuditorService.create({ + return MockRootAuditorService.create({ meta: options?.meta ? { ...options.meta, service } : { service }, }); }, factory( - { - plugin, + { auth: mockAuth, httpAuth: mockHttpAuth, plugin: mockPlugin }, + rootAuditor, + ) { + return rootAuditor.forPlugin({ auth: mockAuth, httpAuth: mockHttpAuth, plugin: mockPlugin, - }, - rootAuditor, - ) { - return rootAuditor.child( - { plugin: plugin.getId() }, - { auth: mockAuth, httpAuth: mockHttpAuth, plugin: mockPlugin }, - ); + }); }, });