Skip to content

Commit

Permalink
Merge pull request #335 from openactive/per-validation-mode-required-…
Browse files Browse the repository at this point in the history
…and-recommended-rules

Per validation mode required and recommended rules
  • Loading branch information
henryaddison authored Oct 25, 2019
2 parents 3464a74 + f392eb0 commit fe08fcf
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 9 deletions.
51 changes: 45 additions & 6 deletions src/classes/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,51 @@ const Model = class {
return this.data.commonTypos || {};
}

get requiredFields() {
return this.data.requiredFields || [];
getImperativeConfiguration(validationMode) {
if (
typeof this.validationMode === 'object'
&& typeof this.validationMode[validationMode] === 'string'
) {
return this.imperativeConfiguration[this.validationMode[validationMode]];
}
return undefined;
}

getRequiredFields(validationMode) {
let fields;
const specificImperativeConfiguration = this.getImperativeConfiguration(validationMode);
if (typeof specificImperativeConfiguration === 'object') {
fields = specificImperativeConfiguration.requiredFields;
} else {
fields = this.data.requiredFields;
}
return fields || [];
}

hasRequiredField(field) {
return PropertyHelper.arrayHasField(this.requiredFields, field, this.version);
}

get requiredOptions() {
return this.data.requiredOptions || [];
getRequiredOptions(validationMode) {
let options;
const specificImperativeConfiguration = this.getImperativeConfiguration(validationMode);
if (typeof specificImperativeConfiguration === 'object') {
options = specificImperativeConfiguration.requiredOptions;
} else {
options = this.data.requiredOptions;
}
return options || [];
}

get recommendedFields() {
return this.data.recommendedFields || [];
getRecommendedFields(validationMode) {
let fields;
const specificImperativeConfiguration = this.getImperativeConfiguration(validationMode);
if (typeof specificImperativeConfiguration === 'object') {
fields = specificImperativeConfiguration.recommendedFields;
} else {
fields = this.data.recommendedFields;
}
return fields || [];
}

hasRecommendedField(field) {
Expand Down Expand Up @@ -114,6 +145,14 @@ const Model = class {
}
return undefined;
}

get validationMode() {
return this.data.validationMode;
}

get imperativeConfiguration() {
return this.data.imperativeConfiguration;
}
};

module.exports = Model;
56 changes: 56 additions & 0 deletions src/rules/core/recommended-fields-rule-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const Model = require('../../classes/model');
const ModelNode = require('../../classes/model-node');
const ValidationErrorType = require('../../errors/validation-error-type');
const ValidationErrorSeverity = require('../../errors/validation-error-severity');
const ValidationMode = require('../../helpers/validation-mode');
const OptionsHelper = require('../../helpers/options');

describe('RecommendedFieldsRule', () => {
const model = new Model({
Expand All @@ -11,6 +13,16 @@ describe('RecommendedFieldsRule', () => {
'description',
'name',
],
validationMode: {
C1Request: 'request',
},
imperativeConfiguration: {
request: {
recommendedFields: [
'duration',
],
},
},
}, 'latest');
model.hasSpecification = true;

Expand Down Expand Up @@ -90,6 +102,50 @@ describe('RecommendedFieldsRule', () => {
}
});

describe('when validation mode is on with separate required fields', () => {
const options = new OptionsHelper({ validationMode: ValidationMode.C1Request });

it('should return no errors if all required fields are present', () => {
const data = {
type: 'Event',
duration: 'PT1H30M',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(0);
});

it('should return a warning per field if any required fields are missing', () => {
const data = {
type: 'Event',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(1);

for (const error of errors) {
expect(error.type).toBe(ValidationErrorType.MISSING_RECOMMENDED_FIELD);
expect(error.severity).toBe(ValidationErrorSeverity.WARNING);
}
});
});

describe('with inheritsTo properties', () => {
it('should respect recommended fields when inheritsTo is *', () => {
const modelObj = loadInheritanceModel();
Expand Down
3 changes: 2 additions & 1 deletion src/rules/core/recommended-fields-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ module.exports = class RecommendedFieldsRule extends Rule {
return [];
}
const errors = [];
for (const field of node.model.recommendedFields) {

for (const field of node.model.getRecommendedFields(node.options.validationMode)) {
const testValue = node.getValueWithInheritance(field);
const example = node.model.getRenderedExample(field);
if (typeof testValue === 'undefined') {
Expand Down
57 changes: 57 additions & 0 deletions src/rules/core/required-fields-rule-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const Model = require('../../classes/model');
const ModelNode = require('../../classes/model-node');
const ValidationErrorType = require('../../errors/validation-error-type');
const ValidationErrorSeverity = require('../../errors/validation-error-severity');
const ValidationMode = require('../../helpers/validation-mode');
const OptionsHelper = require('../../helpers/options');

describe('RequiredFieldsRule', () => {
const model = new Model({
Expand All @@ -12,6 +14,16 @@ describe('RequiredFieldsRule', () => {
'activity',
'location',
],
validationMode: {
C1Request: 'request',
},
imperativeConfiguration: {
request: {
requiredFields: [
'duration',
],
},
},
}, 'latest');
model.hasSpecification = true;

Expand Down Expand Up @@ -115,6 +127,50 @@ describe('RequiredFieldsRule', () => {
}
});

describe('when validation mode is on with separate required fields', () => {
const options = new OptionsHelper({ validationMode: ValidationMode.C1Request });

it('should return no errors if all required fields are present', () => {
const data = {
type: 'Event',
duration: 'PT1H30M',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(0);
});

it('should return a failure per field if any required fields are missing', () => {
const data = {
type: 'Event',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(1);

for (const error of errors) {
expect(error.type).toBe(ValidationErrorType.MISSING_REQUIRED_FIELD);
expect(error.severity).toBe(ValidationErrorSeverity.FAILURE);
}
});
});

describe('with inheritsTo properties', () => {
it('should respect required fields when inheritsTo is *', () => {
const modelObj = loadInheritanceModel();
Expand Down Expand Up @@ -296,6 +352,7 @@ describe('RequiredFieldsRule', () => {
});
});


describe('with inheritsFrom properties', () => {
it('should respect required fields when inheritsFrom is *', () => {
const modelObj = loadInheritanceModel();
Expand Down
3 changes: 2 additions & 1 deletion src/rules/core/required-fields-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ module.exports = class RequiredFieldsRule extends Rule {
return [];
}
const errors = [];
for (const field of node.model.requiredFields) {

for (const field of node.model.getRequiredFields(node.options.validationMode)) {
const testValue = node.getValueWithInheritance(field);
const example = node.model.getRenderedExample(field);
if (typeof testValue === 'undefined') {
Expand Down
60 changes: 60 additions & 0 deletions src/rules/core/required-optional-fields-rule-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const Model = require('../../classes/model');
const ModelNode = require('../../classes/model-node');
const ValidationErrorType = require('../../errors/validation-error-type');
const ValidationErrorSeverity = require('../../errors/validation-error-severity');
const ValidationMode = require('../../helpers/validation-mode');
const OptionsHelper = require('../../helpers/options');

describe('RequiredOptionalFieldsRule', () => {
const model = new Model({
Expand All @@ -18,6 +20,21 @@ describe('RequiredOptionalFieldsRule', () => {
],
},
],
validationMode: {
C1Request: 'request',
},
imperativeConfiguration: {
request: {
requiredOptions: [
{
options: [
'duration',
'endDate',
],
},
],
},
},
}, 'latest');
model.hasSpecification = true;

Expand Down Expand Up @@ -121,6 +138,49 @@ describe('RequiredOptionalFieldsRule', () => {
expect(errors[0].path).toBe('$["startDate","schedule"]');
});

describe('when validation mode is on with separate required fields', () => {
const options = new OptionsHelper({ validationMode: ValidationMode.C1Request });
it('should return no errors if required optional fields are present', () => {
const data = {
type: 'Event',
endDate: '2018-01-27T12:00:00Z',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(0);
});

it('should return a failure per option group if any required optional fields are missing', () => {
const data = {
type: 'Event',
startDate: '2018-01-27T12:00:00Z',
};

const nodeToTest = new ModelNode(
'$',
data,
null,
model,
options,
);
const errors = rule.validate(nodeToTest);

expect(errors.length).toBe(1);

expect(errors[0].type).toBe(ValidationErrorType.MISSING_REQUIRED_FIELD);
expect(errors[0].severity).toBe(ValidationErrorSeverity.FAILURE);
expect(errors[0].path).toBe('$["duration","endDate"]');
});
});

describe('with inheritsTo properties', () => {
it('should respect required optional fields when inheritsTo is *', () => {
const modelObj = loadInheritanceModel();
Expand Down
3 changes: 2 additions & 1 deletion src/rules/core/required-optional-fields-rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ module.exports = class RequiredOptionalFieldsRule extends Rule {
return [];
}
const errors = [];
for (const option of node.model.requiredOptions) {

for (const option of node.model.getRequiredOptions(node.options.validationMode)) {
if (typeof (option.options) !== 'undefined'
&& option.options instanceof Array
) {
Expand Down

0 comments on commit fe08fcf

Please sign in to comment.