add entity link field validation & tests
This commit is contained in:
@@ -115,8 +115,98 @@ describe('FieldFormatEntityPolicy', () => {
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/tags.*"Hello World"/i);
|
||||
});
|
||||
|
||||
it('rejects bad link icon value', async () => {
|
||||
data.metadata.links.push({ url: 'https://foo', icon: 'some icon' });
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.*"some icon"/i);
|
||||
it('accepts missing links', async () => {
|
||||
delete data.metadata.links;
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('accepts empty links array', async () => {
|
||||
data.metadata.links = [];
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('accepts multiple links', async () => {
|
||||
data.metadata.links = [{ url: 'http://foo' }, { url: 'https://bar' }];
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects missing link url value', async () => {
|
||||
data.metadata.links = [{}];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
});
|
||||
|
||||
it('rejects a single bad missing url value', async () => {
|
||||
data.metadata.links = [{ url: 'http://good' }, { url: '' }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.1/);
|
||||
});
|
||||
|
||||
it('rejects empty link url value', async () => {
|
||||
data.metadata.links = [{ url: '' }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
});
|
||||
|
||||
it('rejects bad link url value', async () => {
|
||||
data.metadata.links = [{ url: 'invalid' }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
});
|
||||
|
||||
it('accepts missing link title', async () => {
|
||||
data.metadata.links = [{ url: 'http://foo', icon: 'dashboard' }];
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects empty link title', async () => {
|
||||
data.metadata.links = [{ url: 'http://foo', title: '' }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
});
|
||||
|
||||
it.each([[123], [{}], [[]]])(
|
||||
'rejects bad link title %s',
|
||||
async (title: unknown) => {
|
||||
data.metadata.links = [{ url: 'http://foo', title }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
},
|
||||
);
|
||||
|
||||
it('rejects a single bad link title', async () => {
|
||||
data.metadata.links = [
|
||||
{ url: 'http://foo', title: 'good' },
|
||||
{ url: 'http://foo', title: '' },
|
||||
];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.1/);
|
||||
});
|
||||
|
||||
it('accepts missing link icon', async () => {
|
||||
data.metadata.links = [{ url: 'http://foo', title: 'foo' }];
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects empty link icon', async () => {
|
||||
data.metadata.links = [{ url: 'http://foo', icon: '' }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
});
|
||||
|
||||
it.each([['dashboard'], ['admin-dashboard'], ['foo_dashboard']])(
|
||||
'accepts valid link icon',
|
||||
async icon => {
|
||||
data.metadata.links = [{ url: 'http://foo', icon }];
|
||||
await expect(policy.enforce(data)).resolves.toBe(data);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([[123], [{}], [[]], ['abc xyz']])(
|
||||
'rejects bad link icon value %s',
|
||||
async (icon: unknown) => {
|
||||
data.metadata.links = [{ url: 'http://foo', icon }];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.0/);
|
||||
},
|
||||
);
|
||||
|
||||
it('rejects a single bad link icon value', async () => {
|
||||
data.metadata.links = [
|
||||
{ url: 'http://foo', icon: 'good' },
|
||||
{ url: 'http://foo', icon: 'not good' },
|
||||
];
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links.1/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,12 @@ export class FieldFormatEntityPolicy implements EntityPolicy {
|
||||
expectation =
|
||||
'a string that is a sequence of [a-zA-Z][a-z0-9A-Z], at most 63 characters in total';
|
||||
break;
|
||||
case 'isValidUrl':
|
||||
expectation = 'a string that is a valid url';
|
||||
break;
|
||||
case 'isValidString':
|
||||
expectation = 'a non empty string';
|
||||
break;
|
||||
default:
|
||||
expectation = undefined;
|
||||
break;
|
||||
@@ -137,7 +143,9 @@ export class FieldFormatEntityPolicy implements EntityPolicy {
|
||||
const links = entity.metadata.links ?? [];
|
||||
|
||||
for (let i = 0; i < links.length; ++i) {
|
||||
optional(`links.${i}`, links[i]?.icon, this.validators.isValidEntityName);
|
||||
require(`links.${i}`, links[i]?.url, this.validators.isValidLinkUrl);
|
||||
optional(`links.${i}`, links[i]?.title, this.validators.isValidLinkTitle);
|
||||
optional(`links.${i}`, links[i]?.icon, this.validators.isValidLinkIcon);
|
||||
}
|
||||
|
||||
return entity;
|
||||
|
||||
@@ -214,7 +214,7 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
|
||||
it.each([['invalid type'], [123], [{}], [{ url: 'https://foo' }]])(
|
||||
'rejects bad links type %s',
|
||||
async (val: any) => {
|
||||
async (val: unknown) => {
|
||||
data.metadata.links = val;
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/links/);
|
||||
},
|
||||
|
||||
@@ -161,4 +161,44 @@ describe('CommonValidatorFunctions', () => {
|
||||
])(`isValidDnsLabel %p ? %p`, (value, result) => {
|
||||
expect(CommonValidatorFunctions.isValidDnsLabel(value)).toBe(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, false],
|
||||
[7, false],
|
||||
['', false],
|
||||
['abc', false],
|
||||
[' abc', false],
|
||||
['', false],
|
||||
[{}, false],
|
||||
['http://foo', true],
|
||||
['https://www.foo.com/', true],
|
||||
['https://foo.com:8080', true],
|
||||
['https://foo:8080/page', true],
|
||||
['https://foo:8080/sub/page', true],
|
||||
['https://foo:8080/sub/page?query', true],
|
||||
['https://foo:8080/sub/page/?query=value', true],
|
||||
['https://foo:8080/sub/page/?query=value&', true],
|
||||
['https://foo:8080/sub/page/?query=value&another=val', true],
|
||||
['https://foo.com/page#fragment', true],
|
||||
['ftp://ftp.some.domain.com/path', true],
|
||||
['xyz://custom-protocol:4444/path', true],
|
||||
])(`isValidUrl %p ? %p`, (value, result) => {
|
||||
expect(CommonValidatorFunctions.isValidUrl(value)).toBe(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, false],
|
||||
[true, false],
|
||||
[7, false],
|
||||
[{}, false],
|
||||
['', false],
|
||||
[' ', false],
|
||||
[' ', false],
|
||||
['abc', true],
|
||||
[' abc ', true],
|
||||
['abc xyz', true],
|
||||
['abc xyz abc.', true],
|
||||
])(`isValidString %p ? %p`, (value, result) => {
|
||||
expect(CommonValidatorFunctions.isValidString(value)).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,4 +92,35 @@ export class CommonValidatorFunctions {
|
||||
/^[a-z0-9]+(\-[a-z0-9]+)*$/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the value is a valid URL.
|
||||
*
|
||||
* @param value The value to check
|
||||
*/
|
||||
static isValidUrl(value: unknown): boolean {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return !!url;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the value is a non empty string value.
|
||||
*
|
||||
* @param value The value to check
|
||||
*/
|
||||
static isValidString(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
/^(?!\s*$).+/.test(value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ const defaultValidators: Validators = {
|
||||
isValidAnnotationKey: KubernetesValidatorFunctions.isValidAnnotationKey,
|
||||
isValidAnnotationValue: KubernetesValidatorFunctions.isValidAnnotationValue,
|
||||
isValidTag: CommonValidatorFunctions.isValidDnsLabel,
|
||||
isValidLinkUrl: CommonValidatorFunctions.isValidUrl,
|
||||
isValidLinkTitle: CommonValidatorFunctions.isValidString,
|
||||
isValidLinkIcon: KubernetesValidatorFunctions.isValidObjectName,
|
||||
};
|
||||
|
||||
export function makeValidator(overrides: Partial<Validators> = {}): Validators {
|
||||
|
||||
@@ -24,4 +24,7 @@ export type Validators = {
|
||||
isValidAnnotationKey(value: unknown): boolean;
|
||||
isValidAnnotationValue(value: unknown): boolean;
|
||||
isValidTag(value: unknown): boolean;
|
||||
isValidLinkUrl(value: unknown): boolean;
|
||||
isValidLinkTitle(value: unknown): boolean;
|
||||
isValidLinkIcon(value: unknown): boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user