Skip to content

Commit 4bb1e75

Browse files
author
Jared Parnell
committed
data-quality: schedule: Check scheduleEventType is a valid event subClass
1 parent 4c7f8a9 commit 4bb1e75

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

src/errors/validation-error-type.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const ValidationErrorType = {
4343
BELOW_MIN_VALUE_INCLUSIVE: 'below_min_value_inclusive',
4444
URI_TEMPLATE_MISSING_PLACEHOLDER: 'uri_template_missing_placeholder',
4545
EXCEPTION_DATES_NOT_IN_SCHEDULE: 'exception_dates_not_in_schedule',
46+
INVALID_SCHEDULE_EVENT_TYPE: 'invalid_schedule_event_type',
4647
};
4748

4849
module.exports = Object.freeze(ValidationErrorType);
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const ScheduleEventTypeIsEventSubclass = require('./scheduleeventype-is-valid-event-subclass-rule');
2+
const Model = require('../../classes/model');
3+
const ModelNode = require('../../classes/model-node');
4+
const ValidationErrorType = require('../../errors/validation-error-type');
5+
const ValidationErrorSeverity = require('../../errors/validation-error-severity');
6+
7+
describe('ScheduleEventTypeIsEventSubclass', () => {
8+
let rule;
9+
10+
beforeEach(() => {
11+
rule = new ScheduleEventTypeIsEventSubclass();
12+
});
13+
14+
it('should target models of any type', () => {
15+
const model = new Model({
16+
type: 'Schedule',
17+
}, 'latest');
18+
19+
const isTargeted = rule.isModelTargeted(model);
20+
expect(isTargeted).toBe(true);
21+
});
22+
23+
it('should return no errors if scheduleEventType is a subClass of Event', async () => {
24+
const model = new Model({
25+
type: 'Schedule',
26+
subClassGraph: ['#Event'],
27+
}, 'latest');
28+
29+
const data = {
30+
scheduledEventType: 'ScheduledSession',
31+
};
32+
33+
const nodeToTest = new ModelNode(
34+
'$',
35+
data,
36+
null,
37+
model,
38+
);
39+
const errors = await rule.validate(nodeToTest);
40+
41+
expect(errors.length).toBe(0);
42+
});
43+
44+
it('should return errors if scheduleEventType is not a subClass of Event', async () => {
45+
const model = new Model({
46+
type: 'Schedule',
47+
subClassGraph: ['#Event'],
48+
}, 'latest');
49+
50+
const data = {
51+
scheduledEventType: 'Place',
52+
};
53+
54+
const nodeToTest = new ModelNode(
55+
'$',
56+
data,
57+
null,
58+
model,
59+
);
60+
const errors = await rule.validate(nodeToTest);
61+
62+
expect(errors.length).toBe(1);
63+
for (const error of errors) {
64+
expect(error.type).toBe(ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE);
65+
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
66+
}
67+
});
68+
69+
it('should return errors if scheduleEventType does not have a valid model', async () => {
70+
const model = new Model({
71+
type: 'Schedule',
72+
subClassGraph: ['#Event'],
73+
}, 'latest');
74+
75+
const data = {
76+
scheduledEventType: 'Banana',
77+
};
78+
79+
const nodeToTest = new ModelNode(
80+
'$',
81+
data,
82+
null,
83+
model,
84+
);
85+
const errors = await rule.validate(nodeToTest);
86+
87+
expect(errors.length).toBe(1);
88+
for (const error of errors) {
89+
expect(error.type).toBe(ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE);
90+
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
91+
}
92+
});
93+
94+
it('should return errors if scheduleEventType does not have a subClassGraph', async () => {
95+
const model = new Model({
96+
type: 'Schedule',
97+
subClassGraph: ['#Event'],
98+
}, 'latest');
99+
100+
const data = {
101+
scheduledEventType: 'DownloadData',
102+
};
103+
104+
const nodeToTest = new ModelNode(
105+
'$',
106+
data,
107+
null,
108+
model,
109+
);
110+
const errors = await rule.validate(nodeToTest);
111+
112+
expect(errors.length).toBe(1);
113+
for (const error of errors) {
114+
expect(error.type).toBe(ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE);
115+
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
116+
}
117+
});
118+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const Rule = require('../rule');
2+
const DataModelHelper = require('../../helpers/data-model');
3+
const ValidationErrorType = require('../../errors/validation-error-type');
4+
const ValidationErrorCategory = require('../../errors/validation-error-category');
5+
const ValidationErrorSeverity = require('../../errors/validation-error-severity');
6+
7+
module.exports = class ScheduleEventTypeIsEventSubclass extends Rule {
8+
constructor(options) {
9+
super(options);
10+
this.targetModels = 'Schedule';
11+
this.meta = {
12+
name: 'ScheduleEventTypeIsEventSubclass',
13+
description: 'The `scheduleEventType` in the `Schedule` is not a subclass of `Event`.',
14+
tests: {
15+
modelNotRecognised: {
16+
message: 'The model described by `scheduleEventType` ({{value}}) is not a valid model type. See the [Inheritence Overview](https://developer.openactive.io/publishing-data/data-feeds/types-of-feed#schema-org-type-inheritance-overview) for more information.',
17+
sampleValues: {
18+
value: 'BigSwimEvent',
19+
},
20+
category: ValidationErrorCategory.CONFORMANCE,
21+
severity: ValidationErrorSeverity.FAILURE,
22+
type: ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE,
23+
},
24+
modelHasNoSubClassGraph: {
25+
message: 'The model described by `scheduleEventType` ({{value}}) does not list any subclass types. See the [Inheritence Overview](https://developer.openactive.io/publishing-data/data-feeds/types-of-feed#schema-org-type-inheritance-overview) for more information.',
26+
sampleValues: {
27+
value: 'DownloadData',
28+
},
29+
category: ValidationErrorCategory.CONFORMANCE,
30+
severity: ValidationErrorSeverity.FAILURE,
31+
type: ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE,
32+
},
33+
modelIsNotEventSubClass: {
34+
message: 'The `scheduleEventType` ({{value}}) in `Schedule` does not inherit from `Event`. See the [Inheritence Overview](https://developer.openactive.io/publishing-data/data-feeds/types-of-feed#schema-org-type-inheritance-overview) for more information.',
35+
sampleValues: {
36+
value: 'ScheduledSession',
37+
},
38+
category: ValidationErrorCategory.CONFORMANCE,
39+
severity: ValidationErrorSeverity.FAILURE,
40+
type: ValidationErrorType.INVALID_SCHEDULE_EVENT_TYPE,
41+
},
42+
},
43+
};
44+
}
45+
46+
validateModel(node) {
47+
const errors = [];
48+
let model;
49+
let errorCondition;
50+
const scheduledEventType = node.getValue('scheduledEventType');
51+
52+
try {
53+
model = DataModelHelper.loadModel(scheduledEventType, 'latest');
54+
} catch (error) {
55+
model = undefined;
56+
}
57+
58+
if (typeof model === 'undefined') {
59+
errorCondition = 'modelNotRecognised';
60+
} else if (typeof model.subClassGraph === 'undefined') {
61+
errorCondition = 'modelHasNoSubClassGraph';
62+
} else if (model.subClassGraph.indexOf('#Event') === -1) {
63+
errorCondition = 'modelIsNotEventSubClass';
64+
}
65+
66+
if (errorCondition === 'modelIsNotEventSubClass') {
67+
errors.push(
68+
this.createError(
69+
errorCondition,
70+
{
71+
value: scheduledEventType,
72+
path: node.getPath('scheduledEventType'),
73+
},
74+
),
75+
);
76+
} else {
77+
return [];
78+
}
79+
80+
return errors;
81+
}
82+
};

0 commit comments

Comments
 (0)