diff --git a/package.json b/package.json
index ee2f306a6..eeb15e951 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@stoplight/spectral",
"version": "0.0.0",
- "description": "A flexible object linter with out of the box OpenAPI v2 and v3 support.",
+ "description": "A flexible object linter with out of the box support for OpenAPI v2 and v3.",
"keywords": [
"json linter",
"linter",
@@ -50,10 +50,10 @@
"should": "13.2.x"
},
"devDependencies": {
- "@stoplight/scripts": "3.0.2",
+ "@stoplight/scripts": "3.1.0",
"@types/jsonpath": "0.2.x",
"@types/lodash": "4.x.x",
- "typescript": "3.2.1"
+ "typescript": "3.2.2"
},
"lint-staged": {
"*.{ts,tsx}$": [
diff --git a/src/__tests__/functions.ts b/src/__tests__/functions.ts
new file mode 100644
index 000000000..773ef1a68
--- /dev/null
+++ b/src/__tests__/functions.ts
@@ -0,0 +1,617 @@
+import { Spectral } from '../spectral';
+import { IRuleResult, Rule, RuleFunction } from '../types';
+
+const applyRuleToObject = (r: Rule, o: object): IRuleResult[] => {
+ const s = new Spectral();
+ s.addRules({
+ testRule: r,
+ });
+ return s.run(o).results;
+};
+
+describe('functions', () => {
+ describe('truthy', () => {
+ test('returns result if value is not present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.TRUTHY,
+ functionOptions: { properties: 'something-not-present' },
+ },
+ },
+ {
+ info: {
+ version: '1.0.0',
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('returns result if value is not truthy', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$',
+ then: {
+ function: RuleFunction.TRUTHY,
+ functionOptions: { properties: 'count' },
+ },
+ },
+ {
+ count: 0,
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('returns results for multiple properties', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$',
+ then: {
+ function: RuleFunction.TRUTHY,
+ functionOptions: { properties: ['count', 'name', 'count2'] },
+ },
+ },
+ {
+ count: 0,
+ name: 'joe',
+ count2: 0,
+ }
+ ).length
+ ).toEqual(2);
+ });
+
+ test('doesnt return result if value is present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$',
+ then: {
+ function: RuleFunction.TRUTHY,
+ functionOptions: { properties: 'info' },
+ },
+ },
+ {
+ info: {
+ version: '1.0.0',
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+ });
+
+ describe('alphabetical', () => {
+ describe('arrays', () => {
+ test('returns result if value keys are not alphabetized', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ field: 'tags',
+ function: RuleFunction.ALPHABETICAL,
+ functionOptions: { keyedBy: 'name' },
+ },
+ },
+ {
+ info: {
+ tags: [{ name: 'Far', description: 'bar' }, { name: 'Boo', description: 'foo' }],
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont return result if value keys are alphabetized', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info.tags',
+ then: {
+ function: RuleFunction.ALPHABETICAL,
+ functionOptions: { keyedBy: 'name' },
+ },
+ },
+ {
+ info: {
+ tags: [{ name: 'Boo', description: 'bar' }, { name: 'Far', description: 'foo' }],
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+
+ test('return results if values are alphabetized', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ field: 'tags',
+ function: RuleFunction.ALPHABETICAL,
+ },
+ },
+ {
+ info: {
+ tags: ['b', 'a'],
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+ });
+
+ describe('objects', () => {
+ test('returns result if array values are not alphabetized', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.ALPHABETICAL,
+ },
+ },
+ {
+ info: {
+ b: true,
+ a: true,
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont return result if object values are alphabetized', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$',
+ then: {
+ field: 'info',
+ function: RuleFunction.ALPHABETICAL,
+ },
+ },
+ {
+ info: {
+ a: true,
+ b: true,
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+ });
+ });
+
+ describe('or', () => {
+ test('returns result if no properties are present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.OR,
+ functionOptions: {
+ properties: ['something-not-present', 'something-else-not-present'],
+ },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont returns results if any properties are present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.OR,
+ functionOptions: {
+ properties: ['version', 'something-else-not-present'],
+ },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ },
+ }
+ ).length
+ ).toEqual(0);
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.OR,
+ functionOptions: {
+ properties: ['version', 'title', 'termsOfService'],
+ },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+ });
+
+ describe('xor', () => {
+ test('returns result if no properties are present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.XOR,
+ functionOptions: { properties: ['yada-yada', 'whatever'] },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('returns result if both properties are present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.XOR,
+ functionOptions: { properties: ['version', 'title'] },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont returns results if one of the properties are present', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.XOR,
+ functionOptions: { properties: ['something', 'title'] },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+ });
+
+ describe('pattern', () => {
+ test('returns result if pattern is not matched (on string)', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.PATTERN,
+ functionOptions: {
+ property: 'termsOfService',
+ value: '^orange.*$',
+ },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('returns result if pattern is not matched (on object keys)', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.responses',
+ then: {
+ function: RuleFunction.PATTERN,
+ functionOptions: { property: '*', value: '^[0-9]+$' },
+ },
+ },
+ {
+ responses: {
+ '123': {
+ test: 'something',
+ },
+ '456avbas': {
+ test: 'something',
+ },
+ '789': {
+ test: 'something',
+ },
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont return result if pattern is matched (on string)', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.info',
+ then: {
+ function: RuleFunction.PATTERN,
+ functionOptions: {
+ property: 'termsOfService',
+ value: '^http.*$',
+ },
+ },
+ },
+ {
+ swagger: '2.0',
+ info: {
+ version: '1.0.0',
+ title: 'Swagger Petstore',
+ termsOfService: 'http://swagger.io/terms/',
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+
+ test('dont return result if pattern is matched (on object keys)', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$.responses',
+ then: {
+ function: RuleFunction.PATTERN,
+ functionOptions: { property: '*', value: '^[0-9]+$' },
+ },
+ },
+ {
+ responses: {
+ '123': {
+ test: 'something',
+ },
+ '456': {
+ test: 'something',
+ },
+ '789': {
+ test: 'something',
+ },
+ },
+ }
+ ).length
+ ).toEqual(0);
+ });
+ });
+
+ describe('notContain', () => {
+ test('returns result if property contains value', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$..*',
+ then: {
+ function: RuleFunction.NOT_CONTAIN,
+ functionOptions: {
+ properties: ['description'],
+ value: '",
+ },
+ }
+ ).length
+ ).toEqual(1);
+ });
+
+ test('dont return results if property doesnt contain value', () => {
+ expect(
+ applyRuleToObject(
+ {
+ summary: '',
+ given: '$..*',
+ then: {
+ function: RuleFunction.NOT_CONTAIN,
+ functionOptions: {
+ properties: ['description'],
+ value: '",
- },
- }
- ).length
- ).toEqual(1);
- });
-
- test('dont return results if property doesnt contain value', () => {
- expect(
- applyRuleToObject(
- {
- type: RuleType.STYLE,
- then: {
- function: RuleFunction.NOT_CONTAIN,
- functionOptions: { properties: ['description'], value: '