From abaaf4970a9997f5f2f7d2cf7d33edeec22ec59b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Feb 2024 00:38:58 +0100 Subject: [PATCH] backend-test-utils: add mocks for AuthService and HttpAuthService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Carl-Erik Bergström Co-authored-by: blam Signed-off-by: Patrik Oldsberg --- packages/backend-test-utils/api-report.md | 22 +++++ .../src/next/services/MockAuthService.ts | 90 +++++++++++++++++++ .../src/next/services/mockServices.ts | 28 ++++++ .../src/next/wiring/TestBackend.ts | 2 + 4 files changed, 142 insertions(+) create mode 100644 packages/backend-test-utils/src/next/services/MockAuthService.ts diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 204e5dc84a..a29a782af0 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -6,12 +6,14 @@ /// /// +import { AuthService } from '@backstage/backend-plugin-api'; import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CacheService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HttpRouterFactoryOptions } from '@backstage/backend-app-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; @@ -86,6 +88,17 @@ export interface MockDirectoryOptions { // @public (undocumented) export namespace mockServices { + // (undocumented) + export function auth(options?: { pluginId?: string }): AuthService; + // (undocumented) + export namespace auth { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } // (undocumented) export namespace cache { const // (undocumented) @@ -105,6 +118,15 @@ export namespace mockServices { ) => ServiceMock; } // (undocumented) + export namespace httpAuth { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } + // (undocumented) export namespace httpRouter { const // (undocumented) factory: ( diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.ts b/packages/backend-test-utils/src/next/services/MockAuthService.ts new file mode 100644 index 0000000000..9af9be4818 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockAuthService.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2024 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 { + BackstageCredentials, + BackstageServicePrincipal, + BackstagePrincipalTypes, + BackstageUserPrincipal, + BackstageNonePrincipal, + AuthService, +} from '@backstage/backend-plugin-api'; +import { AuthenticationError } from '@backstage/errors'; + +/** @internal */ +export class MockAuthService implements AuthService { + constructor(private readonly pluginId: string) {} + + async authenticate(token: string): Promise { + if (token === 'mock-user-token') { + return { + $$type: '@backstage/BackstageCredentials', + principal: { type: 'user', userEntityRef: 'user:default/mock' }, + }; + } else if (token === 'mock-service-token') { + return { + $$type: '@backstage/BackstageCredentials', + principal: { type: 'service', subject: 'external:test-service' }, + }; + } + + throw new AuthenticationError('Invalid token'); + } + + async getOwnCredentials(): Promise< + BackstageCredentials + > { + return { + $$type: '@backstage/BackstageCredentials', + principal: { type: 'service', subject: `plugin:${this.pluginId}` }, + }; + } + + isPrincipal( + credentials: BackstageCredentials, + type: TType, + ): credentials is BackstageCredentials { + const principal = credentials.principal as + | BackstageUserPrincipal + | BackstageServicePrincipal + | BackstageNonePrincipal; + if (principal.type !== type) { + return false; + } + + return true; + } + + async issueServiceToken(options: { + forward: BackstageCredentials; + }): Promise<{ token: string }> { + const principal = options.forward.principal as + | BackstageUserPrincipal + | BackstageServicePrincipal + | BackstageNonePrincipal; + + switch (principal.type) { + case 'user': + return { token: 'mock-user-token' }; + case 'service': + return { token: 'mock-service-token' }; + default: + throw new AuthenticationError( + `Refused to issue service token for credential type '${principal.type}'`, + ); + } + } +} diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 78f59b53c4..58a6f7c421 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -23,6 +23,7 @@ import { ServiceFactory, ServiceRef, TokenManagerService, + AuthService, } from '@backstage/backend-plugin-api'; import { cacheServiceFactory, @@ -35,11 +36,13 @@ import { rootLifecycleServiceFactory, schedulerServiceFactory, urlReaderServiceFactory, + httpAuthServiceFactory, } from '@backstage/backend-app-api'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { MockIdentityService } from './MockIdentityService'; import { MockRootLoggerService } from './MockRootLoggerService'; +import { MockAuthService } from './MockAuthService'; /** @internal */ function simpleFactory< @@ -160,6 +163,23 @@ export namespace mockServices { })); } + export function auth(options?: { pluginId?: string }): AuthService { + return new MockAuthService(options?.pluginId ?? 'test'); + } + export namespace auth { + export const factory = createServiceFactory({ + service: coreServices.auth, + deps: { plugin: coreServices.pluginMetadata }, + factory: ({ plugin }) => new MockAuthService(plugin.getId()), + }); + export const mock = simpleMock(coreServices.auth, () => ({ + authenticate: jest.fn(), + getOwnCredentials: jest.fn(), + isPrincipal: jest.fn() as any, + issueServiceToken: jest.fn(), + })); + } + // TODO(Rugvip): Not all core services have implementations available here yet. // some may need a bit more refactoring for it to be simpler to // re-implement functioning mock versions here. @@ -185,6 +205,14 @@ export namespace mockServices { addAuthPolicy: jest.fn(), })); } + export namespace httpAuth { + export const factory = httpAuthServiceFactory; + export const mock = simpleMock(coreServices.httpAuth, () => ({ + credentials: jest.fn(), + issueUserCookie: jest.fn(), + requestHeaders: jest.fn(), + })); + } export namespace rootHttpRouter { export const factory = rootHttpRouterServiceFactory; export const mock = simpleMock(coreServices.rootHttpRouter, () => ({ diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 14e8920390..72a2ed8edb 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -66,9 +66,11 @@ export interface TestBackend extends Backend { } export const defaultServiceFactories = [ + mockServices.auth.factory(), mockServices.cache.factory(), mockServices.rootConfig.factory(), mockServices.database.factory(), + mockServices.httpAuth.factory(), mockServices.httpRouter.factory(), mockServices.identity.factory(), mockServices.lifecycle.factory(),