Skip to content

Commit

Permalink
Merge pull request #54 from jj4th/add-json-comprehension
Browse files Browse the repository at this point in the history
Add JSON comprehension
  • Loading branch information
mrak committed Mar 18, 2016
2 parents 184ed05 + 0784682 commit 9216aff
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 3 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,24 @@ postedData.json

* if `postedData.json` doesn't exist on the filesystem when `/match/against/file` is requested, stubby will match post contents against `{"fallback":"data"}` (from `post`) instead.

#### json

* not used if `post` or `file` are present.
* will be parsed into a JavaScript object.
* allows you to specify a JSON string that will be deeply compared with a JSON request

Although not required, it is recommended to also specify a `application/json` header requirement.

```yaml
- request:
url: ^/match/against/jsonString$
headers:
content-type: application/json
json: '{"key1":"value1","key2":"value2"}'
```

JSON strings may contain `"key": "value"` pairs in any order: `{"key1":"value1, "key2":"value2"}` is equivalent to `{"key2":"value2, "key1":"value1"}`

#### headers

* values are full-fledged __regular expressions__
Expand Down
27 changes: 26 additions & 1 deletion src/models/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function Endpoint(endpoint, datadir) {
}

Endpoint.prototype.matches = function (request) {
var file, post, upperMethods;
var file, post, json, upperMethods;
var matches = {};

matches.url = matchRegex(this.request.url, request.url);
Expand All @@ -41,6 +41,13 @@ Endpoint.prototype.matches = function (request) {
if (post && request.post) {
matches.post = matchRegex(normalizeEOL(post), normalizeEOL(request.post));
if (!matches.post) { return null; }
} else if (this.request.json && request.post) {
try {
json = JSON.parse(request.post);
if (!compareObjects(this.request.json, json)) { return null; }
} catch (e) {
return null;
}
}

if (this.request.method instanceof Array) {
Expand Down Expand Up @@ -113,6 +120,10 @@ function purifyRequest(incoming) {
post: incoming.post
};

if (incoming.json) {
outgoing.json = JSON.parse(incoming.json);
}

outgoing.headers = purifyAuthorization(outgoing.headers);
outgoing = pruneUndefined(outgoing);
return outgoing;
Expand Down Expand Up @@ -206,6 +217,20 @@ function compareHashMaps(configured, incoming) {
return headers;
}

function compareObjects(configured, incoming) {
var key;

for (key in configured) {
if (typeof configured[key] !== typeof incoming[key]) { return false; }

if (typeof configured[key] === 'object') {
if (!compareObjects(configured[key], incoming[key])) { return false; }
} else if (configured[key] !== incoming[key]) { return false; }
}

return true;
}

function matchRegex(compileMe, testMe) {
if (testMe == null) { testMe = ''; }
return String(testMe).match(RegExp(compileMe, 'm'));
Expand Down
12 changes: 12 additions & 0 deletions test/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@ describe('contract', function () {
result = sut(data);
assert(result === null);
});

it('should return no errors for a missing json field', function () {
var result;

data.request.json = null;
result = sut(data);
assert(result === null);

delete data.request.json;
result = sut(data);
assert(result === null);
});
});

describe('response', function () {
Expand Down
24 changes: 22 additions & 2 deletions test/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ describe('Endpoint', function () {

describe('constructor', function () {
it('should at least copy over valid data', function () {
var expectedBody;
var expectedBody, expectedJSON;
var data = {
request: {
url: '/',
Expand All @@ -194,7 +194,8 @@ describe('Endpoint', function () {
header: 'string'
},
post: 'data',
file: 'file.txt'
file: 'file.txt',
json: '{"key":"value"}'
},
response: [{
latency: 3000,
Expand All @@ -208,13 +209,19 @@ describe('Endpoint', function () {
};
var actual = new Endpoint(data);
var actualbody = actual.response[0].body.toString();
var actualJSON = actual.request.json;

delete actual.response[0].body;
expectedBody = data.response[0].body;
delete data.response[0].body;

delete actual.request.json;
expectedJSON = JSON.parse(data.request.json);
delete data.request.json;

assert.deepEqual(actual, data);
assert(expectedBody === actualbody);
assert.deepEqual(expectedJSON, actualJSON);
});

it('should default method to GET', function () {
Expand Down Expand Up @@ -326,6 +333,19 @@ describe('Endpoint', function () {
assert(actual.response[0].body.toString() === expected);
});

it('should JSON parse the object json in request', function () {
var actual, expected;
expected = {
key: 'value'
};
this.data.request = {
json: '{"key":"value"}'
};

actual = new Endpoint(this.data);
assert.deepEqual(actual.request.json, expected);
});

it('should get the Origin header', function () {
var actual, expected;
expected = 'http://example.org';
Expand Down
91 changes: 91 additions & 0 deletions test/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,97 @@ describe('Endpoints', function () {
});
});

describe('request json versus post or file', function () {
it('should not match response if the request json does not match the incoming post', function () {
var expected = 'Endpoint with given request doesn\'t exist.';

sut.create({
request: {
url: '/testing',
json: '{"key2":"value2", "key1":"value1"}',
method: 'post'
},
response: 200
});
data = {
method: 'POST',
url: '/testing',
post: '{"key1": "value1", "key3":"value3"}'
};
sut.find(data, callback);

assert(callback.calledWith(expected));
});

it('should match response with json if json is supplied and neither post nor file are supplied', function () {
var expected = {
status: 200
};
sut.create({
request: {
url: '/testing',
json: '{"key2":"value2", "key1":"value1"}',
method: 'post'
},
response: expected
});
data = {
method: 'POST',
url: '/testing',
post: '{"key1": "value1", "key2":"value2"}'
};
sut.find(data, callback);

assert(callback.calledWith(null));
});

it('should match response with post if post is supplied', function () {
var expected = {
status: 200
};
sut.create({
request: {
url: '/testing',
json: '{"key":"value"}',
post: 'the post!',
method: 'post'
},
response: expected
});
data = {
method: 'POST',
url: '/testing',
post: 'the post!'
};
sut.find(data, callback);

assert(callback.calledWith(null));
});

it('should match response with file if file is supplied', function () {
var expected = {
status: 200
};
sut.create({
request: {
url: '/testing',
file: 'test/data/endpoints.file',
json: '{"key":"value"}',
method: 'post'
},
response: expected
});
data = {
method: 'POST',
url: '/testing',
post: 'file contents!'
};
sut.find(data, callback);

assert(callback.calledWith(null));
});
});

describe('request post versus file', function () {
it('should match response with post if file is not supplied', function () {
var expected = {
Expand Down

0 comments on commit 9216aff

Please sign in to comment.