From f3e11ff0ccc117d43e7da8f65f692e0a20fb92ca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 16 Feb 2024 14:36:25 +0100 Subject: [PATCH] backend-test-utils: mockCredentials refactor for ref validation, header, inline payload + tests Signed-off-by: Patrik Oldsberg --- packages/backend-test-utils/api-report.md | 4 +- .../src/next/services/MockAuthService.test.ts | 4 +- .../src/next/services/MockAuthService.ts | 4 +- .../src/next/services/mockCredentials.test.ts | 142 ++++++++++++++++++ .../src/next/services/mockCredentials.ts | 19 ++- 5 files changed, 160 insertions(+), 13 deletions(-) create mode 100644 packages/backend-test-utils/src/next/services/mockCredentials.test.ts diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 5c22cfb149..b553d20e48 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -64,8 +64,8 @@ export namespace mockCredentials { userEntityRef?: string, ): BackstageCredentials; export namespace user { - export function header(payload?: TokenPayload): string; - export function token(payload?: TokenPayload): string; + export function header(userEntityRef?: string): string; + export function token(userEntityRef?: string): string; export type TokenPayload = { userEntityRef?: string; }; diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.test.ts b/packages/backend-test-utils/src/next/services/MockAuthService.test.ts index e94f944d8b..b354301c53 100644 --- a/packages/backend-test-utils/src/next/services/MockAuthService.test.ts +++ b/packages/backend-test-utils/src/next/services/MockAuthService.test.ts @@ -55,9 +55,7 @@ describe('MockAuthService', () => { ).resolves.toEqual(mockCredentials.user(DEFAULT_MOCK_USER_ENTITY_REF)); await expect( - auth.authenticate( - mockCredentials.user.token({ userEntityRef: 'user:default/other' }), - ), + auth.authenticate(mockCredentials.user.token('user:default/other')), ).resolves.toEqual(mockCredentials.user('user:default/other')); }); diff --git a/packages/backend-test-utils/src/next/services/MockAuthService.ts b/packages/backend-test-utils/src/next/services/MockAuthService.ts index 2ac7fff3a1..38a068adeb 100644 --- a/packages/backend-test-utils/src/next/services/MockAuthService.ts +++ b/packages/backend-test-utils/src/next/services/MockAuthService.ts @@ -110,9 +110,7 @@ export class MockAuthService implements AuthService { return { token: mockCredentials.user.token() }; } return { - token: mockCredentials.user.token({ - userEntityRef: principal.userEntityRef, - }), + token: mockCredentials.user.token(principal.userEntityRef), }; case 'service': return { diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts new file mode 100644 index 0000000000..aaa2626309 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts @@ -0,0 +1,142 @@ +/* + * 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 { mockCredentials } from './mockCredentials'; + +describe('mockCredentials', () => { + it('creates a mocked credentials object for a none principal', () => { + expect(mockCredentials.none()).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { type: 'none' }, + }); + }); + + it('creates a mocked credentials object for a user principal', () => { + expect(mockCredentials.user()).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { type: 'user', userEntityRef: 'user:default/mock' }, + }); + + expect(mockCredentials.user('user:default/other')).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { type: 'user', userEntityRef: 'user:default/other' }, + }); + }); + + it('creates a mocked credentials object for a service principal', () => { + expect(mockCredentials.service()).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { type: 'service', subject: 'external:test-service' }, + }); + + expect(mockCredentials.service('plugin:other')).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { type: 'service', subject: 'plugin:other' }, + }); + }); + + it('creates user tokens and headers', () => { + expect(mockCredentials.user.token()).toBe('mock-user-token'); + expect(mockCredentials.user.token('user:default/other')).toBe( + 'mock-user-token:{"userEntityRef":"user:default/other"}', + ); + expect(mockCredentials.user.header()).toBe('Bearer mock-user-token'); + expect(mockCredentials.user.header('user:default/other')).toBe( + 'Bearer mock-user-token:{"userEntityRef":"user:default/other"}', + ); + }); + + it('creates service tokens and headers', () => { + expect(mockCredentials.service.token()).toBe('mock-service-token'); + expect(mockCredentials.service.token({ subject: 'external:other' })).toBe( + 'mock-service-token:{"subject":"external:other"}', + ); + expect( + mockCredentials.service.token({ + targetPluginId: 'other', + }), + ).toBe('mock-service-token:{"targetPluginId":"other"}'); + expect( + mockCredentials.service.token({ + subject: 'external:other', + targetPluginId: 'other', + }), + ).toBe( + 'mock-service-token:{"subject":"external:other","targetPluginId":"other"}', + ); + // Object keys are reordered, ordering in the token should be stable + expect( + mockCredentials.service.token({ + targetPluginId: 'other', + subject: 'external:other', + }), + ).toBe( + 'mock-service-token:{"subject":"external:other","targetPluginId":"other"}', + ); + + expect(mockCredentials.service.header()).toBe('Bearer mock-service-token'); + expect(mockCredentials.service.header({ subject: 'external:other' })).toBe( + 'Bearer mock-service-token:{"subject":"external:other"}', + ); + expect( + mockCredentials.service.header({ + targetPluginId: 'other', + }), + ).toBe('Bearer mock-service-token:{"targetPluginId":"other"}'); + expect( + mockCredentials.service.header({ + subject: 'external:other', + targetPluginId: 'other', + }), + ).toBe( + 'Bearer mock-service-token:{"subject":"external:other","targetPluginId":"other"}', + ); + // Object keys are reordered, ordering in the token should be stable + expect( + mockCredentials.service.header({ + targetPluginId: 'other', + subject: 'external:other', + }), + ).toBe( + 'Bearer mock-service-token:{"subject":"external:other","targetPluginId":"other"}', + ); + }); + + it('should throw on invalid user entity refs', () => { + expect(() => mockCredentials.user('wrong')).toThrow( + "Invalid user entity reference 'wrong', expected :/", + ); + expect(() => mockCredentials.user('wr:ong')).toThrow( + "Invalid user entity reference 'wr:ong', expected :/", + ); + expect(() => mockCredentials.user('wr/ong')).toThrow( + "Invalid user entity reference 'wr/ong', expected :/", + ); + expect(() => mockCredentials.user('wr/o:ng')).toThrow( + "Invalid user entity reference 'wr/o:ng', expected :/", + ); + expect(() => mockCredentials.user('wr:/ong')).toThrow( + "Invalid user entity reference 'wr:/ong', expected :/", + ); + + expect(() => mockCredentials.user.token('wrong')).toThrow( + "Invalid user entity reference 'wrong', expected :/", + ); + expect(() => mockCredentials.user.header('wrong')).toThrow( + "Invalid user entity reference 'wrong', expected :/", + ); + }); +}); diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.ts b/packages/backend-test-utils/src/next/services/mockCredentials.ts index 7eda645150..e86916db3b 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.ts @@ -29,6 +29,14 @@ export const MOCK_USER_TOKEN_PREFIX = 'mock-user-token:'; export const MOCK_SERVICE_TOKEN = 'mock-service-token'; export const MOCK_SERVICE_TOKEN_PREFIX = 'mock-service-token:'; +function validateUserEntityRef(ref: string) { + if (!ref.match(/^.+:.+\/.+$/)) { + throw new TypeError( + `Invalid user entity reference '${ref}', expected :/`, + ); + } +} + /** * @public */ @@ -51,6 +59,7 @@ export namespace mockCredentials { export function user( userEntityRef: string = DEFAULT_MOCK_USER_ENTITY_REF, ): BackstageCredentials { + validateUserEntityRef(userEntityRef); return { $$type: '@backstage/BackstageCredentials', principal: { type: 'user', userEntityRef }, @@ -71,9 +80,9 @@ export namespace mockCredentials { * into the token and forwarded to the credentials object when authenticated * by the mock auth service. */ - export function token(payload?: TokenPayload): string { - if (payload) { - const { userEntityRef } = payload; // for fixed ordering + export function token(userEntityRef?: string): string { + if (userEntityRef) { + validateUserEntityRef(userEntityRef); return `${MOCK_USER_TOKEN_PREFIX}${JSON.stringify({ userEntityRef })}`; } return MOCK_USER_TOKEN; @@ -84,8 +93,8 @@ export namespace mockCredentials { * provided it will be encoded into the token and forwarded to the * credentials object when authenticated by the mock auth service. */ - export function header(payload?: TokenPayload): string { - return `Bearer ${token(payload)}`; + export function header(userEntityRef?: string): string { + return `Bearer ${token(userEntityRef)}`; } }