catalog-backend: add location for validate-entity endpoint

Signed-off-by: Bret Hubbard <hubbard.bret@gmail.com>
This commit is contained in:
Bret Hubbard
2022-03-21 17:43:52 -06:00
parent bf82edf4c9
commit 0d5f9fe6e9
3 changed files with 66 additions and 15 deletions
@@ -415,7 +415,7 @@ describe('createRouter readonly disabled', () => {
const response = await request(app)
.post('/validate-entity')
.send(entity);
.send({ entity, location: 'validate:entity' });
expect(response.status).toEqual(200);
expect(orchestrator.process).toHaveBeenCalledTimes(1);
@@ -436,7 +436,7 @@ describe('createRouter readonly disabled', () => {
});
describe('invalid entity', () => {
it('returns 422', async () => {
it('returns 400', async () => {
const entity: Entity = {
apiVersion: 'a',
kind: 'b',
@@ -450,10 +450,10 @@ describe('createRouter readonly disabled', () => {
const response = await request(app)
.post('/validate-entity')
.send(entity);
.send({ entity, location: 'validate:entity' });
expect(response.status).toEqual(422);
expect(response.body).toEqual(['Invalid entity name']);
expect(response.status).toEqual(400);
expect(response.body.error.message).toContain('Invalid entity name');
expect(orchestrator.process).toHaveBeenCalledTimes(1);
expect(orchestrator.process).toHaveBeenCalledWith({
entity: {
@@ -470,6 +470,48 @@ describe('createRouter readonly disabled', () => {
});
});
});
describe('no location', () => {
it('returns 400', async () => {
const entity: Entity = {
apiVersion: 'a',
kind: 'b',
metadata: { name: 'n' },
};
const response = await request(app)
.post('/validate-entity')
.send({ entity, location: null });
expect(response.status).toEqual(400);
expect(response.body.error.message).toContain('Malformed request:');
expect(orchestrator.process).toHaveBeenCalledTimes(0);
});
});
describe('no entity', () => {
it('returns 400', async () => {
orchestrator.process.mockResolvedValueOnce({
ok: false,
errors: [
new Error(
'Entity envelope failed validation before processing; caused by TypeError: <root> must be object - type: object',
),
],
});
const response = await request(app)
.post('/validate-entity')
.send({ entity: null, location: 'validate:entity' });
expect(response.status).toEqual(400);
expect(response.body.error.message).toContain(
'Entity envelope failed validation',
);
// We expect this to be called because of an open issue with zod where unknown values are optional https://github.com/colinhacks/zod/issues/493
expect(orchestrator.process).toHaveBeenCalledTimes(1);
});
});
});
});
@@ -17,7 +17,7 @@
import { errorHandler } from '@backstage/backend-common';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { InputError, NotFoundError } from '@backstage/errors';
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
@@ -34,7 +34,6 @@ import {
disallowReadonlyMode,
locationInput,
validateRequestBody,
requireRequestBody,
setRequiredEntityLocationAnnotations,
} from './util';
import { z } from 'zod';
@@ -234,15 +233,22 @@ export async function createRouter(
if (orchestrator) {
router.post('/validate-entity', async (req, res) => {
const input = (await requireRequestBody(req)) as Entity;
const entity = setRequiredEntityLocationAnnotations(input);
const bodySchema = z.object({
entity: z.unknown(),
location: z.string(),
});
const body = await validateRequestBody(req, bodySchema);
const entity = setRequiredEntityLocationAnnotations(
body.entity as Entity,
body.location,
);
const processingResult = await orchestrator.process({
entity,
});
if (!processingResult.ok)
return res
.status(422)
.json(processingResult.errors.map(e => e.message));
throw new InputError(processingResult.errors[0].message);
return res.status(200).send();
});
}
+6 -3
View File
@@ -53,17 +53,20 @@ export const locationInput = z
})
.strict(); // no unknown keys;
export function setRequiredEntityLocationAnnotations(entity: Entity): Entity {
export function setRequiredEntityLocationAnnotations(
entity: Entity,
location: string,
): Entity {
const clonedEntity = lodash.cloneDeep(entity);
lodash.set(
clonedEntity,
`metadata.annotations.['${ANNOTATION_SOURCE_LOCATION}']`,
'validate:entity',
location,
);
lodash.set(
clonedEntity,
`metadata.annotations.['${ANNOTATION_ORIGIN_LOCATION}']`,
'validate:entity',
location,
);
return clonedEntity;