backend-test-utils: add mocks for AuthService and HttpAuthService

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: Carl-Erik Bergström <cbergstrom@spotify.com>
Co-authored-by: blam <ben@blam.sh>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-14 00:38:58 +01:00
parent 54fa4997ee
commit abaaf4970a
4 changed files with 142 additions and 0 deletions
+22
View File
@@ -6,12 +6,14 @@
/// <reference types="jest" />
/// <reference types="node" />
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<AuthService, 'plugin'>;
const // (undocumented)
mock: (
partialImpl?: Partial<AuthService> | undefined,
) => ServiceMock<AuthService>;
}
// (undocumented)
export namespace cache {
const // (undocumented)
@@ -105,6 +118,15 @@ export namespace mockServices {
) => ServiceMock<DatabaseService>;
}
// (undocumented)
export namespace httpAuth {
const // (undocumented)
factory: () => ServiceFactory<HttpAuthService, 'plugin'>;
const // (undocumented)
mock: (
partialImpl?: Partial<HttpAuthService> | undefined,
) => ServiceMock<HttpAuthService>;
}
// (undocumented)
export namespace httpRouter {
const // (undocumented)
factory: (
@@ -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<BackstageCredentials> {
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<BackstageServicePrincipal>
> {
return {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'service', subject: `plugin:${this.pluginId}` },
};
}
isPrincipal<TType extends keyof BackstagePrincipalTypes>(
credentials: BackstageCredentials,
type: TType,
): credentials is BackstageCredentials<BackstagePrincipalTypes[TType]> {
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}'`,
);
}
}
}
@@ -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, () => ({
@@ -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(),