Address comments
This commit is contained in:
@@ -29,10 +29,10 @@ import { EntityPolicy } from './types';
|
||||
class AllEntityPolicies implements EntityPolicy {
|
||||
constructor(private readonly policies: EntityPolicy[]) {}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
let result = entity;
|
||||
for (const policy of this.policies) {
|
||||
result = await policy.apply(entity);
|
||||
result = await policy.enforce(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -43,10 +43,10 @@ class AllEntityPolicies implements EntityPolicy {
|
||||
class AnyEntityPolicy implements EntityPolicy {
|
||||
constructor(private readonly policies: EntityPolicy[]) {}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
for (const policy of this.policies) {
|
||||
try {
|
||||
return await policy.apply(entity);
|
||||
return await policy.enforce(entity);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export class EntityPolicies implements EntityPolicy {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
apply(entity: Entity): Promise<Entity> {
|
||||
return this.policy.apply(entity);
|
||||
enforce(entity: Entity): Promise<Entity> {
|
||||
return this.policy.enforce(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,64 +42,64 @@ describe('FieldFormatEntityPolicy', () => {
|
||||
});
|
||||
|
||||
it('works for the happy path', async () => {
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad apiVersion', async () => {
|
||||
data.apiVersion = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/apiVersion/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/apiVersion/);
|
||||
data.apiVersion = 'a#b';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/apiVersion/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects bad kind', async () => {
|
||||
data.kind = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/kind/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/kind/);
|
||||
data.kind = 'a#b';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/kind/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('handles missing metadata gracefully', async () => {
|
||||
delete data.medatata;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('handles missing spec gracefully', async () => {
|
||||
delete data.spec;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad name', async () => {
|
||||
data.metadata.name = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/name.*7/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/name.*7/);
|
||||
data.metadata.name = 'a'.repeat(1000);
|
||||
await expect(policy.apply(data)).rejects.toThrow(/name.*aaaa/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/name.*aaaa/);
|
||||
});
|
||||
|
||||
it('rejects bad namespace', async () => {
|
||||
data.metadata.namespace = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/namespace.*7/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/namespace.*7/);
|
||||
data.metadata.namespace = 'a'.repeat(1000);
|
||||
await expect(policy.apply(data)).rejects.toThrow(/namespace.*aaaa/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/namespace.*aaaa/);
|
||||
});
|
||||
|
||||
it('rejects bad label key', async () => {
|
||||
data.metadata.labels['a#b'] = 'value';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/label.*a#b/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/label.*a#b/i);
|
||||
});
|
||||
|
||||
it('rejects bad label value', async () => {
|
||||
data.metadata.labels.a = 'a#b';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/label.*a#b/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/label.*a#b/i);
|
||||
});
|
||||
|
||||
it('rejects bad annotation key', async () => {
|
||||
data.metadata.annotations['a#b'] = 'value';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/annotation.*a#b/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/annotation.*a#b/i);
|
||||
});
|
||||
|
||||
it('rejects bad annotation value', async () => {
|
||||
data.metadata.annotations.a = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/annotation.*7/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/annotation.*7/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ export class FieldFormatEntityPolicy implements EntityPolicy {
|
||||
this.validators = validators;
|
||||
}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
function require(
|
||||
field: string,
|
||||
value: any,
|
||||
|
||||
@@ -42,11 +42,11 @@ describe('NoForeignRootFieldsEntityPolicy', () => {
|
||||
});
|
||||
|
||||
it('works for the happy path', async () => {
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects unknown root fields', async () => {
|
||||
data.spec2 = {};
|
||||
await expect(policy.apply(data)).rejects.toThrow(/spec2/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/spec2/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export class NoForeignRootFieldsEntityPolicy implements EntityPolicy {
|
||||
this.knownFields = knownFields;
|
||||
}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
for (const field of Object.keys(entity)) {
|
||||
if (!this.knownFields.includes(field)) {
|
||||
throw new Error(`Unknown field ${field}`);
|
||||
|
||||
@@ -42,21 +42,23 @@ describe('ReservedFieldsEntityPolicy', () => {
|
||||
});
|
||||
|
||||
it('works for the happy path', async () => {
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in the spec root', async () => {
|
||||
data.spec.apiVersion = 'a/b';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/spec.*apiVersion/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/spec.*apiVersion/i);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in labels', async () => {
|
||||
data.metadata.labels.apiVersion = 'a';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/label.*apiVersion/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/label.*apiVersion/i);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in annotations', async () => {
|
||||
data.metadata.annotations.apiVersion = 'a';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/annotation.*apiVersion/i);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(
|
||||
/annotation.*apiVersion/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ReservedFieldsEntityPolicy implements EntityPolicy {
|
||||
];
|
||||
}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
for (const field of this.reservedFields) {
|
||||
if (entity.spec?.hasOwnProperty(field)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
});
|
||||
|
||||
it('works for the happy path', async () => {
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -51,113 +51,113 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
//
|
||||
|
||||
it('rejects wrong root type', async () => {
|
||||
await expect(policy.apply((7 as unknown) as Entity)).rejects.toThrow(
|
||||
await expect(policy.enforce((7 as unknown) as Entity)).rejects.toThrow(
|
||||
/object/,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects missing apiVersion', async () => {
|
||||
delete data.apiVersion;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/apiVersion/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects bad apiVersion type', async () => {
|
||||
data.apiVersion = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/apiVersion/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects missing kind', async () => {
|
||||
delete data.kind;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/kind/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects bad kind type', async () => {
|
||||
data.kind = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/kind/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/kind/);
|
||||
});
|
||||
|
||||
//
|
||||
// metadata
|
||||
//
|
||||
|
||||
it('accepts missing metadata', async () => {
|
||||
delete data.medatata;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
it('rejects missing metadata', async () => {
|
||||
delete data.metadata;
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('rejects bad metadata type', async () => {
|
||||
data.metadata = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/metadata/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('accepts missing uid', async () => {
|
||||
delete data.metadata.uid;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad uid type', async () => {
|
||||
data.metadata.uid = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/uid/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/uid/);
|
||||
});
|
||||
|
||||
it('accepts missing etag', async () => {
|
||||
delete data.metadata.etag;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad etag type', async () => {
|
||||
data.metadata.etag = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/etag/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/etag/);
|
||||
});
|
||||
|
||||
it('accepts missing generation', async () => {
|
||||
delete data.metadata.generation;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad generation type', async () => {
|
||||
data.metadata.generation = 'a';
|
||||
await expect(policy.apply(data)).rejects.toThrow(/generation/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/generation/);
|
||||
});
|
||||
|
||||
it('accepts missing name', async () => {
|
||||
delete data.metadata.name;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad name type', async () => {
|
||||
data.metadata.name = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/name/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/name/);
|
||||
});
|
||||
|
||||
it('accepts missing namespace', async () => {
|
||||
delete data.metadata.namespace;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad namespace type', async () => {
|
||||
data.metadata.namespace = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/namespace/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/namespace/);
|
||||
});
|
||||
|
||||
it('accepts missing labels', async () => {
|
||||
delete data.metadata.labels;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad labels type', async () => {
|
||||
data.metadata.labels = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/labels/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/labels/);
|
||||
});
|
||||
|
||||
it('accepts missing annotations', async () => {
|
||||
delete data.metadata.annotations;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects bad annotations type', async () => {
|
||||
data.metadata.annotations = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/annotations/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/annotations/);
|
||||
});
|
||||
|
||||
//
|
||||
@@ -166,11 +166,11 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
|
||||
it('accepts missing spec', async () => {
|
||||
delete data.spec;
|
||||
await expect(policy.apply(data)).resolves.toBe(data);
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects non-object spec', async () => {
|
||||
data.spec = 7;
|
||||
await expect(policy.apply(data)).rejects.toThrow(/spec/);
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/spec/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ export class SchemaValidEntityPolicy implements EntityPolicy {
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
async apply(entity: Entity): Promise<Entity> {
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
try {
|
||||
return await this.schema.validate(entity, { strict: true });
|
||||
} catch (e) {
|
||||
|
||||
@@ -50,7 +50,7 @@ export class ComponentV1beta1Policy implements EntityPolicy {
|
||||
});
|
||||
}
|
||||
|
||||
async apply(envelope: Entity): Promise<Entity> {
|
||||
async enforce(envelope: Entity): Promise<Entity> {
|
||||
if (
|
||||
envelope.apiVersion !== 'backstage.io/v1beta1' ||
|
||||
envelope.kind !== 'Component'
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@@ -28,5 +28,5 @@ export type EntityPolicy = {
|
||||
* @returns The incoming entity, or a mutated version of the same
|
||||
* @throws An error if the entity should be rejected
|
||||
*/
|
||||
apply(entity: Entity): Promise<Entity>;
|
||||
enforce(entity: Entity): Promise<Entity>;
|
||||
};
|
||||
|
||||
@@ -32,14 +32,14 @@ describe('DatabaseManager', () => {
|
||||
readLocation: jest.fn(),
|
||||
};
|
||||
const policy: EntityPolicy = {
|
||||
apply: jest.fn(),
|
||||
enforce: jest.fn(),
|
||||
};
|
||||
|
||||
await expect(
|
||||
DatabaseManager.refreshLocations(db, reader, policy, getVoidLogger()),
|
||||
).resolves.toBeUndefined();
|
||||
expect(reader.readLocation).not.toHaveBeenCalled();
|
||||
expect(policy.apply).not.toHaveBeenCalled();
|
||||
expect(policy.enforce).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can update a single location', async () => {
|
||||
@@ -70,7 +70,7 @@ describe('DatabaseManager', () => {
|
||||
),
|
||||
};
|
||||
const policy: EntityPolicy = {
|
||||
apply: jest.fn(() => Promise.resolve(desc)),
|
||||
enforce: jest.fn(() => Promise.resolve(desc)),
|
||||
};
|
||||
|
||||
await expect(
|
||||
@@ -118,7 +118,7 @@ describe('DatabaseManager', () => {
|
||||
),
|
||||
};
|
||||
const policy: EntityPolicy = {
|
||||
apply: jest.fn(() => Promise.resolve(desc)),
|
||||
enforce: jest.fn(() => Promise.resolve(desc)),
|
||||
};
|
||||
|
||||
await expect(
|
||||
@@ -170,7 +170,9 @@ describe('DatabaseManager', () => {
|
||||
),
|
||||
};
|
||||
const policy: EntityPolicy = {
|
||||
apply: jest.fn(() => Promise.reject(new Error('parser error message'))),
|
||||
enforce: jest.fn(() =>
|
||||
Promise.reject(new Error('parser error message')),
|
||||
),
|
||||
};
|
||||
|
||||
await expect(
|
||||
@@ -217,7 +219,9 @@ describe('DatabaseManager', () => {
|
||||
),
|
||||
};
|
||||
const policy: EntityPolicy = {
|
||||
apply: jest.fn(() => Promise.reject(new Error('parser error message'))),
|
||||
enforce: jest.fn(() =>
|
||||
Promise.reject(new Error('parser error message')),
|
||||
),
|
||||
};
|
||||
|
||||
await expect(
|
||||
|
||||
@@ -86,7 +86,7 @@ export class DatabaseManager {
|
||||
}
|
||||
|
||||
try {
|
||||
const entity = await entityPolicy.apply(readerItem.data);
|
||||
const entity = await entityPolicy.enforce(readerItem.data);
|
||||
await DatabaseManager.refreshSingleEntity(
|
||||
database,
|
||||
location.id,
|
||||
|
||||
@@ -60,7 +60,7 @@ export class IngestionModels implements IngestionModel {
|
||||
result.push(item);
|
||||
} else {
|
||||
try {
|
||||
const output = await this.entityPolicy.apply(item.data);
|
||||
const output = await this.entityPolicy.enforce(item.data);
|
||||
result.push({ type: 'data', data: output });
|
||||
} catch (e) {
|
||||
result.push({ type: 'error', error: e });
|
||||
|
||||
Reference in New Issue
Block a user