This repository has been archived by the owner on Nov 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 22
Modularize validation #155
Merged
+1,884
−17
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
b99fecf
refactor: adds "validateStatusCode" unit
artem-zakharchenko 843dea1
refactor: adds "validateHeaders" unit
artem-zakharchenko 788a824
refactor: adds "validateBody" unit
artem-zakharchenko f9f75c2
chore: allow leading underscore for arguments
artem-zakharchenko 492d582
refactor: uses same signature for validation units
artem-zakharchenko 6182a66
refactor: adds "isJsonSchema" predicate for validateBody
artem-zakharchenko 3dd9b6d
refactor: removes "isJsonWeak" from "validateBody"
artem-zakharchenko 846c45d
refactor: accepts "content-type" header directly in "getBodyType"
artem-zakharchenko e8959de
refactor: returns explicit null in "normalizeHeaders"
artem-zakharchenko 558f7b7
refactor: delegate error handling to "validateBody" unit
artem-zakharchenko 9a58c57
refactor: removes content-type parsing error handling from "getBodyType"
artem-zakharchenko 98967c6
test: adds granular unit tests for "validateBody"
artem-zakharchenko 79448a3
refactor: adds "evolve" utility
artem-zakharchenko 350d8ca
fix: adds "normalizeHeaders"
artem-zakharchenko f2c2914
refactor: removes missing validator handling in "getBodyValidator"
artem-zakharchenko dd832ae
refactor: removes extra escaping for media type in "validateHeaders"
artem-zakharchenko b7b4452
refactor: adds "validateRequest"
artem-zakharchenko 1038ec8
fix: fixes wrong strings interpolation in test suits
artem-zakharchenko 46a5769
chore: updates to "[email protected]"
artem-zakharchenko 6eea402
test: integrates new implementation into old test suits
artem-zakharchenko 855a1d9
refactor: ensures same export type of modules
artem-zakharchenko 8ddfa93
test: adds response test cases to "validateElement" tests
artem-zakharchenko e5c6dea
refactor: propagates internal exception to given callback
artem-zakharchenko cf73425
refactor: removes trimming of header values
artem-zakharchenko 6c96930
refactor: removes try/catch blocks in "isJsonContentType"
artem-zakharchenko 0bd7607
test: ensures proper return type of "evolve"
artem-zakharchenko 7d50b13
refactor: renames "validateElement" to "validateMessage"
artem-zakharchenko c8018ea
refactor: prevents a call to callback within try/catch block
artem-zakharchenko c4a71c0
refactor: performs normalization of http messages in "validateMessage"
artem-zakharchenko 85b6c22
refactor: simplifies "isset" util
artem-zakharchenko c77a427
refactor: parameterize http message origin of "getBodyType"
artem-zakharchenko bd2a906
test: asserts each missing header in "validateHeaders" test
artem-zakharchenko 38776ba
test: polishes the test suits
artem-zakharchenko f42f615
test: adds JsonSchema test case for "validateBody"
artem-zakharchenko 5589c2f
test: fixes typos in validation tests
artem-zakharchenko a0e3229
refactor: removes explicit normalization from "validateHeaders"
artem-zakharchenko 98a9e6a
refactor: adds status code normalization to normalization layer
artem-zakharchenko 20ec986
feat: adds new validation algorithm, release preparations
artem-zakharchenko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
const { HttpRequest, ExpectedHttpRequest } = require('./model/http-request'); | ||
const { HttpResponse, ExpectedHttpResponse } = require('./model/http-response'); | ||
|
||
const { validate, isValid, isValidatable } = require('./validate'); | ||
const { isValid, isValidatable } = require('./validate'); | ||
const validate = require('./next/validate'); | ||
|
||
module.exports = { | ||
// next | ||
validate, | ||
|
||
// legacy | ||
HttpRequest, | ||
HttpResponse, | ||
ExpectedHttpRequest, | ||
ExpectedHttpResponse, | ||
validate, | ||
isValid, | ||
isValidatable | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,348 @@ | ||
const { assert } = require('chai'); | ||
const { validateMessage } = require('../../validateMessage'); | ||
|
||
describe('validateMessage', () => { | ||
describe('with matching requests', () => { | ||
const request = { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: '{ "foo": "bar" }' | ||
}; | ||
const result = validateMessage(request, request); | ||
|
||
it('returns validation result object', () => { | ||
assert.isObject(result); | ||
}); | ||
|
||
it('contains all validatable keys', () => { | ||
assert.hasAllKeys(result, ['headers', 'body']); | ||
}); | ||
|
||
describe('headers', () => { | ||
it('has "HeadersJsonExample" validator', () => { | ||
assert.propertyVal(result.headers, 'validator', 'HeadersJsonExample'); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" real headers type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'realType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" expected headers type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'expectedType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.headers.results, 0); | ||
}); | ||
}); | ||
|
||
describe('body', () => { | ||
it('has "JsonExample" validator', () => { | ||
assert.propertyVal(result.body, 'validator', 'JsonExample'); | ||
}); | ||
|
||
it('has "application/json" real body type', () => { | ||
assert.propertyVal(result.body, 'realType', 'application/json'); | ||
}); | ||
|
||
it('has "application/json" expected body type', () => { | ||
assert.propertyVal(result.body, 'expectedType', 'application/json'); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.body.results, 0); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('with non-matching requests', () => { | ||
const result = validateMessage( | ||
{ | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json' | ||
}, | ||
body: '{ "lookHere": "foo" }' | ||
}, | ||
{ | ||
method: 'PUT', | ||
headers: '', | ||
body: '2' | ||
} | ||
); | ||
|
||
it('returns validation result object', () => { | ||
assert.isObject(result); | ||
}); | ||
|
||
it('contains all validatable keys', () => { | ||
assert.hasAllKeys(result, ['headers', 'body']); | ||
}); | ||
|
||
describe('method', () => { | ||
// See https://github.com/apiaryio/gavel.js/issues/158 | ||
it.skip('compares methods'); | ||
}); | ||
|
||
describe('headers', () => { | ||
it('has "HeadersJsonExample" validator', () => { | ||
assert.propertyVal(result.headers, 'validator', 'HeadersJsonExample'); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" as real type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'realType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" expected type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'expectedType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.headers.results, 0); | ||
}); | ||
}); | ||
|
||
describe('body', () => { | ||
it('has "JsonExample" validator', () => { | ||
assert.propertyVal(result.body, 'validator', 'JsonExample'); | ||
}); | ||
|
||
it('has "application/json" real type', () => { | ||
assert.propertyVal(result.body, 'realType', 'application/json'); | ||
}); | ||
|
||
it('has "application/json" expected type', () => { | ||
assert.propertyVal(result.body, 'expectedType', 'application/json'); | ||
}); | ||
|
||
describe('produces an error', () => { | ||
it('exactly one error', () => { | ||
assert.lengthOf(result.body.results, 1); | ||
}); | ||
|
||
it('has "error" severity', () => { | ||
assert.propertyVal(result.body.results[0], 'severity', 'error'); | ||
}); | ||
|
||
it('has explanatory message', () => { | ||
assert.propertyVal( | ||
result.body.results[0], | ||
'message', | ||
`At '' Invalid type: object (expected integer)` | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('with matching responses', () => { | ||
const response = { | ||
statusCode: 200, | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: '{ "foo": "bar" }' | ||
}; | ||
const result = validateMessage(response, response); | ||
|
||
it('returns validation result object', () => { | ||
assert.isObject(result); | ||
}); | ||
|
||
it('contains all validatable keys', () => { | ||
assert.hasAllKeys(result, ['statusCode', 'headers', 'body']); | ||
}); | ||
|
||
describe('statusCode', () => { | ||
it('has "TextDiff" validator', () => { | ||
assert.propertyVal(result.statusCode, 'validator', 'TextDiff'); | ||
}); | ||
|
||
it('has "text/vnd.apiary.status-code" real type', () => { | ||
assert.propertyVal( | ||
result.statusCode, | ||
'realType', | ||
'text/vnd.apiary.status-code' | ||
); | ||
}); | ||
|
||
it('has "text/vnd.apiary.status-code" expected type', () => { | ||
assert.propertyVal( | ||
result.statusCode, | ||
'expectedType', | ||
'text/vnd.apiary.status-code' | ||
); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.statusCode.results, 0); | ||
}); | ||
}); | ||
|
||
describe('headers', () => { | ||
it('has "HeadersJsonExample" validator', () => { | ||
assert.propertyVal(result.headers, 'validator', 'HeadersJsonExample'); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" real type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'realType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" expected type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'expectedType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.headers.results, 0); | ||
}); | ||
}); | ||
|
||
describe('body', () => { | ||
it('has "JsonExample" validator', () => { | ||
assert.propertyVal(result.body, 'validator', 'JsonExample'); | ||
}); | ||
|
||
it('has "application/json" real type', () => { | ||
assert.propertyVal(result.body, 'realType', 'application/json'); | ||
}); | ||
|
||
it('has "application/json" expected type', () => { | ||
assert.propertyVal(result.body, 'expectedType', 'application/json'); | ||
}); | ||
|
||
it('has no errors', () => { | ||
assert.lengthOf(result.body.results, 0); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('with non-matching responses', () => { | ||
const realResponse = { | ||
statusCode: 400, | ||
headers: { | ||
'Content-Type': 'application/json' | ||
} | ||
}; | ||
const expectedResponse = { | ||
statusCode: 200, | ||
headers: { | ||
'Accept-Language': 'en-US' | ||
} | ||
}; | ||
const result = validateMessage(realResponse, expectedResponse); | ||
|
||
it('returns validation result object', () => { | ||
assert.isObject(result); | ||
}); | ||
|
||
it('contains all validatable keys', () => { | ||
assert.hasAllKeys(result, ['statusCode', 'headers']); | ||
}); | ||
|
||
describe('statusCode', () => { | ||
it('has "TextDiff" validator', () => { | ||
assert.propertyVal(result.statusCode, 'validator', 'TextDiff'); | ||
}); | ||
|
||
it('has "text/vnd.apiary.status-code" real type', () => { | ||
assert.propertyVal( | ||
result.statusCode, | ||
'realType', | ||
'text/vnd.apiary.status-code' | ||
); | ||
}); | ||
|
||
it('has "text/vnd.apiary.status-code" expected type', () => { | ||
assert.propertyVal( | ||
result.statusCode, | ||
'expectedType', | ||
'text/vnd.apiary.status-code' | ||
); | ||
}); | ||
|
||
describe('produces an error', () => { | ||
it('exactly one error', () => { | ||
assert.lengthOf(result.statusCode.results, 1); | ||
}); | ||
|
||
it('has "error" severity', () => { | ||
assert.propertyVal(result.statusCode.results[0], 'severity', 'error'); | ||
}); | ||
|
||
it('has explanatory message', () => { | ||
assert.propertyVal( | ||
result.statusCode.results[0], | ||
'message', | ||
'Real and expected data does not match.' | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('headers', () => { | ||
it('has "HeadersJsonExample" validator', () => { | ||
assert.propertyVal(result.headers, 'validator', 'HeadersJsonExample'); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" real type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'realType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
it('has "application/vnd.apiary.http-headers+json" real type', () => { | ||
assert.propertyVal( | ||
result.headers, | ||
'realType', | ||
'application/vnd.apiary.http-headers+json' | ||
); | ||
}); | ||
|
||
describe('produces an error', () => { | ||
it('exactly one error', () => { | ||
assert.lengthOf(result.headers.results, 1); | ||
}); | ||
|
||
it('has "error" severity', () => { | ||
assert.propertyVal(result.headers.results[0], 'severity', 'error'); | ||
}); | ||
|
||
it('includes missing header in the message', () => { | ||
assert.propertyVal( | ||
result.headers.results[0], | ||
'message', | ||
`At '/accept-language' Missing required property: accept-language` | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this added? I don't see anything with leading underscores.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used here currently:
gavel.js/lib/api/units/validateBody.js
Line 140 in e260a4c
We can always swap the order of arguments, but it feels as a wrong compromise imho. The order of arguments if a functional design decision, and alerts on unused variables is a developer-friendly reminder. I would prioritize the first one, but am always open to discussion :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with it, but perhaps it doesn't deserve a global rule if it's at a single place :)