chore: added the user entity for decoration and added some more tests for the router which were untested before

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-05-03 17:27:01 +02:00
parent d48f69fa60
commit d06a045a68
5 changed files with 144 additions and 12 deletions
@@ -37,6 +37,7 @@ import {
TaskSpecV1beta3,
TaskStep,
} from '@backstage/plugin-scaffolder-common';
import { UserEntity } from '@backstage/catalog-model';
type NunjucksWorkflowRunnerOptions = {
workingDirectory: string;
@@ -52,6 +53,10 @@ type TemplateContext = {
[stepName: string]: { output: { [outputName: string]: JsonValue } };
};
secrets?: Record<string, string>;
user?: {
entity?: UserEntity;
ref?: string;
};
};
const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => {
@@ -203,6 +208,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const context: TemplateContext = {
parameters: task.spec.parameters,
steps: {},
user: task.spec.user,
};
for (const step of task.spec.steps) {
@@ -59,6 +59,10 @@ export class TaskManager implements TaskContext {
return this.task.secrets;
}
get createdBy() {
return this.task.createdBy;
}
async getWorkspaceName() {
return this.task.taskId;
}
@@ -109,6 +109,7 @@ export type TaskBrokerDispatchOptions = {
export interface TaskContext {
spec: TaskSpec;
secrets?: TaskSecrets;
createdBy?: string;
done: boolean;
emitLog(message: string, logMetadata?: JsonObject): Promise<void>;
complete(result: TaskCompletionState, metadata?: JsonObject): Promise<void>;
@@ -48,12 +48,11 @@ import request from 'supertest';
*/
import { createRouter, DatabaseTaskStore, TaskBroker } from '../index';
import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker';
import { stringifyEntityRef } from '@backstage/catalog-model';
const createCatalogClient = (template: any) =>
({
getEntityByRef: async () => template,
} as unknown as CatalogApi);
import {
parseEntityRef,
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
function createDatabase(): PluginDatabaseManager {
return DatabaseManager.fromConfig(
@@ -76,7 +75,9 @@ const mockUrlReader = UrlReaders.default({
describe('createRouter', () => {
let app: express.Express;
let taskBroker: TaskBroker;
const template: TemplateEntityV1beta3 = {
const catalogClient = { getEntityByRef: jest.fn() } as unknown as CatalogApi;
const mockTemplate: TemplateEntityV1beta3 = {
apiVersion: 'scaffolder.backstage.io/v1beta3',
kind: 'Template',
metadata: {
@@ -105,6 +106,22 @@ describe('createRouter', () => {
},
};
const mockUser: UserEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'User',
metadata: {
name: 'guest',
annotations: {
'google.com/email': 'bobby@tables.com',
},
},
spec: {
profile: {
displayName: 'Robert Tables of the North',
},
},
};
beforeEach(async () => {
const logger = getVoidLogger();
const databaseTaskStore = await DatabaseTaskStore.create({
@@ -120,11 +137,26 @@ describe('createRouter', () => {
logger: getVoidLogger(),
config: new ConfigReader({}),
database: createDatabase(),
catalogClient: createCatalogClient(template),
catalogClient,
reader: mockUrlReader,
taskBroker,
});
app = express().use(router);
jest
.spyOn(catalogClient, 'getEntityByRef')
.mockImplementation(async ref => {
const { kind } = parseEntityRef(ref);
if (kind === 'template') {
return mockTemplate;
}
if (kind === 'user') {
return mockUser;
}
throw new Error(`no mock found for kind: ${kind}`);
});
});
afterEach(() => {
@@ -158,9 +190,8 @@ describe('createRouter', () => {
});
it('return the template id', async () => {
(
taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch']
).mockResolvedValue({
const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch'];
broker.mockResolvedValue({
taskId: 'a-random-id',
});
@@ -179,6 +210,84 @@ describe('createRouter', () => {
expect(response.body.id).toBe('a-random-id');
expect(response.status).toEqual(201);
});
it('should call the broker with a correct spec', async () => {
const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch'];
const mockToken =
'blob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvZ3Vlc3QiLCJuYW1lIjoiSm9obiBEb2UifQ.blob';
await request(app)
.post('/v2/tasks')
.set('Authorization', `Bearer ${mockToken}`)
.send({
templateRef: stringifyEntityRef({
kind: 'template',
name: 'create-react-app-template',
}),
values: {
required: 'required-value',
},
});
expect(broker).toHaveBeenCalledWith(
expect.objectContaining({
createdBy: 'user:default/guest',
secrets: {
backstageToken: mockToken,
},
spec: {
apiVersion: mockTemplate.apiVersion,
steps: mockTemplate.spec.steps.map((step, index) => ({
...step,
id: step.id ?? `step-${index + 1}`,
name: step.name ?? step.action,
})),
output: mockTemplate.spec.output ?? {},
parameters: {
required: 'required-value',
},
user: {
entity: mockUser,
ref: 'user:default/guest',
},
templateInfo: {
entityRef: stringifyEntityRef({
kind: 'Template',
namespace: 'Default',
name: mockTemplate.metadata?.name,
}),
baseUrl: 'https://dev.azure.com',
},
},
}),
);
});
it('should not decorate a user when no backstage auth is passed', async () => {
const broker = taskBroker.dispatch as jest.Mocked<TaskBroker>['dispatch'];
await request(app)
.post('/v2/tasks')
.send({
templateRef: stringifyEntityRef({
kind: 'template',
name: 'create-react-app-template',
}),
values: {
required: 'required-value',
},
});
expect(broker).toHaveBeenCalledWith(
expect.objectContaining({
createdBy: undefined,
spec: expect.objectContaining({
user: { entity: undefined, ref: undefined },
}),
}),
);
});
});
describe('GET /v2/tasks/:taskId', () => {
@@ -16,7 +16,11 @@
import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import {
parseEntityRef,
stringifyEntityRef,
UserEntity,
} from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { InputError, NotFoundError } from '@backstage/errors';
@@ -173,6 +177,10 @@ export async function createRouter(
req.headers.authorization,
);
const userEntity = userEntityRef
? await catalogClient.getEntityByRef(userEntityRef, { token })
: undefined;
const values = req.body.values;
const template = await findTemplate({
@@ -208,6 +216,10 @@ export async function createRouter(
})),
output: template.spec.output ?? {},
parameters: values,
user: {
entity: userEntity as UserEntity,
ref: userEntityRef,
},
templateInfo: {
entityRef: stringifyEntityRef({
kind,