backend-test-utils: mockCredentials refactor for ref validation, header, inline payload + tests

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-16 14:36:25 +01:00
parent 67e53eb38f
commit f3e11ff0cc
5 changed files with 160 additions and 13 deletions
+2 -2
View File
@@ -64,8 +64,8 @@ export namespace mockCredentials {
userEntityRef?: string,
): BackstageCredentials<BackstageUserPrincipal>;
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;
};
@@ -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'));
});
@@ -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 {
@@ -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 <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user('wr:ong')).toThrow(
"Invalid user entity reference 'wr:ong', expected <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user('wr/ong')).toThrow(
"Invalid user entity reference 'wr/ong', expected <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user('wr/o:ng')).toThrow(
"Invalid user entity reference 'wr/o:ng', expected <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user('wr:/ong')).toThrow(
"Invalid user entity reference 'wr:/ong', expected <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user.token('wrong')).toThrow(
"Invalid user entity reference 'wrong', expected <kind>:<namespace>/<name>",
);
expect(() => mockCredentials.user.header('wrong')).toThrow(
"Invalid user entity reference 'wrong', expected <kind>:<namespace>/<name>",
);
});
});
@@ -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 <kind>:<namespace>/<name>`,
);
}
}
/**
* @public
*/
@@ -51,6 +59,7 @@ export namespace mockCredentials {
export function user(
userEntityRef: string = DEFAULT_MOCK_USER_ENTITY_REF,
): BackstageCredentials<BackstageUserPrincipal> {
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)}`;
}
}