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(),