Add a standard 'toString' on credentials objects

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2025-06-03 17:51:37 +02:00
parent 9ad11f6555
commit ead925a8a2
5 changed files with 186 additions and 49 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-defaults': patch
'@backstage/backend-test-utils': minor
---
Add a standard `toString` on credentials objects
@@ -42,6 +42,23 @@ describe('credentials', () => {
},
});
expect(
createCredentialsWithUserPrincipal(
'user:default/mock',
'my-token',
undefined,
'my-actor',
),
).toEqual({
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'user',
userEntityRef: 'user:default/mock',
actor: { type: 'service', subject: 'my-actor' },
},
});
expect(createCredentialsWithNonePrincipal()).toEqual({
$$type: '@backstage/BackstageCredentials',
version: 'v1',
@@ -64,4 +81,34 @@ describe('credentials', () => {
),
).not.toMatch(/my-token/);
});
it('should have a serializable form', () => {
expect(
String(createCredentialsWithServicePrincipal('my-service')),
).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","type":"service","subject":"my-service"}"`,
);
expect(
String(
createCredentialsWithUserPrincipal('user:default/mock', 'my-token'),
),
).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`,
);
expect(
String(
createCredentialsWithUserPrincipal(
'user:default/mock',
'my-token',
undefined,
'my-actor',
),
),
).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`,
);
expect(String(createCredentialsWithNonePrincipal())).toMatchInlineSnapshot(
`"{"$$type":"@backstage/BackstageCredentials","type":"none"}"`,
);
});
});
@@ -28,23 +28,31 @@ export function createCredentialsWithServicePrincipal(
token?: string,
accessRestrictions?: BackstagePrincipalAccessRestrictions,
): InternalBackstageCredentials<BackstageServicePrincipal> {
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'service',
subject: sub,
accessRestrictions,
},
} as const;
Object.defineProperty(result, 'token', {
enumerable: false,
configurable: true,
value: token,
});
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/BackstageCredentials',
type: 'service',
subject: sub,
accessRestrictions,
},
},
'token',
{
enumerable: false,
configurable: true,
value: token,
},
);
}),
});
return result;
}
export function createCredentialsWithUserPrincipal(
@@ -53,36 +61,57 @@ export function createCredentialsWithUserPrincipal(
expiresAt?: Date,
actor?: string,
): InternalBackstageCredentials<BackstageUserPrincipal> {
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
version: 'v1',
expiresAt,
principal: {
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
expiresAt,
principal: {
type: 'user',
userEntityRef: sub,
...(actor && {
actor: { type: 'service', subject: actor } as const,
}),
},
} as const;
Object.defineProperty(result, 'token', {
enumerable: false,
configurable: true,
value: token,
});
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/BackstageCredentials',
type: 'user',
userEntityRef: sub,
...(actor && {
actor: { type: 'service', subject: actor },
}),
},
},
'token',
{
enumerable: false,
configurable: true,
value: token,
},
);
}),
});
return result;
}
export function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {
return {
const result = {
$$type: '@backstage/BackstageCredentials',
version: 'v1',
principal: {
type: 'none',
},
};
} as const;
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/BackstageCredentials',
type: 'none',
}),
});
return result;
}
export function toInternalBackstageCredentials(
@@ -170,4 +170,27 @@ describe('mockCredentials', () => {
"Invalid user entity reference 'wrong', expected <kind>:<namespace>/<name>",
);
});
it('should have a serializable form', () => {
expect(String(mockCredentials.service('my-service'))).toMatchInlineSnapshot(
`"{"$$type":"@backstage/MockBackstageCredentials","type":"service","subject":"my-service"}"`,
);
expect(
String(mockCredentials.user('user:default/mock')),
).toMatchInlineSnapshot(
`"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock"}"`,
);
expect(
String(
mockCredentials.user('user:default/mock', {
actor: { subject: 'my-actor' },
}),
),
).toMatchInlineSnapshot(
`"{"$$type":"@backstage/MockBackstageCredentials","type":"user","userEntityRef":"user:default/mock","actor":{"type":"service","subject":"my-actor"}}"`,
);
expect(String(mockCredentials.none())).toMatchInlineSnapshot(
`"{"$$type":"@backstage/MockBackstageCredentials","type":"none"}"`,
);
});
});
@@ -76,10 +76,20 @@ export namespace mockCredentials {
* Creates a mocked credentials object for a unauthenticated principal.
*/
export function none(): BackstageCredentials<BackstageNonePrincipal> {
return {
const result = {
$$type: '@backstage/BackstageCredentials',
principal: { type: 'none' },
};
} as const;
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/MockBackstageCredentials',
type: 'none',
}),
});
return result;
}
/**
@@ -111,24 +121,35 @@ export namespace mockCredentials {
options?: { actor?: { subject: string } },
): BackstageCredentials<BackstageUserPrincipal> {
validateUserEntityRef(userEntityRef);
return Object.defineProperty(
{
$$type: '@backstage/BackstageCredentials',
principal: {
const result = {
$$type: '@backstage/BackstageCredentials',
principal: {
type: 'user',
userEntityRef,
...(options?.actor && {
actor: { type: 'service', subject: options.actor.subject } as const,
}),
},
} as const;
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/MockBackstageCredentials',
type: 'user',
userEntityRef,
...(options?.actor && {
actor: { type: 'service', subject: options.actor.subject },
}),
},
},
'token',
{
enumerable: false,
configurable: true,
value: user.token(),
},
);
}),
});
Object.defineProperty(result, 'token', {
enumerable: false,
configurable: true,
value: user.token(),
});
return result;
}
/**
@@ -231,14 +252,25 @@ export namespace mockCredentials {
subject: string = DEFAULT_MOCK_SERVICE_SUBJECT,
accessRestrictions?: BackstagePrincipalAccessRestrictions,
): BackstageCredentials<BackstageServicePrincipal> {
return {
const result = {
$$type: '@backstage/BackstageCredentials',
principal: {
type: 'service',
subject,
...(accessRestrictions ? { accessRestrictions } : {}),
},
};
} as const;
Object.defineProperty(result, 'toString', {
enumerable: false,
configurable: true,
value: () =>
JSON.stringify({
$$type: '@backstage/MockBackstageCredentials',
type: 'service',
subject,
}),
});
return result;
}
/**