├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENCE.md
├── examples-command-line
├── crud.json
├── get-and-post-with-after-response.json
├── modify-with-patch.json
├── ranged-delay.json
├── regexp-get.json
└── simple-get.json
├── examples-javascript
├── echo.js
├── fluent-crud.js
├── fluent-echo.js
├── fluent-extends.js
├── fluent-proxy.js
├── fluent-regex.js
├── fluent-simple.js
├── fluent-web-page-test.html
├── fluent-web-page-test.js
├── load-file-reload-unload.js
├── load-file-reload-unload.json
├── load-file-watch.js
├── regexp-url.js
├── simple.js
└── testwatch.js
├── index.js
├── lib
├── cors.js
├── debug.js
├── fluent.js
├── grammar.pegjs
├── route.js
└── server.js
├── npm-shrinkwrap.json
├── package.json
├── readme.md
└── tests
├── fluent.test.js
├── http.test.js
├── javascript.test.js
├── loadFileTest-1.json
├── loadFileTest-2.json
├── loadFileTest-3.json
└── static-test.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | node_modules
15 | npm-debug.log
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 6
4 | before_install:
5 | - npm install npm -g
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Interfake
2 |
3 | So you've been using Interfake and have decided that you'd like to help out? Fantastic! There's plenty to do and it could always benefit from another pair of eyes to make it even better and more useful. Hopefully this document will answer some of your questions. Give it a read, and if you're still confused you can to [get in touch](#get-in-touch).
4 |
5 | ## Goals
6 |
7 | Once upon a time, someone needed an "easier way than Sinatra" to spin up some test APIs for a bunch of iPhone UI tests. Soon, Interfake was born and allowed JSON files to be easily turned into APIs. Once the fluent interface was developed, things took off. It is fairly easy to summarise the goals of the project:
8 |
9 | > Provide a simple way to *define* JSON-based HTTP APIs
10 |
11 | In doing so, Interfake should...
12 |
13 | 1. Not impose strong opinions about how an API should look
14 | 2. Keep dependencies to a minimum
15 | 3. Improve productivity for developers at any level of the stack
16 | 4. Work on a wide range of platforms
17 |
18 | As well as that, anybody working on Interfake should strive to:
19 |
20 | 1. Keep the code well-covered by tests
21 | 2. Take strong measures to *not* break the existing interface for developers
22 | 3. Enjoy themselves. If it ever becomes a chore, then you shouldn't feel obliged to continue :)
23 |
24 | So without further ado, here's some recommendations of how to get started!
25 |
26 | ## How to make a bug report
27 |
28 | If you've found something which is *demonstrably behaving incorrectly* with Interfake, that's really awesome, because it means there's a great opportunity to improve the tool. Here's some guidelines for bug reports:
29 |
30 | 1. *Search the repo for Issues*: Use GitHub's search tool to search just this repository with some keywords about the bug you've found. It's possible it has already been reported.
31 | 2. **Check if it's really a bug**: It's possible that if you found the bug on the latest release of Interfake, the `master` branch on GitHub has a fix for it. Check it out, try and reproduce the bug using that code and if it's still there, there's probably a real issue.
32 | 3. **Create a test case for it**: Try writing a test case for the bug using Interfake's existing test files which can be used to help track down the responsible code. Essentially, extract only the code wrote which caused the bug and strip away everything else.
33 |
34 | So, if you're sure you've found a real bug you need to file a bug report. To be as helpful as possible, please try to include **reproduceable steps**, the **test case mentioned above**, and the **environment on which you are running Interfake**.
35 |
36 | ## How to make a feature request
37 |
38 | I'm very welcome to hear ideas for how to add useful new features to Interfake, and there are probably a lot of things that could be added. However, please don't be offended if your idea is rejected. There could be many reasons for why this is happening - it may already be a feature, or it may be too large in scope for a project of this size, or it may just not be in alignment with the aims of the project.
39 |
40 | 1. **Search through the existing issues for your feature**: If you find it, give it a :+1: (`:+1:`) and await a response. If it's been rejected, move on :) If you don't find it...
41 | 2. **Create an issue!** Please try to be as descriptive as possible with your feature, and mention in the title of the issue that it is a feature request so it can be tagged as such. I'll review it, but if it isn't appropriate, again, **please** don't take it personally. I mean you no offense :)
42 |
43 | ## How to make a Pull Request
44 |
45 | I love pull requests! However, there are some guidelines about how to make a good pull request, and how to further the chances of your pull request being merged. Here they are:
46 |
47 | 1. **Run the unit tests before you're done**: This is the number one golden rule of pull requests. If any of the existing tests in the Interfake test suite fail as a result of the code in your pull request, your request will simply not be accepted. You may be lucky in that a contributor is willing to help make them pass, but it won't be merged until those tests pass.
48 | 2. **Write some unit tests to cover your addition**: This is the number one point five golden rule of pull requests. You must create test cases for your feature or fix alongside the existing test cases, and they must pass.
49 | 3. **Make your pull request**: If you don't know how to do this, I recommend you read this [excellent GitHub help article](https://help.github.com/articles/creating-a-pull-request).
50 | 4. **Wait**: I will have a look. If your request is useful it may still take a while to be merged, and it's possible that you will be asked to change a couple of things - or I might change a couple of things myself. Whatever the case, please co-operate, it's for the good of the project.
51 |
52 | ## Code Guidelines
53 |
54 | Please try to keep to the same style used throughout Interfake. In general, it favours brevity over verbosity for public interfaces, but verbosity where it helps in private interfaces and variables. In other words, keep things as short and intuitive as possible. No crucial variables called things like `grMod` or anything strange and obfuscated like that.
55 |
56 | Also, we use tabs, not spaces. No, this doesn't mean tabs are better than spaces. I just chose tabs, and we're sticking with it.
57 |
58 | ## Versioning
59 |
60 | Please **do not include version bumps in your pull requests**. If you have a bug fix by all means make a pull request with it, but don't bump the version. This is the responsibility of official contributors (currently just @basicallydan). If you'd like to be an official contributor, please make some contributions first, and then get in touch if you think you can make a lasting, more permanent contribution to the project.
61 |
62 | # Thanks!
63 |
64 | Thanks for reading this, it's important we're all on the same page for a project like this. I look forward to your contribution :)
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | Interfake is licensed under the MIT License:
2 |
3 | > Copyright (c) 2012-2014: Daniel Hough, and other contributors:
4 | >
5 | > https://github.com/basicallydan/interfake/contributors
6 | >
7 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 | >
9 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | >
11 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/examples-command-line/crud.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/posts/1",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 404
9 | }
10 | },
11 | {
12 | "request": {
13 | "url": "/posts",
14 | "method": "post"
15 | },
16 | "response": {
17 | "code": 201,
18 | "body" : {
19 | "title": "First post",
20 | "version": 1,
21 | "links": {
22 | "self": "/posts/1"
23 | }
24 | }
25 | },
26 | "afterResponse": {
27 | "endpoints": [
28 | {
29 | "request": {
30 | "url": "/posts/1",
31 | "method": "get"
32 | },
33 | "response": {
34 | "code": 200,
35 | "body": {
36 | "title": "First post",
37 | "version": 1,
38 | "links": {
39 | "self": "/posts/1"
40 | }
41 | }
42 | }
43 | },
44 | {
45 | "request": {
46 | "url": "/posts/1",
47 | "method": "put"
48 | },
49 | "response": {
50 | "code": 200
51 | },
52 | "afterResponse": {
53 | "endpoints": [
54 | {
55 | "request": {
56 | "url": "/posts/1",
57 | "method": "get"
58 | },
59 | "response": {
60 | "code": 200,
61 | "body": {
62 | "title": "Modified First post",
63 | "version": 2,
64 | "links": {
65 | "self": "/posts/1"
66 | }
67 | }
68 | }
69 | }
70 | ]
71 | }
72 | },
73 | {
74 | "request": {
75 | "url": "/posts/1",
76 | "method": "delete"
77 | },
78 | "response": {
79 | "code": 210,
80 | "body": {}
81 | },
82 | "afterResponse": {
83 | "endpoints": [
84 | {
85 | "request": {
86 | "url": "/posts/1",
87 | "method": "get"
88 | },
89 | "response": {
90 | "code": 410
91 | }
92 | }
93 | ]
94 | }
95 | }
96 | ]
97 | }
98 | }
99 | ]
--------------------------------------------------------------------------------
/examples-command-line/get-and-post-with-after-response.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/books",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code":200,
9 | "body": {
10 | "books": [{
11 | "id":1,
12 | "title":"The War of the Worlds"
13 | }]
14 | }
15 | }
16 | },
17 | {
18 | "request": {
19 | "url": "/books",
20 | "method": "post"
21 | },
22 | "response": {
23 | "code":201,
24 | "body": {}
25 | },
26 | "afterResponse": {
27 | "endpoints": [
28 | {
29 | "request": {
30 | "url": "/books",
31 | "method": "get"
32 | },
33 | "response": {
34 | "code":200,
35 | "body": {
36 | "books": [
37 | {
38 | "id":1,
39 | "title":"The War of the Worlds"
40 | },
41 | {
42 | "id":2,
43 | "title":"Ender's Game"
44 | }
45 | ]
46 | }
47 | }
48 | }
49 | ]
50 | }
51 | }
52 | ]
--------------------------------------------------------------------------------
/examples-command-line/modify-with-patch.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/posts/1",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "body" : {
10 | "title": "First post",
11 | "version": 1,
12 | "links": {
13 | "self": "/posts/1"
14 | }
15 | }
16 | }
17 | },
18 | {
19 | "request": {
20 | "url": "/posts/1",
21 | "method": "patch"
22 | },
23 | "response": {
24 | "code": 200,
25 | "body" : {}
26 | },
27 | "afterResponse": {
28 | "modifications": [
29 | {
30 | "request": {
31 | "url": "/posts/1",
32 | "method": "get"
33 | },
34 | "response": {
35 | "body": {
36 | "links": {
37 | "next": "/posts/2"
38 | }
39 | }
40 | }
41 | }
42 | ]
43 | }
44 | }
45 | ]
--------------------------------------------------------------------------------
/examples-command-line/ranged-delay.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/whattimeisit",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "delay": "200..3000",
10 | "body": {
11 | "theTime": "Adventure Time!",
12 | "starring": [
13 | "Finn",
14 | "Jake"
15 | ],
16 | "location": "ooo"
17 | }
18 | }
19 | }
20 | ]
--------------------------------------------------------------------------------
/examples-command-line/regexp-get.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": {
5 | "pattern" : "/what/the/.*",
6 | "regexp" : true
7 | },
8 | "method": "get"
9 | },
10 | "response": {
11 | "code": 200,
12 | "body": {
13 | "theTime": "Adventure Time!",
14 | "starring": [
15 | "Finn",
16 | "Jake"
17 | ],
18 | "location": "ooo"
19 | },
20 | "headers": {
21 | "X-Powered-By": "Interfake"
22 | }
23 | }
24 | }
25 | ]
--------------------------------------------------------------------------------
/examples-command-line/simple-get.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/whattimeisit",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "body": {
10 | "theTime": "Adventure Time!",
11 | "starring": [
12 | "Finn",
13 | "Jake"
14 | ],
15 | "location": "ooo"
16 | },
17 | "headers": {
18 | "X-Powered-By": "Interfake"
19 | }
20 | }
21 | }
22 | ]
--------------------------------------------------------------------------------
/examples-javascript/echo.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ debug: true });
5 | interfake.createRoute({
6 | request: {
7 | url: '/echo',
8 | method: 'post'
9 | },
10 | response: {
11 | code: 200,
12 | echo: true
13 | }
14 | });
15 |
16 | interfake.listen(3030); // The server will listen on port 3030
17 |
18 | request({ method : 'post', url : 'http://localhost:3030/echo', json : true, body : { echo : 'Echo!' } }, function (error, response, body) {
19 | console.log(response.statusCode); // prints 200
20 | console.log(body); // prints { "echo" : "Echo!" }
21 | });
22 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-crud.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake();
5 |
6 | // We start with an empty set of items
7 | interfake.get('/items').body({ items: [] });
8 |
9 | // When an item is created, update the items set request
10 | var postRequest = interfake.post('/items').status(201);
11 | postRequest.extends.get('/items').body({ items: [
12 | { id: 1, name: 'Item 1' }
13 | ]});
14 |
15 | // And also create an endpoint for our new item
16 | postRequest.creates.get('/items/1').body({ id: 1, name: 'Item 1' });
17 |
18 | // Also create a PUT request for our new item so we can edit it
19 | var putRequest = postRequest.creates.put('/items/1').body({ items: [
20 | { id: 1, name: 'Item One' }
21 | ]});
22 | var deleteRequest = postRequest.creates.delete('/items/1').status(204);
23 |
24 | // But when the put request is hit we need to edit both
25 | putRequest.extends.get('/items').body({ items: {
26 | 0 : { name: 'Item One' }
27 | }});
28 |
29 | // And also create an endpoint for our new item
30 | putRequest.extends.get('/items/1').body({ name: 'Item One' });
31 |
32 | deleteRequest.creates.get('/items/1').status(410);
33 |
34 | interfake.listen(3030); // The server will listen on port 3030
35 |
36 | function printStatusAndBody(error, response, body) {
37 | console.log(response.statusCode);
38 | console.log(body);
39 | }
40 |
41 | request('http://localhost:3030/items', function (error, response, body) {
42 | console.log('1. No items from GET /items.');
43 | printStatusAndBody.apply(null, arguments);
44 | request.post('http://localhost:3030/items', function () {
45 | console.log('2. Create an item with POST /items.');
46 | printStatusAndBody.apply(null, arguments);
47 | request.get('http://localhost:3030/items', function() {
48 | console.log('3. New item in GET /items.');
49 | printStatusAndBody.apply(null, arguments);
50 | request.get('http://localhost:3030/items/1', function () {
51 | console.log('4. New item at GET /items/1.');
52 | printStatusAndBody.apply(null, arguments);
53 | request.put('http://localhost:3030/items/1', function () {
54 | console.log('5. Update item with PUT /items/1.');
55 | printStatusAndBody.apply(null, arguments);
56 | request.get('http://localhost:3030/items/1', function () {
57 | console.log('6. Updated item at GET /items/1.');
58 | printStatusAndBody.apply(null, arguments);
59 | request.del('http://localhost:3030/items/1', function () {
60 | console.log('7. Deleted item at GET /items/1.');
61 | printStatusAndBody.apply(null, arguments);
62 | request.get('http://localhost:3030/items/1', function () {
63 | console.log('7. Can no longer request item at GET /items/1.');
64 | printStatusAndBody.apply(null, arguments);
65 | });
66 | });
67 | });
68 | });
69 | });
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-echo.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ debug: true });
5 | interfake.post('/echo').echo();
6 | interfake.listen(3030); // The server will listen on port 3030
7 |
8 | request({ method : 'post', url : 'http://localhost:3030/echo', json : true, body : { echo : 'Echo!' } }, function (error, response, body) {
9 | console.log(response.statusCode); // prints 200
10 | console.log(body); // prints { "echo" : "Echo!" }
11 | });
12 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-extends.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var interfake = new Interfake();
3 | interfake.get('/items').status(200).body({ items: [ { id: 1, name: 'Item 1' } ] });
4 | interfake.get('/items/1').status(200).body({ id: 1, name: 'Item 1' });
5 | var postResponse = interfake.post('/items').status(201).body({ created : true });
6 | postResponse.creates.get('/items/2').status(200).body({ id: 2, name: 'Item 2' });
7 | postResponse.extends.get('/items').status(200).body({ items: [ { id: 2, name: 'Item 2' } ] });
8 | interfake.listen(3000);
9 |
10 | /*
11 | # Request:
12 | $ curl http://localhost:3000/items -X GET
13 | # Response:
14 | 200
15 | {
16 | "items" : [
17 | {
18 | "id":1
19 | "name":"Item 1"
20 | }
21 | ]
22 | }
23 |
24 | # Request:
25 | $ curl http://localhost:3000/items -X POST
26 | # Response:
27 | 201
28 | {
29 | "created":true
30 | }
31 |
32 |
33 | # Request:
34 | $ curl http://localhost:3000/items -X GET
35 | # Response:
36 | 200
37 | {
38 | "items" : [
39 | {
40 | "id":1
41 | "name":"Item 1"
42 | },
43 | {
44 | "id":2
45 | "name":"Item 2"
46 | }
47 | ]
48 | }
49 | */
--------------------------------------------------------------------------------
/examples-javascript/fluent-proxy.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ debug: true });
5 | interfake.get('/interfake-tags').proxy({
6 | url: 'https://api.github.com/repos/basicallydan/interfake/tags',
7 | headers: {
8 | 'User-Agent': 'Interfake Proxy Whoop',
9 | 'Accept': 'application/vnd.github.full+json'
10 | }
11 | });
12 | interfake.listen(3030); // The server will listen on port 3030
13 |
14 | request('http://localhost:3030/interfake-tags', function (error, response, body) {
15 | console.log(error);
16 | console.log(response.statusCode); // prints 200
17 | console.log(body); // prints { "next" : "more stuff" }
18 | });
19 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-regex.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake();
5 | interfake.get(/\/*.?-time/).body({ time : 'It\'s something time!'});
6 | // If you give it something more specific it'll respect that (see below)
7 | interfake.get(/\/great-time/).body({ time : 'It\'s not regular expression time!'});
8 | interfake.listen(3030); // The server will listen on port 3030
9 |
10 | request('http://localhost:3030/adventure-time', function (error, response, body) {
11 | console.log(response.statusCode); // prints 200
12 | console.log(body); // prints { "time" : "It's something time!" }
13 | });
14 |
15 | request('http://localhost:3030/lunch-time', function (error, response, body) {
16 | console.log(response.statusCode); // prints 200
17 | console.log(body); // prints { "time" : "It's something time!" }
18 | });
19 |
20 | request('http://localhost:3030/whatever-time', function (error, response, body) {
21 | console.log(response.statusCode); // prints 200
22 | console.log(body); // prints { "time" : "It's something time!" }
23 | });
24 |
25 | request('http://localhost:3030/great-time', function (error, response, body) {
26 | console.log(response.statusCode); // prints 200
27 | console.log(body); // prints { "time" : "It's not regex time!" }
28 | });
29 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-simple.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ debug: true });
5 | interfake.get('/whats-next').body({ next : 'more stuff '});
6 | interfake.listen(3030); // The server will listen on port 3030
7 |
8 | request('http://localhost:3030/whats-next', function (error, response, body) {
9 | console.log(response.statusCode); // prints 200
10 | console.log(body); // prints { "next" : "more stuff" }
11 | });
12 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-web-page-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Interfake it 'til you make it
4 |
5 |
16 |
17 |
18 | Not updated!
19 | Update
20 |
21 |
--------------------------------------------------------------------------------
/examples-javascript/fluent-web-page-test.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var assert = require('assert');
3 | var path = require('path');
4 | var filePath = path.join(__dirname, './');
5 | console.log('Gonna open', filePath);
6 |
7 | var interfake = new Interfake();
8 | // var browser = new Browser();
9 |
10 | // Serve up this folder as a website so we can test it
11 | interfake.serveStatic('/static', filePath);
12 | // Create the /update endpoint
13 | interfake.get('/update').body({ text : 'Updated text!'});
14 | // Start the server
15 | interfake.listen(3000);
16 |
17 | // Zombie is annoyingly buggy and unreliable but if you can get it to work you can use this script below...
18 | // Use zombie to visit the page
19 | // browser.visit('http://localhost:3000/static/fluent-web-page-test.html')
20 | // .then(function() {
21 | // // When we start, the text of the #target element is 'Not updated!'
22 | // assert.equal(browser.text("#target"), 'Not updated!');
23 | // })
24 | // .then(function() {
25 | // // The 'Update' link will trigger an XHR call to /update and update the text with the response data
26 | // return browser.clickLink('Update');
27 | // })
28 | // .then(function () {
29 | // // Give it a sec...
30 | // return browser.wait(150);
31 | // })
32 | // .then(function () {
33 | // // Voila! It has updated. Magic.
34 | // assert.equal(browser.text('#target'), 'Updated text!');
35 | // })
36 | // .fail(function(error) {
37 | // console.log('Error', error);
38 | // browser.close();
39 | // })
40 | // .done(function() {
41 | // console.log('All asserts passed just fine!');
42 | // browser.close();
43 | // });
--------------------------------------------------------------------------------
/examples-javascript/load-file-reload-unload.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake();
5 | var file = interfake.loadFile(require.resolve('./load-file-reload-unload.json')); // We can get a reference to the file we just loaded
6 |
7 | interfake.listen(3030); // The server will listen on port 3030
8 |
9 | request('http://localhost:3030/whattimeisit', function(error, response, body) {
10 | console.log(response.statusCode); // prints 200
11 | console.log(body); // prints { "theTime" : "Adventure Time" }
12 | file = file.reload(); // The file will be reloaded, in case you changed it
13 |
14 | request('http://localhost:3030/whattimeisit', function(error, response, body) {
15 | console.log(response.statusCode); // prints 200
16 | console.log(body); // prints { "theTime" : "Adventure Time" }
17 | file = file.unload(); // The file can be unloaded, but this won't affect other routes
18 |
19 | request('http://localhost:3030/whattimeisit', function(error, response, body) {
20 | console.log(response.statusCode); // prints 404
21 | console.log(body); // prints blank line
22 | });
23 | });
24 | });
--------------------------------------------------------------------------------
/examples-javascript/load-file-reload-unload.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/whattimeisit",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "body": {
10 | "theTime": "Adventure Time!",
11 | "starring": [
12 | "Finn",
13 | "Jake"
14 | ],
15 | "location": "Ooo"
16 | },
17 | "headers": {
18 | "X-Powered-By": "Interfake"
19 | }
20 | }
21 | }
22 | ]
--------------------------------------------------------------------------------
/examples-javascript/load-file-watch.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ });
5 | var file = interfake.loadFile(require.resolve('./load-file-reload-unload.json'), { watch : true });
6 |
7 | interfake.listen(3000);
8 |
9 | console.log('Try changing the file load-file-reload-unload.json and then make reuqests to it, at http://localhost:3000/whattimeisit');
--------------------------------------------------------------------------------
/examples-javascript/regexp-url.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request');
3 |
4 | // Example showing how a regular expression can be used for the URL without using a RegExp type (e.g. you're specifying the endpoints in JSON)
5 |
6 | var interfake = new Interfake({ debug: true });
7 | interfake.createRoute({
8 | request: {
9 | url: {
10 | pattern: '/what/the/.*',
11 | regexp: true
12 | },
13 | method: 'get'
14 | },
15 | response: {
16 | code: 200,
17 | body: {
18 | next:'more stuff'
19 | }
20 | }
21 | });
22 |
23 | interfake.listen(3030); // The server will listen on port 3030
24 |
25 | request('http://localhost:3030/what/the/heck', function (error, response, body) {
26 | console.log(response.statusCode); // prints 200
27 | console.log(body); // prints { "next" : "more stuff" }
28 | });
29 |
30 | request('http://localhost:3030/what/the/fuzz', function (error, response, body) {
31 | console.log(response.statusCode); // prints 200
32 | console.log(body); // prints { "next" : "more stuff" }
33 | });
34 |
35 | request('http://localhost:3030/what/the/what', function (error, response, body) {
36 | console.log(response.statusCode); // prints 200
37 | console.log(body); // prints { "next" : "more stuff" }
38 | });
39 |
--------------------------------------------------------------------------------
/examples-javascript/simple.js:
--------------------------------------------------------------------------------
1 | var Interfake = require('..');
2 | var request = require('request'); // Let's use request for this example
3 |
4 | var interfake = new Interfake({ debug: true });
5 | interfake.createRoute({
6 | request: {
7 | url: '/whats-next',
8 | method: 'get'
9 | },
10 | response: {
11 | code: 200,
12 | body: {
13 | next:'more stuff'
14 | }
15 | }
16 | });
17 |
18 | interfake.listen(3030); // The server will listen on port 3030
19 |
20 | request('http://localhost:3030/whats-next', function (error, response, body) {
21 | console.log(response.statusCode); // prints 200
22 | console.log(body); // prints { "next" : "more stuff" }
23 | });
24 |
--------------------------------------------------------------------------------
/examples-javascript/testwatch.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | var onChange = function () {
4 | console.log('Changed');
5 | fs.watch(require.resolve('./load-file-reload-unload.json'), onChange);
6 | };
7 |
8 | fs.watch(require.resolve('./load-file-reload-unload.json'), onChange);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | var program = require('commander');
4 | var Interfake = require('./lib/server');
5 | var packageInfo = require('./package.json');
6 | program
7 | .version(packageInfo.version)
8 | .option('-f, --file [file]', 'Load an API from a JSON file [file]')
9 | .option('-p --port [port]', 'Specify a port for Interfake to listen on', 3000)
10 | .option('-d --debug', 'Debug mode, turns on console messages')
11 | .option('-w --watch', 'If a loaded file changes, it will be re-loaded')
12 | .parse(process.argv);
13 |
14 | // console.log(program.args[0]);
15 |
16 | var opts = {
17 | debug: false
18 | };
19 |
20 | if (program.debug) {
21 | opts.debug = true;
22 | }
23 |
24 | if (program.watch) {
25 | opts.watch = true;
26 | }
27 |
28 | var interfake = new Interfake(opts);
29 |
30 | if (program.file) {
31 | interfake.loadFile(program.file, opts);
32 | }
33 |
34 | interfake.listen(program.port);
--------------------------------------------------------------------------------
/lib/cors.js:
--------------------------------------------------------------------------------
1 | module.exports = function (req, res, next) {
2 | res.header('Access-Control-Allow-Origin', '*');
3 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
4 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
5 |
6 | next();
7 | };
8 |
--------------------------------------------------------------------------------
/lib/debug.js:
--------------------------------------------------------------------------------
1 | function Deformic(name, enabled) {
2 | this.enabled = enabled || false;
3 | this.log = function() {
4 | if (this.enabled) {
5 | var args = Array.prototype.concat.call([ '(' + name + ')' ], Array.prototype.slice.call(arguments));
6 | console.log.apply(console, args);
7 | }
8 | }.bind(this);
9 | this.log.enable = function () {
10 | console.log('Debugger enabled for', name);
11 | this.enabled = true;
12 | }.bind(this);
13 | return this.log;
14 | }
15 |
16 | function debug(name, enabled) {
17 | return new Deformic(name, enabled);
18 | }
19 |
20 | module.exports = debug;
--------------------------------------------------------------------------------
/lib/fluent.js:
--------------------------------------------------------------------------------
1 | function FluentInterface(server, o) {
2 | o = o || { debug: false };
3 | var debug = require('./debug')('interfake-fluent', o.debug);
4 |
5 | function throwCreatesNotSupportedError () {
6 | throw new Error('Sorry, but modified routes cannot yet create new routes after their response. This is planned for a future version of Interfake.');
7 | }
8 |
9 | function throwExtendsNotSupportedError () {
10 | throw new Error('Sorry, but modified routes cannot yet modify existing routes after their response. This is planned for a future version of Interfake.');
11 | }
12 |
13 | function fluentModify(method, route) {
14 | return function (path) {
15 | var modifyDescriptor = {
16 | routeDescriptor: {
17 | method: method,
18 | url: path
19 | }
20 | };
21 |
22 | route.addModification(modifyDescriptor);
23 |
24 | return {
25 | query: function (query) {
26 | modifyDescriptor.routeDescriptor.query = query;
27 | return this;
28 | },
29 | status: function (status) {
30 | modifyDescriptor.code = status;
31 | return this;
32 | },
33 | body: function (body) {
34 | modifyDescriptor.body = body;
35 | return this;
36 | },
37 | echo: function (echo) {
38 | if (typeof echo === 'undefined') {
39 | echo = true;
40 | }
41 | modifyDescriptor.echo = echo;
42 | return this;
43 | },
44 | delay: function (delay) {
45 | modifyDescriptor.delay = delay;
46 | return this;
47 | },
48 | responseHeaders: function (headers) {
49 | modifyDescriptor.responseHeaders = headers;
50 | return this;
51 | },
52 | creates: {
53 | get: throwCreatesNotSupportedError,
54 | put: throwCreatesNotSupportedError,
55 | post: throwCreatesNotSupportedError,
56 | patch: throwCreatesNotSupportedError,
57 | delete: throwCreatesNotSupportedError
58 | },
59 | extends: {
60 | get: throwExtendsNotSupportedError,
61 | put: throwExtendsNotSupportedError,
62 | post: throwExtendsNotSupportedError,
63 | patch: throwCreatesNotSupportedError,
64 | delete: throwExtendsNotSupportedError
65 | }
66 | };
67 | };
68 | }
69 |
70 | function fluentCreate(method, parent) {
71 | return function (originalPath) {
72 | var route;
73 | var routeDescriptor = {
74 | request: {
75 | url: originalPath,
76 | method: method
77 | }
78 | };
79 |
80 | if (!parent) {
81 | debug('Fluent setup called for', routeDescriptor.request.url);
82 | route = server.createRoute(routeDescriptor);
83 | } else {
84 | debug('Fluent setup called for', routeDescriptor.request.url, 'with parent', parent.request.url);
85 | route = parent.addAfterResponse(routeDescriptor);
86 | }
87 |
88 | var fluentInterface = {
89 | query: function (query) {
90 | route.setQueryStrings(query);
91 | return this;
92 | },
93 | status: function (status) {
94 | route.setStatusCode(status);
95 | return this;
96 | },
97 | body: function (body) {
98 | debug('Changing body of', route.simpleHash(), 'to', body);
99 | route.setResponseBody(body);
100 | return this;
101 | },
102 | echo: function (echo) {
103 | if (typeof echo === 'undefined') {
104 | echo = true;
105 | }
106 | route.setEcho(echo);
107 | return this;
108 | },
109 | proxy: function (url) {
110 | route.setProxyURL(url);
111 | return this;
112 | },
113 | delay: function (delay) {
114 | route.setResponseDelay(delay);
115 | return this;
116 | },
117 | responseHeaders: function (headers) {
118 | route.setResponseHeaders(headers);
119 | return this;
120 | },
121 | creates: {
122 | get: fluentCreate('get', route),
123 | put: fluentCreate('put', route),
124 | post: fluentCreate('post', route),
125 | patch: fluentCreate('patch', route),
126 | delete: fluentCreate('delete', route),
127 | },
128 | extends: {
129 | get: fluentModify('get', route),
130 | put: fluentModify('put', route),
131 | post: fluentModify('post', route),
132 | patch: fluentModify('patch', route),
133 | delete: fluentModify('delete', route),
134 | }
135 | };
136 | return fluentInterface;
137 | };
138 | }
139 |
140 | this.fluentCreate = fluentCreate;
141 | }
142 |
143 | module.exports = FluentInterface;
144 |
--------------------------------------------------------------------------------
/lib/grammar.pegjs:
--------------------------------------------------------------------------------
1 | start
2 | = endpoint +
3 |
4 | endpoint
5 | = m:method " " p:path " " s:status c:(" creates " e:endpoint + { return e; }) ? { return { method: m, path: p, status: s, creates: c } }
6 |
7 | method
8 | = $ "get" / "put" / "post" / "del"
9 |
10 | path
11 | = path:([/a-z] +) { return path.join(''); }
12 |
13 | status
14 | = status:[0-9]+ { return parseInt(status.join(""), 10); }
--------------------------------------------------------------------------------
/lib/route.js:
--------------------------------------------------------------------------------
1 | var util = require('core-util-is');
2 | var url = require('url');
3 | var merge = require('merge');
4 | var deepmerge = require('deepmerge');
5 |
6 | function Route(descriptor, o) {
7 | var path;
8 | o = o || { debug: false };
9 | this.debug = require('./debug')('interfake-route', o.debug);
10 | this.request = descriptor.request;
11 | this.response = descriptor.response;
12 | this.afterResponse = descriptor.afterResponse;
13 | this.o = o;
14 |
15 | if (!this.response) {
16 | this.response = {
17 | delay: 0,
18 | code: 200,
19 | body: {},
20 | echo: false,
21 | headers: {}
22 | };
23 | } else {
24 | this.response = merge({
25 | delay: 0,
26 | code: 200,
27 | body: {},
28 | echo: false,
29 | headers: {}
30 | }, this.response);
31 | }
32 |
33 | this.response.query = {};
34 |
35 | if (!util.isRegExp(this.request.url) && util.isObject(this.request.url) && this.request.url.regexp) {
36 | this.debug('RegExp specified for URL', this.request.url.pattern);
37 | this.request.url = new RegExp(this.request.url.pattern);
38 | }
39 |
40 | if (util.isString(this.request.url)) {
41 | path = url.parse(this.request.url, true);
42 | this.request.url = path.pathname;
43 | this.setQueryStrings(path.query);
44 | }
45 |
46 | if (this.afterResponse && util.isArray(this.afterResponse.endpoints)) {
47 | this.afterResponse.endpoints = (this.afterResponse.endpoints || []).map(function (descriptor) {
48 | return new Route(descriptor);
49 | });
50 | }
51 |
52 | if (this.afterResponse && util.isArray(this.afterResponse.modifications)) {
53 | this.afterResponse.modifications = (this.afterResponse.modifications || []).map(function (descriptor) {
54 | return {
55 | routeDescriptor: descriptor.request,
56 | body: descriptor.response.body,
57 | echo: descriptor.response.echo,
58 | code: descriptor.response.code,
59 | delay: descriptor.response.delay,
60 | responseHeaders: descriptor.response.headers
61 | };
62 | }.bind(this));
63 | }
64 |
65 | if (!this.afterResponse) {
66 | this.afterResponse = {
67 | endpoints: [],
68 | modifications: []
69 | };
70 | }
71 |
72 | if (!this.afterResponse.endpoints) {
73 | this.afterResponse.endpoints = [];
74 | }
75 |
76 | if (!this.afterResponse.modifications) {
77 | this.afterResponse.modifications = [];
78 | }
79 |
80 |
81 | this.debug('Creating route', this);
82 | this.debug('Creating route: ' + this.request.method + ' ' + this.request.url + ' to return ' + this.response.code + ' with a body of length ' + JSON.stringify(this.response.body).length + ' and ' + this.afterResponse.endpoints.length + ' after-responses');
83 | }
84 |
85 | Route.prototype.setQueryStrings = function (query) {
86 | this.request.query = merge(this.request.query, query || {});
87 | };
88 |
89 | Route.prototype.setStatusCode = function (statusCode) {
90 | this.debug('Now returning', statusCode);
91 | this.response.code = statusCode;
92 | };
93 |
94 | Route.prototype.setResponseBody = function (body) {
95 | this.debug('Now returning body of length', JSON.stringify(body).length);
96 | this.response.body = body;
97 | };
98 |
99 | Route.prototype.setProxyURL = function (url) {
100 | this.debug('The route is now a proxy of', this.request.method, url);
101 | this.response.proxy = url;
102 | };
103 |
104 | Route.prototype.mergeResponseBody = function (body) {
105 | this.debug('Merging', body, 'into', this.response.body);
106 | this.response.body = deepmerge(this.response.body, body, { alwaysPush: true });
107 | this.debug('Response body is now', this.response.body);
108 | };
109 |
110 | Route.prototype.mergeResponseHeaders = function (headers) {
111 | this.debug('Merging', headers, 'into', this.response.headers);
112 | this.response.headers = deepmerge(this.response.headers, headers);
113 | this.debug('Response headers is now', this.response.headers);
114 | };
115 |
116 | Route.prototype.setResponseDelay = function (delay) {
117 | this.debug('New delay is', delay);
118 | this.response.delay = delay;
119 | };
120 |
121 | Route.prototype.setResponseHeaders = function (headers) {
122 | this.debug('Setting response headers to', headers);
123 | this.response.headers = headers;
124 | };
125 |
126 | Route.prototype.setEcho = function (echo) {
127 | this.response.echo = echo;
128 | };
129 |
130 | Route.prototype.addAfterResponse = function (descriptors) {
131 | var newRoute = new Route(descriptors, this.o);
132 | this.debug('Adding after response', newRoute.hash());
133 | this.afterResponse.endpoints.push(newRoute);
134 | return newRoute;
135 | };
136 |
137 | Route.prototype.addModification = function (modifyDescriptor) {
138 | if (!this.afterResponse.modifications) {
139 | this.afterResponse.modifications = [];
140 | }
141 |
142 | this.afterResponse.modifications.push(modifyDescriptor);
143 | };
144 |
145 | Route.prototype.creates = function (routeDescriptor) {
146 | var newRoute = new Route(routeDescriptor, this.o);
147 | if (!this.afterResponse) {
148 | this.afterResponse = {};
149 | }
150 | if (!this.afterResponse.endpoints) {
151 | this.afterResponse.endpoints = [];
152 | }
153 | this.afterResponse.endpoints.push(newRoute);
154 | return newRoute;
155 | };
156 |
157 | Route.prototype.hasRegexpURL = function () {
158 | return util.isRegExp(this.request.url);
159 | };
160 |
161 | Route.prototype.simpleHash = function () {
162 | var routeHash = this.request.method.toUpperCase() + ' ' + this.request.url;
163 | return routeHash;
164 | };
165 |
166 | Route.prototype.queryKeys = function () {
167 | var queryKeys = Object.keys(this.request.query || {}).filter(function (key) {
168 | return key !== 'callback';
169 | });
170 | return queryKeys;
171 | };
172 |
173 | Route.prototype.hash = function () {
174 | var routeHash;
175 | if (util.isString(this.request.url)) {
176 | routeHash = this.request.method.toUpperCase() + ' ' + this.request.url;
177 | } else {
178 | routeHash = new RegExp(this.request.method.toUpperCase() + ' ' + this.request.url);
179 | }
180 | var fullQuerystring, querystringArray = [];
181 | if (this.request.query) {
182 | var queryKeys = this.queryKeys();
183 |
184 | if (queryKeys.length) {
185 | this.debug('Query keys are', queryKeys);
186 |
187 | fullQuerystring = this.request.query;
188 | delete fullQuerystring.callback;
189 |
190 | queryKeys.sort().forEach(function (k) {
191 | querystringArray.push(k + '=' + fullQuerystring[k]);
192 | });
193 |
194 | this.debug('Full query string items are', querystringArray);
195 |
196 | routeHash += '?' + querystringArray.join('&');
197 | this.debug('Final route is', routeHash);
198 | }
199 | }
200 |
201 | this.debug('Lookup hash key will be: ' + routeHash);
202 | return routeHash;
203 | };
204 |
205 | Route.prototype.compare = function (route) {
206 | var queryKeys;
207 | var routeQueryKeys;
208 | var same = true;
209 |
210 | if (this.hasRegexpURL()) {
211 | this.debug('1. Testing', route.simpleHash(), 'matches', this.simpleHash());
212 | return this.request.url.test(route.request.url) && this.request.method.toLowerCase() === route.request.method.toLowerCase();
213 | } else if (route.simpleHash() !== this.simpleHash()) {
214 | this.debug('1. Comparing', route.simpleHash(), 'and', this.simpleHash());
215 | this.debug('No match');
216 | return false;
217 | }
218 |
219 | this.debug('2. Testing', route.request.url, 'matches', this.request.url);
220 | if (util.isRegExp(this.request.url)) {
221 | if (!this.request.url.test(route.request.url)) {
222 | this.debug('No match.');
223 | return false;
224 | }
225 | }
226 |
227 | queryKeys = this.queryKeys();
228 | routeQueryKeys = route.queryKeys();
229 |
230 | this.debug('3. Comparing', queryKeys.length, 'and', routeQueryKeys.length);
231 | if (queryKeys.length !== routeQueryKeys.length) {
232 | this.debug('No match');
233 | return false;
234 | }
235 |
236 | this.debug('4. Comparing', this.request.query, 'and', route.request.query);
237 | queryKeys.forEach(function (key) {
238 | var routeQueryValue = route.request.query[key];
239 | if (util.isRegExp(this.request.query[key])) {
240 | this.debug('4b. Testing', routeQueryValue, 'matches', this.request.query[key]);
241 | same = same && this.request.query[key].test(routeQueryValue);
242 | this.debug('4c. Result is', same);
243 | } else if (util.isArray(this.request.query[key])) {
244 | this.debug('4b. Testing', routeQueryValue, 'matches', this.request.query[key]);
245 | same = same && JSON.stringify(this.request.query[key].sort()) === JSON.stringify(routeQueryValue.sort());
246 | this.debug('4c. Result is', same);
247 | } else {
248 | // Not checking type because 1 and '1' are the same in this case
249 | this.debug('4b. Comparing', this.request.query[key], 'and', routeQueryValue);
250 | same = same && this.request.query[key] == routeQueryValue;
251 | this.debug('4c. Result is', same);
252 | }
253 | }.bind(this));
254 |
255 | return same;
256 | };
257 |
258 | module.exports = Route;
259 |
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var serveStatic = require('serve-static');
3 | var path = require('path');
4 | var FluentInterface = require('./fluent');
5 | var corsMiddleware = require('./cors');
6 | var util = require('core-util-is');
7 | var filter = require('lodash/filter');
8 | var each = require('lodash/each');
9 | var last = require('lodash/last');
10 | var url = require('url');
11 | var connectJson = require('connect-json');
12 | var bodyParser = require('body-parser');
13 | var Route = require('./route');
14 | var watchr = require('watchr');
15 | var fs = require('fs');
16 | var request = require('request');
17 | var supportedNonOptionsMethods = ['get','post','put','patch','delete'];
18 |
19 | function determineDelay(delayInput) {
20 | var result = 0,
21 | range, upper, lower;
22 | if (util.isNumber(delayInput)) {
23 | result = delayInput;
24 | } else if (util.isString(delayInput)) {
25 | range = /([0-9]+)..([0-9]+)/.exec(delayInput);
26 | upper = +range[2];
27 | lower = +range[1];
28 | result = Math.floor(Math.random() * (upper - lower + 1) + lower);
29 | }
30 |
31 | return result;
32 | }
33 |
34 | function Interfake(o) {
35 | o = o || {
36 | debug: false
37 | };
38 | this.o = o;
39 | var app = express();
40 | var router = express.Router();
41 | var fluentInterface = new FluentInterface(this, o);
42 | var debug = require('./debug')('interfake-server', o.debug);
43 | var expectationsLookup = {};
44 | var server;
45 |
46 | o.path = url.resolve('/', o.path || '');
47 |
48 | debug('Root path is', o.path);
49 |
50 | app.use(connectJson());
51 | app.use(bodyParser.urlencoded({
52 | extended: true
53 | }));
54 | app.use(bodyParser.json());
55 | app.use(corsMiddleware);
56 |
57 | app.post('/_requests?', function(req, res) {
58 | try {
59 | debug('Being hit with a _request request');
60 | createRoute(req.body);
61 | debug('Returning');
62 | res.status(201).send({
63 | done: true
64 | });
65 | } catch (e) {
66 | debug('Error: ', e);
67 | res.status(400).send(e);
68 | }
69 | });
70 |
71 | app.all(o.path + '*', function(req, res, next) {
72 | debug(req.method, 'request to', req.url);
73 | if (o.onRequest && typeof(o.onRequest) === 'function') {
74 | o.onRequest(req);
75 | }
76 | var hashData = {
77 | method: req.method,
78 | url: req.path,
79 | query: req.query
80 | };
81 | var rootPathRegex, responseBody, responseStatusCode, responseHeaders, expectedRoute, expectedRoutes;
82 | var proxyQueryStrings;
83 | var sendResponse = function () {
84 | var responseBodyLength = 0;
85 | if (responseBody) {
86 | responseBodyLength = JSON.stringify(responseBody).length;
87 | responseBody = JSON.parse(JSON.stringify(responseBody));
88 | }
89 | debug('Sending a', responseStatusCode, 'response with a body whose length is', responseBodyLength);
90 | if (responseHeaders) {
91 | Object.keys(responseHeaders).forEach(function(k) {
92 | res.setHeader(k, responseHeaders[k]);
93 | });
94 | }
95 |
96 | if (req.query.callback) {
97 | debug('Request is asking for jsonp');
98 | if (typeof responseBody !== 'string') responseBody = JSON.stringify(responseBody);
99 | responseBody = req.query.callback.trim() + '(' + responseBody + ');';
100 | }
101 |
102 | debug('Sending response now');
103 |
104 | res.status(responseStatusCode).send(responseBody);
105 | };
106 |
107 | if (o.path.length > 1) {
108 | rootPathRegex = new RegExp('^' + o.path);
109 | if (!rootPathRegex.test(hashData.url)) {
110 | return res.send(404);
111 | }
112 | hashData.url = hashData.url.replace(rootPathRegex, '');
113 | }
114 |
115 | if (req.method.toLowerCase() === 'options') {
116 | expectedRoutes = this.routesMatchingPartial({
117 | url : hashData.url,
118 | query : hashData.query
119 | });
120 | if (!expectedRoutes.length) {
121 | debug('Couldn\'t find any routes for', hashData.url);
122 | return next();
123 | }
124 |
125 | responseStatusCode = 200;
126 | responseHeaders = {
127 | 'Access-Control-Allow-Methods' : expectedRoutes.map(function (route) {
128 | return route.request.method.toUpperCase();
129 | }).join(', ') + ', OPTIONS'
130 | };
131 |
132 | debug('Response headers are now', responseHeaders);
133 |
134 | return sendResponse();
135 | }
136 |
137 | expectedRoute = this.routeMatching(hashData);
138 |
139 | if (!expectedRoute) {
140 | debug('Couldn\'t find a route for', hashData.method, hashData.url);
141 | return next();
142 | // return res.status(404).end();
143 | }
144 |
145 | var specifiedResponse = expectedRoute.response; // req.route.responseData;
146 | var afterSpecifiedResponse = expectedRoute.afterResponse; //req.route.afterResponseData;
147 | var responseDelay = determineDelay(specifiedResponse.delay);
148 |
149 | if (specifiedResponse.proxy) {
150 | proxyQueryStrings = {};
151 | if (req.query) {
152 | Object.keys(req.query).forEach(function(key) {
153 | if (key !== 'callback') {
154 | proxyQueryStrings[key] = req.query[key];
155 | }
156 | });
157 | }
158 | debug('Proxy query strings are', proxyQueryStrings);
159 | request[req.method.toLowerCase()]({ url : specifiedResponse.proxy.url || specifiedResponse.proxy, json : true, body : req.body, qs: proxyQueryStrings, headers : specifiedResponse.proxy.headers || {} }, function (error, response, body) {
160 | debug('Response from ', specifiedResponse.proxy, 'is', body, 'with headers', response.headers);
161 | responseBody = body;
162 | responseStatusCode = response.statusCode;
163 | responseHeaders = response.headers;
164 | delete responseHeaders['content-length'];
165 | sendResponse();
166 | });
167 | } else {
168 | responseBody = specifiedResponse.body;
169 | responseStatusCode = specifiedResponse.code;
170 | responseHeaders = specifiedResponse.headers;
171 |
172 | debug('specifiedResponse is', specifiedResponse);
173 |
174 | if (specifiedResponse.echo) {
175 | debug('Echo! Gonna return', req.body);
176 | responseBody = req.body;
177 | }
178 |
179 | debug(req.method, 'request to', req.url, 'returning', responseStatusCode);
180 | debug(req.method, 'request to', req.url, 'will be delayed by', responseDelay, 'millis');
181 | debug('Expected route is', expectedRoute);
182 |
183 | res.setHeader('Content-Type', 'application/json');
184 |
185 | setTimeout(function() {
186 | var modification;
187 | debug('Expected response is', specifiedResponse);
188 |
189 | sendResponse();
190 |
191 | if (afterSpecifiedResponse && afterSpecifiedResponse.endpoints) {
192 | debug('Response sent, setting up', afterSpecifiedResponse.endpoints.length, 'endpoints');
193 | afterSpecifiedResponse.endpoints.forEach(function(route) {
194 | debug('Setting up route', route.hash());
195 | addRoute(route);
196 | });
197 | }
198 |
199 | if (afterSpecifiedResponse && afterSpecifiedResponse.modifications) {
200 | debug('Response sent, making', afterSpecifiedResponse.modifications.length, 'modifications');
201 | while (afterSpecifiedResponse.modifications.length > 0) {
202 | modification = afterSpecifiedResponse.modifications.pop();
203 |
204 | modification.route = this.routeMatching(modification.routeDescriptor);
205 |
206 | if (!modification.route) {
207 | throw new Error('No route matching', modification.routeDescriptor, 'was found');
208 | }
209 |
210 | if (modification.body) {
211 | debug('Modifying body');
212 | modification.route.mergeResponseBody(modification.body);
213 | }
214 |
215 | if (!util.isNullOrUndefined(modification.echo)) {
216 | debug('Modifying echo to', modification.echo);
217 | modification.route.setEcho(modification.echo);
218 | }
219 |
220 | if (modification.code) {
221 | debug('Modifying code to', modification.code);
222 | modification.route.setStatusCode(modification.code);
223 | }
224 |
225 | if (modification.delay) {
226 | debug('Modifying delay to', modification.delay);
227 | modification.route.setResponseDelay(modification.delay);
228 | }
229 |
230 | if (modification.responseHeaders) {
231 | debug('Modifying response headers');
232 | modification.route.mergeResponseHeaders(modification.responseHeaders);
233 | }
234 | }
235 | }
236 | }.bind(this), responseDelay);
237 | }
238 | }.bind(this));
239 |
240 | function addRoute(route) {
241 | if (expectationsLookup[route.simpleHash()]) {
242 | expectationsLookup[route.simpleHash()].push(route);
243 | } else {
244 | expectationsLookup[route.simpleHash()] = [route];
245 | }
246 | }
247 |
248 | function createRoute(data) {
249 | var newRoute;
250 |
251 | debug('Setting up new route');
252 |
253 | newRoute = new Route(data, o);
254 |
255 | addRoute(newRoute);
256 |
257 | debug('Setup complete');
258 |
259 | return newRoute;
260 | }
261 |
262 | function removeRoute(route) {
263 | var indexInLookup;
264 |
265 | if (!route.simpleHash) {
266 | route = new Route(route, o);
267 | }
268 |
269 | if (expectationsLookup[route.simpleHash()]) {
270 | indexInLookup = expectationsLookup[route.simpleHash()].indexOf(route);
271 | expectationsLookup[route.simpleHash()].splice(indexInLookup, 1);
272 | } else {
273 | debug('No route could be found with the hash', route.simpleHash(), 'so no route was removed');
274 | }
275 | }
276 |
277 | this.createRoute = createRoute;
278 | this.removeRoute = removeRoute;
279 |
280 | this.get = fluentInterface.fluentCreate('get');
281 | this.post = fluentInterface.fluentCreate('post');
282 | this.put = fluentInterface.fluentCreate('put');
283 | this.patch = fluentInterface.fluentCreate('patch');
284 | this.delete = fluentInterface.fluentCreate('delete');
285 | this.options = fluentInterface.fluentCreate('options');
286 |
287 | this.expressApp = app;
288 |
289 | this.serveStatic = function(urlPath, directory) {
290 | urlPath = urlPath || '/_static';
291 | var directoryToUse = path.resolve(process.cwd(), directory);
292 | debug('Serving to', urlPath, 'with directory', directoryToUse);
293 | app.use(urlPath, express.static(directoryToUse + ''));
294 | };
295 |
296 | // Returns an array of routes matching the descriptor where the method is blank
297 | this.routesMatchingPartial = function(partialRequestDescriptor) {
298 | var routes = [];
299 | supportedNonOptionsMethods.forEach(function (method) {
300 | var fullDescriptor = {
301 | url : partialRequestDescriptor.url,
302 | method : method,
303 | query : partialRequestDescriptor.query
304 | };
305 | var route = this.routeMatching(fullDescriptor);
306 | if (route) {
307 | routes.push(route);
308 | }
309 | }.bind(this));
310 | return routes;
311 | };
312 |
313 | this.routeMatching = function(requestDescriptor) {
314 | var incomingRoute = new Route({
315 | request: requestDescriptor
316 | }, o);
317 |
318 | var expectData = expectationsLookup[incomingRoute.simpleHash()];
319 |
320 | if (util.isArray(expectData)) {
321 | expectData = expectData.filter(function(route) {
322 | return route.compare(incomingRoute);
323 | });
324 | } else if (!expectData) {
325 | // Nothing matched, but there may be a regex in there
326 | expectData = last(filter(expectationsLookup, function (routes) {
327 | var route = last(routes);
328 | return route.compare(incomingRoute);
329 | })) || [];
330 | }
331 |
332 | if (expectData.length) {
333 | return last(expectData);
334 | }
335 |
336 | return;
337 | };
338 |
339 | this.listen = function(port, callback) {
340 | port = port || 3000;
341 | server = app.listen(port, function() {
342 | debug('Interfake is listening for requests on port', server.address().port);
343 | if (util.isFunction(callback)) {
344 | callback();
345 | }
346 | });
347 | };
348 |
349 | this.stop = function() {
350 | if (server) {
351 | debug('Interfake is stopping');
352 | server.close(function() {
353 | debug('Interfake has stopped');
354 | server = undefined;
355 | });
356 | }
357 | };
358 |
359 | this.clearAllRoutes = function() {
360 | expectationsLookup = {};
361 | };
362 | }
363 |
364 | Interfake.prototype.loadFile = function(filePath, opts) {
365 | var file;
366 | var debug = require('./debug')('interfake-server-load-file', this.o.debug);
367 |
368 | filePath = path.resolve(process.cwd(), filePath);
369 |
370 | delete require.cache[require.resolve(filePath)];
371 | file = require(filePath);
372 |
373 | file.forEach(function(endpoint) {
374 | this.createRoute(endpoint);
375 | }.bind(this));
376 |
377 | var unload = function() {
378 | file.forEach(function(endpoint) {
379 | this.removeRoute(endpoint);
380 | }.bind(this));
381 | }.bind(this);
382 |
383 | var reload = function() {
384 | unload();
385 | return this.loadFile(filePath, opts);
386 | }.bind(this);
387 |
388 | var onFileChange = function (type) {
389 | reload();
390 | if (opts && opts.watch) {
391 | debug('Watching for changes at', filePath);
392 | fs.watch(filePath, onFileChange);
393 | }
394 | };
395 |
396 | if (opts && opts.watch) {
397 | debug('Watching for changes at', filePath);
398 | fs.watch(filePath, onFileChange);
399 | }
400 |
401 | return {
402 | unload: unload,
403 | reload: reload
404 | };
405 | };
406 |
407 | module.exports = Interfake;
--------------------------------------------------------------------------------
/npm-shrinkwrap.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interfake",
3 | "version": "1.20.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "accepts": {
8 | "version": "1.3.7",
9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
11 | "requires": {
12 | "mime-types": "~2.1.24",
13 | "negotiator": "0.6.2"
14 | }
15 | },
16 | "ajv": {
17 | "version": "6.10.2",
18 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
19 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
20 | "dev": true,
21 | "requires": {
22 | "fast-deep-equal": "^2.0.1",
23 | "fast-json-stable-stringify": "^2.0.0",
24 | "json-schema-traverse": "^0.4.1",
25 | "uri-js": "^4.2.2"
26 | }
27 | },
28 | "ambi": {
29 | "version": "2.5.0",
30 | "resolved": "https://registry.npmjs.org/ambi/-/ambi-2.5.0.tgz",
31 | "integrity": "sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA=",
32 | "requires": {
33 | "editions": "^1.1.1",
34 | "typechecker": "^4.3.0"
35 | }
36 | },
37 | "api-easy": {
38 | "version": "0.4.0",
39 | "resolved": "https://registry.npmjs.org/api-easy/-/api-easy-0.4.0.tgz",
40 | "integrity": "sha1-jKLLH/dtxnkW6DUH2qbFCVnnFu8=",
41 | "dev": true,
42 | "requires": {
43 | "qs": "~2.3.3",
44 | "request": "2.x.x",
45 | "vows": "0.8.x"
46 | },
47 | "dependencies": {
48 | "qs": {
49 | "version": "2.3.3",
50 | "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz",
51 | "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=",
52 | "dev": true
53 | }
54 | }
55 | },
56 | "array-flatten": {
57 | "version": "1.1.1",
58 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
59 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
60 | },
61 | "asn1": {
62 | "version": "0.2.4",
63 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
64 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
65 | "dev": true,
66 | "requires": {
67 | "safer-buffer": "~2.1.0"
68 | }
69 | },
70 | "assert-plus": {
71 | "version": "1.0.0",
72 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
73 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
74 | "dev": true
75 | },
76 | "asynckit": {
77 | "version": "0.4.0",
78 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
79 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
80 | "dev": true
81 | },
82 | "aws-sign2": {
83 | "version": "0.7.0",
84 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
85 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
86 | "dev": true
87 | },
88 | "aws4": {
89 | "version": "1.8.0",
90 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
91 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
92 | "dev": true
93 | },
94 | "balanced-match": {
95 | "version": "1.0.0",
96 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
97 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
98 | "dev": true
99 | },
100 | "bcrypt-pbkdf": {
101 | "version": "1.0.2",
102 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
103 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
104 | "dev": true,
105 | "requires": {
106 | "tweetnacl": "^0.14.3"
107 | }
108 | },
109 | "body-parser": {
110 | "version": "1.19.0",
111 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
112 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
113 | "requires": {
114 | "bytes": "3.1.0",
115 | "content-type": "~1.0.4",
116 | "debug": "2.6.9",
117 | "depd": "~1.1.2",
118 | "http-errors": "1.7.2",
119 | "iconv-lite": "0.4.24",
120 | "on-finished": "~2.3.0",
121 | "qs": "6.7.0",
122 | "raw-body": "2.4.0",
123 | "type-is": "~1.6.17"
124 | }
125 | },
126 | "brace-expansion": {
127 | "version": "1.1.11",
128 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
129 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
130 | "dev": true,
131 | "requires": {
132 | "balanced-match": "^1.0.0",
133 | "concat-map": "0.0.1"
134 | }
135 | },
136 | "browser-stdout": {
137 | "version": "1.3.1",
138 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
139 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
140 | "dev": true
141 | },
142 | "bytes": {
143 | "version": "3.1.0",
144 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
145 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
146 | },
147 | "caseless": {
148 | "version": "0.12.0",
149 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
150 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
151 | "dev": true
152 | },
153 | "combined-stream": {
154 | "version": "1.0.8",
155 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
156 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
157 | "dev": true,
158 | "requires": {
159 | "delayed-stream": "~1.0.0"
160 | }
161 | },
162 | "commander": {
163 | "version": "2.3.0",
164 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz",
165 | "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM="
166 | },
167 | "concat-map": {
168 | "version": "0.0.1",
169 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
170 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
171 | "dev": true
172 | },
173 | "connect": {
174 | "version": "3.7.0",
175 | "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
176 | "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
177 | "dev": true,
178 | "requires": {
179 | "debug": "2.6.9",
180 | "finalhandler": "1.1.2",
181 | "parseurl": "~1.3.3",
182 | "utils-merge": "1.0.1"
183 | }
184 | },
185 | "connect-json": {
186 | "version": "0.0.0",
187 | "resolved": "https://registry.npmjs.org/connect-json/-/connect-json-0.0.0.tgz",
188 | "integrity": "sha1-jmNEKKy16+zW7EiE/BEGzbKCGAg="
189 | },
190 | "content-disposition": {
191 | "version": "0.5.3",
192 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
193 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
194 | "requires": {
195 | "safe-buffer": "5.1.2"
196 | }
197 | },
198 | "content-type": {
199 | "version": "1.0.4",
200 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
201 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
202 | },
203 | "cookie": {
204 | "version": "0.4.0",
205 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
206 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
207 | },
208 | "cookie-signature": {
209 | "version": "1.0.6",
210 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
211 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
212 | },
213 | "core-util-is": {
214 | "version": "1.0.2",
215 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
216 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
217 | },
218 | "csextends": {
219 | "version": "1.2.0",
220 | "resolved": "https://registry.npmjs.org/csextends/-/csextends-1.2.0.tgz",
221 | "integrity": "sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg=="
222 | },
223 | "dashdash": {
224 | "version": "1.14.1",
225 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
226 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
227 | "dev": true,
228 | "requires": {
229 | "assert-plus": "^1.0.0"
230 | }
231 | },
232 | "debug": {
233 | "version": "2.6.9",
234 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
235 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
236 | "requires": {
237 | "ms": "2.0.0"
238 | }
239 | },
240 | "deepmerge": {
241 | "version": "4.0.0",
242 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.0.0.tgz",
243 | "integrity": "sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww=="
244 | },
245 | "delayed-stream": {
246 | "version": "1.0.0",
247 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
248 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
249 | "dev": true
250 | },
251 | "depd": {
252 | "version": "1.1.2",
253 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
254 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
255 | },
256 | "destroy": {
257 | "version": "1.0.4",
258 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
259 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
260 | },
261 | "diff": {
262 | "version": "1.0.8",
263 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz",
264 | "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=",
265 | "dev": true
266 | },
267 | "eachr": {
268 | "version": "3.2.0",
269 | "resolved": "https://registry.npmjs.org/eachr/-/eachr-3.2.0.tgz",
270 | "integrity": "sha1-LDXkPqCGUW95l8+At6pk1VpKRIQ=",
271 | "requires": {
272 | "editions": "^1.1.1",
273 | "typechecker": "^4.3.0"
274 | }
275 | },
276 | "ecc-jsbn": {
277 | "version": "0.1.2",
278 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
279 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
280 | "dev": true,
281 | "requires": {
282 | "jsbn": "~0.1.0",
283 | "safer-buffer": "^2.1.0"
284 | }
285 | },
286 | "editions": {
287 | "version": "1.3.4",
288 | "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
289 | "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg=="
290 | },
291 | "ee-first": {
292 | "version": "1.1.1",
293 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
294 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
295 | },
296 | "encodeurl": {
297 | "version": "1.0.2",
298 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
299 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
300 | },
301 | "errlop": {
302 | "version": "1.1.1",
303 | "resolved": "https://registry.npmjs.org/errlop/-/errlop-1.1.1.tgz",
304 | "integrity": "sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw==",
305 | "requires": {
306 | "editions": "^2.1.2"
307 | },
308 | "dependencies": {
309 | "editions": {
310 | "version": "2.1.3",
311 | "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
312 | "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
313 | "requires": {
314 | "errlop": "^1.1.1",
315 | "semver": "^5.6.0"
316 | }
317 | }
318 | }
319 | },
320 | "escape-html": {
321 | "version": "1.0.3",
322 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
323 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
324 | },
325 | "escape-string-regexp": {
326 | "version": "1.0.5",
327 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
328 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
329 | "dev": true
330 | },
331 | "etag": {
332 | "version": "1.8.1",
333 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
334 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
335 | },
336 | "express": {
337 | "version": "4.17.1",
338 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
339 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
340 | "requires": {
341 | "accepts": "~1.3.7",
342 | "array-flatten": "1.1.1",
343 | "body-parser": "1.19.0",
344 | "content-disposition": "0.5.3",
345 | "content-type": "~1.0.4",
346 | "cookie": "0.4.0",
347 | "cookie-signature": "1.0.6",
348 | "debug": "2.6.9",
349 | "depd": "~1.1.2",
350 | "encodeurl": "~1.0.2",
351 | "escape-html": "~1.0.3",
352 | "etag": "~1.8.1",
353 | "finalhandler": "~1.1.2",
354 | "fresh": "0.5.2",
355 | "merge-descriptors": "1.0.1",
356 | "methods": "~1.1.2",
357 | "on-finished": "~2.3.0",
358 | "parseurl": "~1.3.3",
359 | "path-to-regexp": "0.1.7",
360 | "proxy-addr": "~2.0.5",
361 | "qs": "6.7.0",
362 | "range-parser": "~1.2.1",
363 | "safe-buffer": "5.1.2",
364 | "send": "0.17.1",
365 | "serve-static": "1.14.1",
366 | "setprototypeof": "1.1.1",
367 | "statuses": "~1.5.0",
368 | "type-is": "~1.6.18",
369 | "utils-merge": "1.0.1",
370 | "vary": "~1.1.2"
371 | }
372 | },
373 | "extend": {
374 | "version": "3.0.2",
375 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
376 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
377 | "dev": true
378 | },
379 | "extendr": {
380 | "version": "3.4.1",
381 | "resolved": "https://registry.npmjs.org/extendr/-/extendr-3.4.1.tgz",
382 | "integrity": "sha512-xcdwhNFctt6U7XVBYgB35YG/CZFOZUZw+OCNiMPsgbENLlajgrZLVV6bgS/9zLHnY7s5nW6nJZcNrBvq033l1w==",
383 | "requires": {
384 | "editions": "^2.1.0",
385 | "typechecker": "^4.7.0"
386 | },
387 | "dependencies": {
388 | "editions": {
389 | "version": "2.1.3",
390 | "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
391 | "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
392 | "requires": {
393 | "errlop": "^1.1.1",
394 | "semver": "^5.6.0"
395 | }
396 | }
397 | }
398 | },
399 | "extract-opts": {
400 | "version": "3.3.1",
401 | "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-3.3.1.tgz",
402 | "integrity": "sha1-WrvtyYwNUgLjJ4cn+Rktfghsa+E=",
403 | "requires": {
404 | "eachr": "^3.2.0",
405 | "editions": "^1.1.1",
406 | "typechecker": "^4.3.0"
407 | }
408 | },
409 | "extsprintf": {
410 | "version": "1.3.0",
411 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
412 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
413 | "dev": true
414 | },
415 | "eyes": {
416 | "version": "0.1.8",
417 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
418 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=",
419 | "dev": true
420 | },
421 | "fast-deep-equal": {
422 | "version": "2.0.1",
423 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
424 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
425 | "dev": true
426 | },
427 | "fast-json-stable-stringify": {
428 | "version": "2.0.0",
429 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
430 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
431 | "dev": true
432 | },
433 | "finalhandler": {
434 | "version": "1.1.2",
435 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
436 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
437 | "requires": {
438 | "debug": "2.6.9",
439 | "encodeurl": "~1.0.2",
440 | "escape-html": "~1.0.3",
441 | "on-finished": "~2.3.0",
442 | "parseurl": "~1.3.3",
443 | "statuses": "~1.5.0",
444 | "unpipe": "~1.0.0"
445 | }
446 | },
447 | "forever-agent": {
448 | "version": "0.6.1",
449 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
450 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
451 | "dev": true
452 | },
453 | "form-data": {
454 | "version": "2.3.3",
455 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
456 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
457 | "dev": true,
458 | "requires": {
459 | "asynckit": "^0.4.0",
460 | "combined-stream": "^1.0.6",
461 | "mime-types": "^2.1.12"
462 | }
463 | },
464 | "forwarded": {
465 | "version": "0.1.2",
466 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
467 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
468 | },
469 | "fresh": {
470 | "version": "0.5.2",
471 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
472 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
473 | },
474 | "fs.realpath": {
475 | "version": "1.0.0",
476 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
477 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
478 | "dev": true
479 | },
480 | "getpass": {
481 | "version": "0.1.7",
482 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
483 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
484 | "dev": true,
485 | "requires": {
486 | "assert-plus": "^1.0.0"
487 | }
488 | },
489 | "glob": {
490 | "version": "7.1.4",
491 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
492 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
493 | "dev": true,
494 | "requires": {
495 | "fs.realpath": "^1.0.0",
496 | "inflight": "^1.0.4",
497 | "inherits": "2",
498 | "minimatch": "^3.0.4",
499 | "once": "^1.3.0",
500 | "path-is-absolute": "^1.0.0"
501 | }
502 | },
503 | "graceful-fs": {
504 | "version": "4.2.0",
505 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz",
506 | "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg=="
507 | },
508 | "growl": {
509 | "version": "1.10.5",
510 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
511 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
512 | "dev": true
513 | },
514 | "har-schema": {
515 | "version": "2.0.0",
516 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
517 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
518 | "dev": true
519 | },
520 | "har-validator": {
521 | "version": "5.1.3",
522 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
523 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
524 | "dev": true,
525 | "requires": {
526 | "ajv": "^6.5.5",
527 | "har-schema": "^2.0.0"
528 | }
529 | },
530 | "has-flag": {
531 | "version": "3.0.0",
532 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
533 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
534 | "dev": true
535 | },
536 | "he": {
537 | "version": "1.1.1",
538 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
539 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
540 | "dev": true
541 | },
542 | "http-errors": {
543 | "version": "1.7.2",
544 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
545 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
546 | "requires": {
547 | "depd": "~1.1.2",
548 | "inherits": "2.0.3",
549 | "setprototypeof": "1.1.1",
550 | "statuses": ">= 1.5.0 < 2",
551 | "toidentifier": "1.0.0"
552 | }
553 | },
554 | "http-signature": {
555 | "version": "1.2.0",
556 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
557 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
558 | "dev": true,
559 | "requires": {
560 | "assert-plus": "^1.0.0",
561 | "jsprim": "^1.2.2",
562 | "sshpk": "^1.7.0"
563 | }
564 | },
565 | "iconv-lite": {
566 | "version": "0.4.24",
567 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
568 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
569 | "requires": {
570 | "safer-buffer": ">= 2.1.2 < 3"
571 | }
572 | },
573 | "ignorefs": {
574 | "version": "1.2.0",
575 | "resolved": "https://registry.npmjs.org/ignorefs/-/ignorefs-1.2.0.tgz",
576 | "integrity": "sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y=",
577 | "requires": {
578 | "editions": "^1.3.3",
579 | "ignorepatterns": "^1.1.0"
580 | }
581 | },
582 | "ignorepatterns": {
583 | "version": "1.1.0",
584 | "resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz",
585 | "integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4="
586 | },
587 | "inflight": {
588 | "version": "1.0.6",
589 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
590 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
591 | "dev": true,
592 | "requires": {
593 | "once": "^1.3.0",
594 | "wrappy": "1"
595 | }
596 | },
597 | "inherits": {
598 | "version": "2.0.3",
599 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
600 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
601 | },
602 | "ipaddr.js": {
603 | "version": "1.9.0",
604 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
605 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
606 | },
607 | "is-typedarray": {
608 | "version": "1.0.0",
609 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
610 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
611 | "dev": true
612 | },
613 | "isstream": {
614 | "version": "0.1.2",
615 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
616 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
617 | "dev": true
618 | },
619 | "jsbn": {
620 | "version": "0.1.1",
621 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
622 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
623 | "dev": true
624 | },
625 | "json-schema": {
626 | "version": "0.2.3",
627 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
628 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
629 | "dev": true
630 | },
631 | "json-schema-traverse": {
632 | "version": "0.4.1",
633 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
634 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
635 | "dev": true
636 | },
637 | "json-stringify-safe": {
638 | "version": "5.0.1",
639 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
640 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
641 | "dev": true
642 | },
643 | "jsprim": {
644 | "version": "1.4.1",
645 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
646 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
647 | "dev": true,
648 | "requires": {
649 | "assert-plus": "1.0.0",
650 | "extsprintf": "1.3.0",
651 | "json-schema": "0.2.3",
652 | "verror": "1.10.0"
653 | }
654 | },
655 | "lodash": {
656 | "version": "4.17.15",
657 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
658 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
659 | },
660 | "media-typer": {
661 | "version": "0.3.0",
662 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
663 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
664 | },
665 | "merge": {
666 | "version": "1.2.1",
667 | "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz",
668 | "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ=="
669 | },
670 | "merge-descriptors": {
671 | "version": "1.0.1",
672 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
673 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
674 | },
675 | "methods": {
676 | "version": "1.1.2",
677 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
678 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
679 | },
680 | "mime": {
681 | "version": "1.6.0",
682 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
683 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
684 | },
685 | "mime-db": {
686 | "version": "1.40.0",
687 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
688 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
689 | },
690 | "mime-types": {
691 | "version": "2.1.24",
692 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
693 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
694 | "requires": {
695 | "mime-db": "1.40.0"
696 | }
697 | },
698 | "minimatch": {
699 | "version": "3.0.4",
700 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
701 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
702 | "dev": true,
703 | "requires": {
704 | "brace-expansion": "^1.1.7"
705 | }
706 | },
707 | "minimist": {
708 | "version": "0.0.8",
709 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
710 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
711 | "dev": true
712 | },
713 | "mkdirp": {
714 | "version": "0.5.1",
715 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
716 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
717 | "dev": true,
718 | "requires": {
719 | "minimist": "0.0.8"
720 | }
721 | },
722 | "mocha": {
723 | "version": "5.2.0",
724 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
725 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
726 | "dev": true,
727 | "requires": {
728 | "browser-stdout": "1.3.1",
729 | "commander": "2.15.1",
730 | "debug": "3.1.0",
731 | "diff": "3.5.0",
732 | "escape-string-regexp": "1.0.5",
733 | "glob": "7.1.2",
734 | "growl": "1.10.5",
735 | "he": "1.1.1",
736 | "minimatch": "3.0.4",
737 | "mkdirp": "0.5.1",
738 | "supports-color": "5.4.0"
739 | },
740 | "dependencies": {
741 | "commander": {
742 | "version": "2.15.1",
743 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
744 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
745 | "dev": true
746 | },
747 | "debug": {
748 | "version": "3.1.0",
749 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
750 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
751 | "dev": true,
752 | "requires": {
753 | "ms": "2.0.0"
754 | }
755 | },
756 | "diff": {
757 | "version": "3.5.0",
758 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
759 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
760 | "dev": true
761 | },
762 | "glob": {
763 | "version": "7.1.2",
764 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
765 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
766 | "dev": true,
767 | "requires": {
768 | "fs.realpath": "^1.0.0",
769 | "inflight": "^1.0.4",
770 | "inherits": "2",
771 | "minimatch": "^3.0.4",
772 | "once": "^1.3.0",
773 | "path-is-absolute": "^1.0.0"
774 | }
775 | }
776 | }
777 | },
778 | "ms": {
779 | "version": "2.0.0",
780 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
781 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
782 | },
783 | "negotiator": {
784 | "version": "0.6.2",
785 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
786 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
787 | },
788 | "oauth-sign": {
789 | "version": "0.9.0",
790 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
791 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
792 | "dev": true
793 | },
794 | "on-finished": {
795 | "version": "2.3.0",
796 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
797 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
798 | "requires": {
799 | "ee-first": "1.1.1"
800 | }
801 | },
802 | "once": {
803 | "version": "1.4.0",
804 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
805 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
806 | "dev": true,
807 | "requires": {
808 | "wrappy": "1"
809 | }
810 | },
811 | "parseurl": {
812 | "version": "1.3.3",
813 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
814 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
815 | },
816 | "path-is-absolute": {
817 | "version": "1.0.1",
818 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
819 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
820 | "dev": true
821 | },
822 | "path-to-regexp": {
823 | "version": "0.1.7",
824 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
825 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
826 | },
827 | "performance-now": {
828 | "version": "2.1.0",
829 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
830 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
831 | "dev": true
832 | },
833 | "proxy-addr": {
834 | "version": "2.0.5",
835 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
836 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
837 | "requires": {
838 | "forwarded": "~0.1.2",
839 | "ipaddr.js": "1.9.0"
840 | }
841 | },
842 | "psl": {
843 | "version": "1.2.0",
844 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz",
845 | "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==",
846 | "dev": true
847 | },
848 | "punycode": {
849 | "version": "2.1.1",
850 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
851 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
852 | "dev": true
853 | },
854 | "q": {
855 | "version": "1.0.1",
856 | "resolved": "https://registry.npmjs.org/q/-/q-1.0.1.tgz",
857 | "integrity": "sha1-EYcq7t7okmgRCxCnGESP+xARKhQ=",
858 | "dev": true
859 | },
860 | "qs": {
861 | "version": "6.7.0",
862 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
863 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
864 | },
865 | "range-parser": {
866 | "version": "1.2.1",
867 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
868 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
869 | },
870 | "raw-body": {
871 | "version": "2.4.0",
872 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
873 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
874 | "requires": {
875 | "bytes": "3.1.0",
876 | "http-errors": "1.7.2",
877 | "iconv-lite": "0.4.24",
878 | "unpipe": "1.0.0"
879 | }
880 | },
881 | "request": {
882 | "version": "2.88.0",
883 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
884 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
885 | "dev": true,
886 | "requires": {
887 | "aws-sign2": "~0.7.0",
888 | "aws4": "^1.8.0",
889 | "caseless": "~0.12.0",
890 | "combined-stream": "~1.0.6",
891 | "extend": "~3.0.2",
892 | "forever-agent": "~0.6.1",
893 | "form-data": "~2.3.2",
894 | "har-validator": "~5.1.0",
895 | "http-signature": "~1.2.0",
896 | "is-typedarray": "~1.0.0",
897 | "isstream": "~0.1.2",
898 | "json-stringify-safe": "~5.0.1",
899 | "mime-types": "~2.1.19",
900 | "oauth-sign": "~0.9.0",
901 | "performance-now": "^2.1.0",
902 | "qs": "~6.5.2",
903 | "safe-buffer": "^5.1.2",
904 | "tough-cookie": "~2.4.3",
905 | "tunnel-agent": "^0.6.0",
906 | "uuid": "^3.3.2"
907 | },
908 | "dependencies": {
909 | "qs": {
910 | "version": "6.5.2",
911 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
912 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
913 | "dev": true
914 | }
915 | }
916 | },
917 | "safe-buffer": {
918 | "version": "5.1.2",
919 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
920 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
921 | },
922 | "safefs": {
923 | "version": "4.1.0",
924 | "resolved": "https://registry.npmjs.org/safefs/-/safefs-4.1.0.tgz",
925 | "integrity": "sha1-+CrrS9165R9lPrIPZyizBYyNZEU=",
926 | "requires": {
927 | "editions": "^1.1.1",
928 | "graceful-fs": "^4.1.4"
929 | }
930 | },
931 | "safer-buffer": {
932 | "version": "2.1.2",
933 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
934 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
935 | },
936 | "scandirectory": {
937 | "version": "2.5.0",
938 | "resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz",
939 | "integrity": "sha1-bOA/VKCQtmjjy+2/IO354xBZPnI=",
940 | "requires": {
941 | "ignorefs": "^1.0.0",
942 | "safefs": "^3.1.2",
943 | "taskgroup": "^4.0.5"
944 | },
945 | "dependencies": {
946 | "safefs": {
947 | "version": "3.2.2",
948 | "resolved": "https://registry.npmjs.org/safefs/-/safefs-3.2.2.tgz",
949 | "integrity": "sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw=",
950 | "requires": {
951 | "graceful-fs": "*"
952 | }
953 | },
954 | "taskgroup": {
955 | "version": "4.3.1",
956 | "resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz",
957 | "integrity": "sha1-feGT/r12gnPEV3MElwJNUSwnkVo=",
958 | "requires": {
959 | "ambi": "^2.2.0",
960 | "csextends": "^1.0.3"
961 | }
962 | }
963 | }
964 | },
965 | "semver": {
966 | "version": "5.7.0",
967 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
968 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
969 | },
970 | "send": {
971 | "version": "0.17.1",
972 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
973 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
974 | "requires": {
975 | "debug": "2.6.9",
976 | "depd": "~1.1.2",
977 | "destroy": "~1.0.4",
978 | "encodeurl": "~1.0.2",
979 | "escape-html": "~1.0.3",
980 | "etag": "~1.8.1",
981 | "fresh": "0.5.2",
982 | "http-errors": "~1.7.2",
983 | "mime": "1.6.0",
984 | "ms": "2.1.1",
985 | "on-finished": "~2.3.0",
986 | "range-parser": "~1.2.1",
987 | "statuses": "~1.5.0"
988 | },
989 | "dependencies": {
990 | "ms": {
991 | "version": "2.1.1",
992 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
993 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
994 | }
995 | }
996 | },
997 | "serve-static": {
998 | "version": "1.14.1",
999 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
1000 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
1001 | "requires": {
1002 | "encodeurl": "~1.0.2",
1003 | "escape-html": "~1.0.3",
1004 | "parseurl": "~1.3.3",
1005 | "send": "0.17.1"
1006 | }
1007 | },
1008 | "setprototypeof": {
1009 | "version": "1.1.1",
1010 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
1011 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
1012 | },
1013 | "sshpk": {
1014 | "version": "1.16.1",
1015 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
1016 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
1017 | "dev": true,
1018 | "requires": {
1019 | "asn1": "~0.2.3",
1020 | "assert-plus": "^1.0.0",
1021 | "bcrypt-pbkdf": "^1.0.0",
1022 | "dashdash": "^1.12.0",
1023 | "ecc-jsbn": "~0.1.1",
1024 | "getpass": "^0.1.1",
1025 | "jsbn": "~0.1.0",
1026 | "safer-buffer": "^2.0.2",
1027 | "tweetnacl": "~0.14.0"
1028 | }
1029 | },
1030 | "statuses": {
1031 | "version": "1.5.0",
1032 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
1033 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
1034 | },
1035 | "supports-color": {
1036 | "version": "5.4.0",
1037 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
1038 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
1039 | "dev": true,
1040 | "requires": {
1041 | "has-flag": "^3.0.0"
1042 | }
1043 | },
1044 | "taskgroup": {
1045 | "version": "5.3.0",
1046 | "resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-5.3.0.tgz",
1047 | "integrity": "sha512-++j3Yi3XZGYgAvmGzRtNa+BnDvkPbdroyMffCY+Gj9A4iH2IJ1S7/g6LewGVXQkVw/KOzlfE1TimARYXvOEsgQ==",
1048 | "requires": {
1049 | "ambi": "^3.0.0",
1050 | "eachr": "^3.2.0",
1051 | "editions": "^1.3.4",
1052 | "extendr": "^3.2.2",
1053 | "unbounded": "^1.1.0"
1054 | },
1055 | "dependencies": {
1056 | "ambi": {
1057 | "version": "3.2.0",
1058 | "resolved": "https://registry.npmjs.org/ambi/-/ambi-3.2.0.tgz",
1059 | "integrity": "sha512-nj5sHLPFd7u2OLmHdFs4DHt3gK6edpNw35hTRIKyI/Vd2Th5e4io50rw1lhmCdUNO2Mm4/4FkHmv6shEANAWcw==",
1060 | "requires": {
1061 | "editions": "^2.1.0",
1062 | "typechecker": "^4.3.0"
1063 | },
1064 | "dependencies": {
1065 | "editions": {
1066 | "version": "2.1.3",
1067 | "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
1068 | "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
1069 | "requires": {
1070 | "errlop": "^1.1.1",
1071 | "semver": "^5.6.0"
1072 | }
1073 | }
1074 | }
1075 | }
1076 | }
1077 | },
1078 | "toidentifier": {
1079 | "version": "1.0.0",
1080 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
1081 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
1082 | },
1083 | "tough-cookie": {
1084 | "version": "2.4.3",
1085 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
1086 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
1087 | "dev": true,
1088 | "requires": {
1089 | "psl": "^1.1.24",
1090 | "punycode": "^1.4.1"
1091 | },
1092 | "dependencies": {
1093 | "punycode": {
1094 | "version": "1.4.1",
1095 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
1096 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
1097 | "dev": true
1098 | }
1099 | }
1100 | },
1101 | "tunnel-agent": {
1102 | "version": "0.6.0",
1103 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
1104 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
1105 | "dev": true,
1106 | "requires": {
1107 | "safe-buffer": "^5.0.1"
1108 | }
1109 | },
1110 | "tweetnacl": {
1111 | "version": "0.14.5",
1112 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
1113 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
1114 | "dev": true
1115 | },
1116 | "type-is": {
1117 | "version": "1.6.18",
1118 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1119 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1120 | "requires": {
1121 | "media-typer": "0.3.0",
1122 | "mime-types": "~2.1.24"
1123 | }
1124 | },
1125 | "typechecker": {
1126 | "version": "4.7.0",
1127 | "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.7.0.tgz",
1128 | "integrity": "sha512-4LHc1KMNJ6NDGO+dSM/yNfZQRtp8NN7psYrPHUblD62Dvkwsp3VShsbM78kOgpcmMkRTgvwdKOTjctS+uMllgQ==",
1129 | "requires": {
1130 | "editions": "^2.1.0"
1131 | },
1132 | "dependencies": {
1133 | "editions": {
1134 | "version": "2.1.3",
1135 | "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
1136 | "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
1137 | "requires": {
1138 | "errlop": "^1.1.1",
1139 | "semver": "^5.6.0"
1140 | }
1141 | }
1142 | }
1143 | },
1144 | "unbounded": {
1145 | "version": "1.2.0",
1146 | "resolved": "https://registry.npmjs.org/unbounded/-/unbounded-1.2.0.tgz",
1147 | "integrity": "sha512-D/ENuBtxxwhxEiSXHgHwM/UXsDRuXz7JwqBQ54zrbaqmbmSWPvsGMzslr5mEftsDo1KOfl7vEIXGCamWBlAT6Q==",
1148 | "requires": {
1149 | "editions": "^2.1.3"
1150 | },
1151 | "dependencies": {
1152 | "editions": {
1153 | "version": "2.1.3",
1154 | "resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
1155 | "integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
1156 | "requires": {
1157 | "errlop": "^1.1.1",
1158 | "semver": "^5.6.0"
1159 | }
1160 | }
1161 | }
1162 | },
1163 | "unpipe": {
1164 | "version": "1.0.0",
1165 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1166 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
1167 | },
1168 | "uri-js": {
1169 | "version": "4.2.2",
1170 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
1171 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
1172 | "dev": true,
1173 | "requires": {
1174 | "punycode": "^2.1.0"
1175 | }
1176 | },
1177 | "utils-merge": {
1178 | "version": "1.0.1",
1179 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1180 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
1181 | },
1182 | "uuid": {
1183 | "version": "3.3.2",
1184 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
1185 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
1186 | "dev": true
1187 | },
1188 | "vary": {
1189 | "version": "1.1.2",
1190 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1191 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
1192 | },
1193 | "verror": {
1194 | "version": "1.10.0",
1195 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
1196 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
1197 | "dev": true,
1198 | "requires": {
1199 | "assert-plus": "^1.0.0",
1200 | "core-util-is": "1.0.2",
1201 | "extsprintf": "^1.2.0"
1202 | }
1203 | },
1204 | "vows": {
1205 | "version": "0.8.2",
1206 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.8.2.tgz",
1207 | "integrity": "sha1-aR95qybM3oC6cm3en+yOc9a88us=",
1208 | "dev": true,
1209 | "requires": {
1210 | "diff": "~1.0.8",
1211 | "eyes": "~0.1.6",
1212 | "glob": "^7.1.2"
1213 | }
1214 | },
1215 | "watchr": {
1216 | "version": "2.6.0",
1217 | "resolved": "https://registry.npmjs.org/watchr/-/watchr-2.6.0.tgz",
1218 | "integrity": "sha1-51xCOxC+eSZ6DD73bi6hBP4CZ6U=",
1219 | "requires": {
1220 | "eachr": "^3.2.0",
1221 | "extendr": "^3.2.2",
1222 | "extract-opts": "^3.3.1",
1223 | "ignorefs": "^1.1.1",
1224 | "safefs": "^4.1.0",
1225 | "scandirectory": "^2.5.0",
1226 | "taskgroup": "^5.0.1",
1227 | "typechecker": "^4.3.0"
1228 | }
1229 | },
1230 | "wrappy": {
1231 | "version": "1.0.2",
1232 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1233 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1234 | "dev": true
1235 | }
1236 | }
1237 | }
1238 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "interfake",
3 | "preferGlobal": true,
4 | "version": "1.20.0",
5 | "author": "Daniel Hough ",
6 | "description": "A simple way to create dummy APIs",
7 | "contributors": [
8 | {
9 | "name": "Daniel Hough",
10 | "email": "daniel.hough@gmail.com",
11 | "url": "https://danhough.com"
12 | },
13 | {
14 | "name": "rajit",
15 | "url": "https://github.com/rajit"
16 | },
17 | {
18 | "name": "bruce-one",
19 | "url": "https://github.com/bruce-one"
20 | },
21 | {
22 | "name": "roychoo",
23 | "url": "https://github.com/roychoo"
24 | }
25 | ],
26 | "scripts": {
27 | "test": "mocha tests/*.test.js --reporter spec"
28 | },
29 | "files": [
30 | "example-apis",
31 | "index.js",
32 | "lib"
33 | ],
34 | "keywords": [
35 | "cli",
36 | "http",
37 | "server",
38 | "mocking",
39 | "mocks",
40 | "testing",
41 | "api",
42 | "rest"
43 | ],
44 | "repository": {
45 | "type": "git",
46 | "url": "git@github.com:basicallydan/interfake.git"
47 | },
48 | "dependencies": {
49 | "body-parser": "^1.6.7",
50 | "commander": "2.3.0",
51 | "connect-json": "0.0.0",
52 | "core-util-is": "^1.0.1",
53 | "deepmerge": "^4.0.0",
54 | "express": "^4.16.4",
55 | "lodash": "^4.17.15",
56 | "merge": "^1.2.1",
57 | "serve-static": "^1.13.2",
58 | "watchr": "^2.4.13"
59 | },
60 | "license": "MIT",
61 | "engines": {
62 | "node": ">=4"
63 | },
64 | "main": "./lib/server.js",
65 | "bin": "./index.js",
66 | "devDependencies": {
67 | "api-easy": "^0.4.0",
68 | "connect": "^3.6.6",
69 | "mocha": "^5.2.0",
70 | "q": "~1.0.1",
71 | "request": "^2.88.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Interfake: Quick JSON APIs
2 |
3 | [](https://gitter.im/basicallydan/interfake?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4 |
5 | Interfake is a tool which allows developers of client-side applications of *any* platform to easily create dummy HTTP APIs to develop against. Let's get started with a simple example.
6 |
7 | ## Get started
8 |
9 | If you don't want to use the JavaScript method to create your Interfake API, go read up on [all the methods](https://github.com/basicallydan/interfake/wiki). Otherwise, read on.
10 |
11 | Install Interfake in your project.
12 |
13 | ```
14 | npm install interfake --save
15 | ```
16 |
17 | Let's write a simple fake API:
18 |
19 | ```js
20 | var Interfake = require('interfake');
21 | var interfake = new Interfake();
22 | interfake.get('/whats-next').body({ next : 'more stuff '});
23 | interfake.listen(3000); // The server will listen on port 3000
24 | ```
25 |
26 | Now go to http://localhost:3000/whats-next in your browser (or [`curl`](http://curl.haxx.se)), and you will see the following:
27 |
28 | ```json
29 | {
30 | "next":"more stuff"
31 | }
32 | ```
33 |
34 | You can also chain response properties:
35 |
36 | ```js
37 | var Interfake = require('interfake');
38 | var interfake = new Interfake();
39 | interfake.get('/whats-next').status(400).body({ error : 'such a bad request'});
40 | interfake.listen(3000);
41 |
42 | /*
43 | # Request:
44 | $ curl http://localhost:3000/whats-next -X GET
45 | # Response:
46 | 400
47 | {
48 | "error":"such a bad request"
49 | }
50 | */
51 | ```
52 |
53 | You can use different HTTP methods:
54 |
55 | ```js
56 | var Interfake = require('interfake');
57 | var interfake = new Interfake();
58 | interfake.post('/next-items').status(201).body({ created : true });
59 | interfake.listen(3000);
60 |
61 | /*
62 | # Request:
63 | $ curl http://localhost:3000/next-items -X POST
64 | # Response:
65 | 201
66 | {
67 | "created":true
68 | }
69 | */
70 | ```
71 |
72 | You can specify endpoints which should only be **created** once other ones have been hit.
73 |
74 | ```js
75 | var Interfake = require('interfake');
76 | var interfake = new Interfake();
77 | var postResponse = interfake.post('/next-items').status(201).body({ created : true });
78 | postResponse.creates.get('/items/1').status(200).body({ id: 1, name: 'Item 1' });
79 | postResponse.creates.get('/next-items').status(200).body({ items: [ { id: 1, name: 'Item 1' } ] });
80 | interfake.listen(3000);
81 |
82 | /*
83 | # Request:
84 | $ curl http://localhost:3000/next-items -X POST
85 | # Response:
86 | 201
87 | {
88 | "created":true
89 | }
90 |
91 |
92 | # Request:
93 | $ curl http://localhost:3000/items/1 -X GET
94 | # Response:
95 | 200
96 | {
97 | "id":1
98 | "name":"Item 1"
99 | }
100 | */
101 | ```
102 |
103 | You can even specify how endpoints should be **extended** once others have been hit.
104 |
105 | ```js
106 | var Interfake = require('interfake');
107 | var interfake = new Interfake();
108 | interfake.get('/items').status(200).body({ items: [ { id: 1, name: 'Item 1' } ] });
109 | interfake.get('/items/1').status(200).body({ id: 1, name: 'Item 1' });
110 | var postResponse = interfake.post('/items').status(201).body({ created : true });
111 | postResponse.creates.get('/items/2').status(200).body({ id: 2, name: 'Item 2' });
112 | postResponse.extends.get('/items').status(200).body({ items: [ { id: 2, name: 'Item 2' } ] });
113 | interfake.listen(3000);
114 |
115 | /*
116 | # Request:
117 | $ curl http://localhost:3000/items -X GET
118 | # Response:
119 | 200
120 | {
121 | "items" : [
122 | {
123 | "id":1
124 | "name":"Item 1"
125 | }
126 | ]
127 | }
128 |
129 | # Request:
130 | $ curl http://localhost:3000/items -X POST
131 | # Response:
132 | 201
133 | {
134 | "created":true
135 | }
136 |
137 |
138 | # Request:
139 | $ curl http://localhost:3000/items -X GET
140 | # Response:
141 | 200
142 | {
143 | "items" : [
144 | {
145 | "id":1
146 | "name":"Item 1"
147 | },
148 | {
149 | "id":2
150 | "name":"Item 2"
151 | }
152 | ]
153 | }
154 | */
155 | ```
156 |
157 | There's more options, though, including delays, custom response headers, and handling query string parameters.
158 |
159 | ---
160 |
161 | ## API
162 |
163 | The majority of Interfake users will probably be interested in the JavaScript API, which is covered below. However, there are in fact [three ways to use Interfake: JavaScript, on the Command Line (using static JSON files), or using an HTTP meta-API](https://github.com/basicallydan/interfake/wiki). These are covered in detail in [the Wiki](https://github.com/basicallydan/interfake/wiki).
164 |
165 | ### JavaScript
166 |
167 | * `new Interfake(options)`: creates an Interfake object. Options are:
168 | * `debug`: If `true`, outputs lots of annoying but helpful log messages. Default is `false`.
169 | * `path`: Sets the API root path. E.g. if `api` is used then the route at `/users` will be accessible at `/api/path`
170 | * `#createRoute(route)`: Takes a JSON object with the following:
171 | * `request`
172 | * `response`
173 | * `afterResponse` (optional)
174 | * `#listen(port, callback)`: Takes a port and starts the server, and a callback which executes when the server is running
175 | * `#stop()`: Stops the server if it's been started
176 | * `#serveStatic(path, directory)`: Serve static (usually a website) files from a certain path. This is useful for testing [SPAs](http://en.wikipedia.org/wiki/Single-page_application). ([Example use.](/examples-javascript/fluent-web-page-test.js))
177 | * `#loadFile(path, options)`: Load a JSON file containing an Interfake-shaped API configuration. Options includes `watch`, which, if true, means that the file loaded there will be reloaded when it changes.
178 |
179 | #### Fluent Interface
180 |
181 | * `#get|post|put|patch|delete(url)`: Create an endpoint at the specified URL. Can then be followed by each of the following, which can follow each other too e.g. `get().query().body().status().body().creates.get()` etc.
182 | * `#query(queryParameters)`: An object containing query parameters to accept. Overwrites matching URL params. E.g. `get('/a?b=1').query({b:2})` means `/a?b=2` will work but `/a?b=1` will not. You can also use arrays as the value, e.g. `.query({b:[1,2]})` or even a valid ```RegExp```. All values which will be matched regardless of order.
183 | * `#status(statusCode)`: Set the response status code for the endpoint
184 | * `#body(body)`: Set the JSON response body of the end point
185 | * `#echo(true|false)`: The response body should be the same as the request body. Can be used after `extends` too. ([Example use](/examples-javascript/fluent-echo.js))
186 | * `#proxy(url|options)`: The response should be a proxy of another URL. Currently, options accepts both `url` and `headers` properties. The `headers` property specifies the headers which should be sent in the request to the proxy URL
187 | * `#delay(milliseconds)`: Set the number of milliseconds to delay the response by to mimic network of processing lag
188 | * Also accepts a delay range in the format 'ms..ms' e.g. '50..100'
189 | * `#responseHeaders(headers)`: An object containing response headers. The keys are header names.
190 | * `#creates#get|post|put|patch|delete(url)`: Specify an endpoint to create *after* the first execution of this one. API is the same as above.
191 | * `#extends#get|post|put|patch|delete(url)`: Specify an endpoint to modify *after* the first execution of this one. API is the same as above. The endpoints you extend are matched based on `url` and `query`. The `status`, `body`, `delay` and `responseHeaders` are the extendable bits. Keep in mind that keys will be replaced, and arrays will be added to.
192 |
193 | ## JSONP
194 |
195 | Interfake supports [JSONP](http://en.wikipedia.org/wiki/JSONP). Just put `?callback` on the end of the URLs being called.
196 |
197 | ```
198 | $ curl http://localhost:3000/whattimeisit?callback=handleSomeJson
199 | ```
200 |
201 | ## Use Cases
202 |
203 | ### Backend/API Prototype for a Single-Page Application (SPA)
204 |
205 | By using Interfake's `.serveStatic()` method, you can serve some front-end HTML, JavaScript and CSS which uses the API you've created as the backend. Not only does this massively speed up development time by not having to have a real API, it serves as a great prototype for the real API, and avoids having to mock requests. This is my most common use for Interfake.
206 |
207 | ### Backend for a Mobile Application
208 |
209 | If you'd like to develop an API-driven mobile application you might not yet have a finished API available. This is a perfect example of where Interfake is useful. You can quickly mock up some dummy APIs and work on the mobile application. In parallel, perhaps another developer will be creating the real API, or you could create it later.
210 |
211 | ### Automated Testing
212 |
213 | You can use Interfake to create dummy APIs which use data from your test setup with the HTTP method above, or by using a static set of test data. If you're writing your test suite using a NodeJS library, you can use the JavaScript API.
214 |
215 | The HTTP API is particularly useful for developing iOS Applications which uses Automated tests written in JavaScript, or developing Node.js applications which rely on external APIs.
216 |
217 | For an example of how to do this, please see the [web page test example](/examples-javascript/fluent-web-page-test.js).
218 |
219 | ### Regular Expressions for URLs
220 |
221 | Regular expressions can be used to specify endpoint URLs in two different ways depending on which interface you use. For the fluent API, you simply put a JavaScript regular expression as the URL, e.g.
222 |
223 | ```javascript
224 | interfake.get(/\/regular\/expression/).status(200);
225 | ```
226 |
227 | This is also supported when using `createRoute`, but since JSON does not support regular expressions, a different method must be used here:
228 |
229 | ```json
230 | [
231 | {
232 | "request": {
233 | "url": {
234 | "pattern" : "/what/the/.*",
235 | "regexp" : true
236 | },
237 | "method": "get"
238 | },
239 | "response": {
240 | "code": 200
241 | }
242 | }
243 | ]
244 | ```
245 |
246 | The pattern specified in the `request.url.pattern` string will be parsed and treated as a regular expression.
247 |
248 | ### Proxying another API
249 |
250 | There are a number of reasons you might want to proxy another API. Three of the more common ones are:
251 |
252 | * It requires some authorization options which you want to hide from a client-side script but nonetheless want to use every time you make a request
253 | * There is a [cross-origin request sharing (CORS)](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing) issue which prevents your client-side code from using the API
254 | * It requires some tedious header setup
255 |
256 | Interfake allows you to proxy another URL quite easily and also specify any headers you like while doing so, using the `proxy` option.
257 |
258 | ```js
259 | interfake.get('/github-issues').proxy('https://api.github.com/repos/basicallydan/interfake/tags');
260 | ```
261 |
262 | The example above creates a simple proxy against the URL `https://api.github.com/repos/basicallydan/interfake/tags` and will return whatever a public, non-authorized user will see. However, consider an endpoint which requires authorization.
263 |
264 | ```js
265 | interfake.get('/github-issues').proxy({
266 | url: 'https://api.github.com/repos/basicallydan/interfake/tags',
267 | headers: {
268 | 'Authorization': 'Token qoinfiu13jfcikwkhf1od091dj0'
269 | }
270 | });
271 | ```
272 |
273 | This example uses an authorization token to authorize the request. This is one of the common use-cases. However, the first one will easily solve CORS issues, and any other headers apart from `Authorization` can be specified instead.
274 |
275 | ### Echoing the request body
276 |
277 | This can be easily achieved using the `.echo()` method in the fluent interface, or by specifying the following for route options:
278 |
279 | ```json
280 | {
281 | request : {
282 | url : '/echo',
283 | method: 'post'
284 | },
285 | response : {
286 | echo : true
287 | }
288 | }
289 | ```
290 |
291 | A request to the `/echo` endpint will return whatever body it is sent. You can see more examples of this in the [examples folder](/examples-javascript/).
292 |
293 | ### Creating a static API
294 |
295 | If you have a website or mobile application which only needs static data, deploy Interfake to a server somewhere with a JSON file serving up the data, and point your application at it.
296 |
297 | ## Compatibility
298 |
299 | I tested this on my Mac. If you have trouble on Windows or any other platform, [raise an issue](https://github.com/basicallydan/interfake/issues).
300 |
301 | ## Version History
302 |
303 | * 1.19.0: Using the npm version of `deepmerge`, not my fork on gh. Exposing underlying express app as instance variable `expressApp`
304 | * 1.18.0: Array support in query string params added thanks to [@roychoo](https://github.com/roychoo). Also, fixed a couple of tests which broke in Node 5.0.
305 | * 1.17.0: Regular expressions can now be specified in JSON route files and in the normal JavaScript API (`.createRoute()`) using `{ url : { pattern : '', regexp : true } }`
306 | * 1.16.0: Added automatic `OPTIONS` support for any routes specified (e.g. if `GET` has been defined then `OPTIONS` will say so. Also includes `access-control-allow-origin`)
307 | * 1.15.0: Added `.echo` or `{ echo : true }` support for response. Now, the response body can an echo of the request body.
308 | * 1.14.0: Fixed `serveStatic` but also accidental new feature. Now, 404 responses include some text: the default express text too.
309 | * 1.13.0: Regex URL support
310 | * 1.12.1: Bug fix from [Alexander Pope](https://github.com/popeindustries), proxy and query params not playing well together
311 | * 1.12.0: Proxy support
312 | * 1.11.0: Config reload
313 | * 1.10.0: Support for PATCH
314 | * 1.9.2: Updated deepmerge dependency, since it included a bug
315 | * 1.9.1: Updated dependencies, and fixed a bug where `.serveStatic` was not working on Windows because of the directory being wrong.
316 | * 1.9.0: Created the `.extends` methods to extend existing endpoints
317 | * 1.8.2: Bug fix for Windows - paths were screwed up
318 | * 1.8.1: Bug fix for responseheaders
319 | * 1.8.0: Querystring parameter values can now be regular expressions
320 | * 1.7.2: Fixed a bug where `.delay()` was not allowing chaining
321 | * 1.7.1: Added ability to set a root path for the API only (skipped 1.7.0 which was a bit broken)
322 | * 1.6.2: Can add a callback to `listen` so that you know when the server has started (by [bruce-one](https://github.com/bruce-one))
323 | * 1.6.1: Upgraded to Express 4.0.0 (thanks to [Sebastian Schürmann](https://github.com/sebs)).
324 | * 1.6.0: Custom response headers (thanks to [Sebastian Schürmann](https://github.com/sebs)).
325 | * 1.5.0: Can now use querystring params (thanks to [rajit](https://github.com/rajit)). Massive.
326 | * 1.4.0: Can specify delay range using `delay(10..50)` (by [bruce-one](https://github.com/bruce-one))
327 | * 1.3.0: Can mimic slow responses using `delay()` (by [bruce-one](https://github.com/bruce-one))
328 | * 1.2.0: Added ability to do static files
329 | * 1.1.1: Fixed the response to `POST /_request` to be a 201, and `POST /_requests` is now the path used
330 | * 1.1.0: Added the fluent interface for easier creation of endpoints
331 | * 1.0.0: Backwards-incompatible changes for JavaScript API, now creating an `Interfake` instance
332 | * 0.2.0: Added JSONP support
333 | * 0.1.0: Support for creating mocked JSON APIs using HTTP, JavaScript or command line
334 |
335 | ## Contribute
336 |
337 | Interfake is a labour of love, created for front-end and mobile developers to increase their prototyping and development speeds. If you can contribute by getting through some issues, I would be very grateful. Please read more about how to contribute in the [CONTRIBUTING.md](https://github.com/basicallydan/interfake/blob/master/CONTRIBUTING.md) document.
338 |
339 | It's important that the tests all pass so we can keep this little badge green:
340 |
341 | [](https://travis-ci.org/basicallydan/interfake)
342 |
343 | <3 Open Source!
344 |
345 | [](http://www.iloveopensource.io/projects/5319884587659fce66000943)
346 |
347 | ## Dependencies
348 |
349 | * [express](https://github.com/visionmedia/express)
350 | * [express body-parser](https://github.com/expressjs/body-parser)
351 | * [connect-json](https://github.com/dtinth/connect-json)
352 | * [commander](https://github.com/visionmedia/commander.js/)
353 | * [core-util-is](https://github.com/isaacs/core-util-is)
354 | * [merge](https://github.com/yeikos/js.merge)
355 | * [basicallydan/deepmerge](https://github.com/basicallydan/deepmerge) forked from [nrf110/deepmerge](https://github.com/nrf110/deepmerge)
356 |
357 | ## Works well with
358 |
359 | * [Mocha](http://mochajs.org/) - the test framework
360 | * [Zombie.js](http://zombie.labnotes.org/) - the Node.js-powered headless web browser
361 |
362 | ## Thank yous
363 |
364 | [Alun](https://github.com/4lun) for reading this readme.
365 |
366 | ## Author & Contributors
367 |
368 | * [Dan Hough](https://github.com/basicallydan) ([Twitter](https://twitter.com/basicallydan) | [Website](http://danielhough.co.uk))
369 | * [bruce-one](https://github.com/bruce-one)
370 | * [rajit](https://github.com/rajit)
371 | * [Sebastian Schürmann](https://github.com/sebs)
372 | * [roychoo](https://github.com/roychoo)
373 |
374 | ## Future work
375 |
376 | * Create a guide/some examples for how to integrate this with existing test frameworks, whether written in JavaScript or not
377 | * Improve the templating, so that a response might include a repeated structure with an incrementing counter or randomized data
378 |
--------------------------------------------------------------------------------
/tests/fluent.test.js:
--------------------------------------------------------------------------------
1 | /* globals describe, beforeEach, afterEach, it */
2 | var assert = require('assert');
3 | var request = require('request');
4 | var Q = require('q');
5 |
6 | request = request.defaults({
7 | json: true
8 | });
9 |
10 | var get = Q.denodeify(request.get);
11 | var post = Q.denodeify(request.post);
12 | var put = Q.denodeify(request.put);
13 | var del = Q.denodeify(request.del);
14 | var patch = Q.denodeify(request.patch);
15 |
16 | // The thing we're testing
17 | var Interfake = require('..');
18 | var interfake;
19 |
20 | describe('Interfake Fluent JavaScript API', function () {
21 | beforeEach(function () {
22 | interfake = new Interfake();
23 | });
24 | afterEach(function () {
25 | if (interfake) {
26 | interfake.stop();
27 | }
28 | });
29 |
30 | // Testing the fluent interface
31 | describe('#get()', function () {
32 | it('should create one GET endpoint', function (done) {
33 | interfake.get('/fluent');
34 | interfake.listen(3000);
35 |
36 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
37 | assert.equal(response.statusCode, 200);
38 | done();
39 | });
40 | });
41 |
42 | it('should create one GET endpoint with a querystring', function (done) {
43 | interfake.get('/fluent?query=1');
44 | interfake.listen(3000);
45 |
46 | request({ url : 'http://localhost:3000/fluent?query=1', json : true }, function (error, response, body) {
47 | assert.equal(response.statusCode, 200);
48 | done();
49 | });
50 | });
51 |
52 | it('should create one GET endpoint with a RegExp path', function (done) {
53 | interfake = new Interfake();
54 | interfake.get(/\/fluent\/.*/);
55 | interfake.listen(3000);
56 |
57 | request({ url : 'http://localhost:3000/fluent/whatever', json : true }, function (error, response, body) {
58 | assert.equal(response.statusCode, 200);
59 | done();
60 | });
61 | });
62 |
63 | describe('#responseHeaders()', function () {
64 | it('should create one GET endpoint which returns custom headers', function (done) {
65 | interfake.get('/fluent').responseHeaders({ 'X-Request-Type': 'test', 'X-lol-TEST': 'bleep' });
66 | interfake.listen(3000);
67 |
68 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response) {
69 | assert.equal(response.statusCode, 200);
70 | assert.equal(response.headers['x-request-type'], 'test');
71 | assert.equal(response.headers['x-lol-test'], 'bleep');
72 | assert.equal(response.headers['x-undef'], undefined);
73 | done();
74 | });
75 | });
76 |
77 | describe('#status()', function () {
78 | it('should return a 300 status', function (done) {
79 | interfake.get('/fluent').responseHeaders({ 'X-Request-Type': 'test', 'X-lol-TEST': 'bleep' }).status(300);
80 | interfake.listen(3000);
81 |
82 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response) {
83 | assert.equal(response.statusCode, 300);
84 | assert.equal(response.headers['x-request-type'], 'test');
85 | assert.equal(response.headers['x-lol-test'], 'bleep');
86 | assert.equal(response.headers['x-undef'], undefined);
87 | done();
88 | });
89 | });
90 | });
91 | });
92 |
93 | describe('#proxy()', function () {
94 | it('should create one GET endpoint which acts as a proxy for another', function (done) {
95 | var proxiedInterfake = new Interfake();
96 | proxiedInterfake.get('/whatever').status(404).body({
97 | message: 'This is something you proxied!'
98 | });
99 | proxiedInterfake.listen(3051);
100 | interfake.get('/proxy').proxy('http://localhost:3051/whatever');
101 | interfake.listen(3000);
102 |
103 | request('http://localhost:3000/proxy', function (error, response, body) {
104 | assert.equal(response.statusCode, 404);
105 | assert.equal(body.message, 'This is something you proxied!');
106 | proxiedInterfake.stop();
107 | done();
108 | });
109 | });
110 |
111 | it('should create one GET endpoint which acts as a proxy for another and sends the specified header', function (done) {
112 | var proxiedInterfake = new Interfake({
113 | onRequest: function (req) {
114 | assert.equal(req.get('Authorization'), 'Basic username:password');
115 | proxiedInterfake.stop();
116 | done();
117 | }
118 | });
119 | proxiedInterfake.get('/whatever').status(404).body({
120 | message: 'This is something you proxied!'
121 | });
122 | proxiedInterfake.listen(3051);
123 | interfake.get('/proxy').proxy({
124 | url: 'http://localhost:3051/whatever',
125 | headers: {
126 | 'Authorization': 'Basic username:password'
127 | }
128 | });
129 | interfake.listen(3000);
130 |
131 | request('http://localhost:3000/proxy', function (error, response, body) {
132 | assert.equal(response.statusCode, 404);
133 | assert.equal(body.message, 'This is something you proxied!');
134 | });
135 | });
136 | });
137 |
138 | describe('#query()', function () {
139 | it('should use the query object as priority', function (done) {
140 | interfake.get('/fluent?query=1').query({ query: 2 }).status(200);
141 | interfake.listen(3000);
142 |
143 | Q.all([get({url:'http://localhost:3000/fluent?query=1',json:true}), get({url:'http://localhost:3000/fluent?query=2',json:true})])
144 | .then(function (results) {
145 | assert.equal(results[0][0].statusCode, 404);
146 | assert.equal(results[1][0].statusCode, 200);
147 | done();
148 | });
149 | });
150 |
151 | it('should use a RegExp to find a partially-matched query string param', function (done) {
152 | interfake.get('/fluent').query({ query: /[0-9]+/ }).status(200);
153 | interfake.listen(3000);
154 |
155 | Q.all([get({url:'http://localhost:3000/fluent?query=1',json:true}), get({url:'http://localhost:3000/fluent?query=2',json:true})])
156 | .then(function (results) {
157 | assert.equal(results[0][0].statusCode, 200);
158 | assert.equal(results[1][0].statusCode, 200);
159 | done();
160 | });
161 | });
162 |
163 | it('should use a RegExp to find a partially-matched query string param and a fully-matched one', function (done) {
164 | interfake.get('/fluent').query({ query: /[0-9]+/, page: 2 }).status(200);
165 | interfake.listen(3000);
166 |
167 | Q.all([get({url:'http://localhost:3000/fluent?query=1&page=5',json:true}), get({url:'http://localhost:3000/fluent?query=2&page=2',json:true})])
168 | .then(function (results) {
169 | assert.equal(results[0][0].statusCode, 404, 'The non-existent page should not be found');
170 | assert.equal(results[1][0].statusCode, 200, 'The existing page should be found');
171 | done();
172 | });
173 | });
174 |
175 | it('should use a RegExp to find a partially-matched query string param and a fully-matched one, when there is also a query-free endpoint', function (done) {
176 | interfake.get('/fluent').query({ query: /[0-9]+/, page: 2 }).status(200);
177 | interfake.get('/fluent').status(300);
178 | interfake.get('/fluent?page=8').status(512);
179 | interfake.listen(3000);
180 |
181 | Q.all([get({url:'http://localhost:3000/fluent?query=1&page=5',json:true}), get({url:'http://localhost:3000/fluent?query=2&page=2',json:true}), get({url:'http://localhost:3000/fluent',json:true}), get({url:'http://localhost:3000/fluent?page=8',json:true})])
182 | .then(function (results) {
183 | assert.equal(results[0][0].statusCode, 404, 'The non-existent page should not be found');
184 | assert.equal(results[1][0].statusCode, 200, 'The existing page should be found');
185 | assert.equal(results[2][0].statusCode, 300, 'The non-query-string page should be found');
186 | assert.equal(results[3][0].statusCode, 512, 'The query-string page without additional params should be found');
187 | done();
188 | });
189 | });
190 |
191 | describe('when a query parameter has an array value', function () {
192 | it('should use an array to find array query string params regardless of order', function (done) {
193 | interfake.get('/fluent').query({ pages: [ '1', '2' ]}).status(200);
194 | interfake.listen(3000);
195 |
196 | Q.all([get({url:'http://localhost:3000/fluent?pages=1&pages=2',json:true}), get({url:'http://localhost:3000/fluent?pages=2&pages=1',json:true})])
197 | .then(function (results) {
198 | assert.equal(results[0][0].statusCode, 200);
199 | assert.equal(results[1][0].statusCode, 200);
200 | done();
201 | });
202 | });
203 |
204 | it('should use an array to find array query string params using the older square-bracket syntax', function (done) {
205 | interfake.get('/fluent').query({ pages: [ '1', '2' ]}).status(200);
206 | interfake.listen(3000);
207 |
208 | Q.all([get({url:'http://localhost:3000/fluent?pages[]=1&pages[]=2',json:true}), get({url:'http://localhost:3000/fluent?pages[]=2&pages[]=1',json:true})])
209 | .then(function (results) {
210 | assert.equal(results[0][0].statusCode, 200);
211 | assert.equal(results[1][0].statusCode, 200);
212 | done();
213 | });
214 | });
215 | });
216 |
217 | describe('#status()', function () {
218 | it('should create a GET endpoint which accepts different querystrings using both methods of querystring specification', function (done) {
219 | interfake.get('/fluent?query=1').query({ page: 1 }).status(400);
220 | interfake.get('/fluent?query=1').query({ page: 2 }).status(500);
221 | interfake.listen(3000);
222 |
223 |
224 | Q.all([get({url:'http://localhost:3000/fluent?query=1&page=1',json:true}), get({url:'http://localhost:3000/fluent?query=1&page=2',json:true})])
225 | .then(function (results) {
226 | assert.equal(results[0][0].statusCode, 400);
227 | assert.equal(results[1][0].statusCode, 500);
228 | done();
229 | });
230 | });
231 |
232 | it('should create a GET endpoint which accepts and does not accept a query string', function (done) {
233 | interfake.get('/fluent');
234 | interfake.get('/fluent').query({ page: 2 }).status(500);
235 | interfake.listen(3000);
236 |
237 |
238 | Q.all([get({url:'http://localhost:3000/fluent',json:true}), get({url:'http://localhost:3000/fluent?page=2',json:true})])
239 | .then(function (results) {
240 | assert.equal(results[0][0].statusCode, 200);
241 | assert.equal(results[1][0].statusCode, 500);
242 | done();
243 | });
244 | });
245 | });
246 |
247 | describe('#creates', function () {
248 | it('should create a GET endpoint which creates another GET endpoint which accepts a query string', function (done) {
249 | interfake.get('/fluent').creates.get('/fluent').query({ page : 2 }).status(300);
250 | interfake.listen(3000);
251 |
252 | get('http://localhost:3000/fluent')
253 | .then(function (results) {
254 | assert.equal(results[0].statusCode, 200);
255 | return get('http://localhost:3000/fluent?page=2');
256 | })
257 | .then(function (results) {
258 | assert.equal(results[0].statusCode, 300);
259 | return get('http://localhost:3000/fluent');
260 | })
261 | .then(function (results) {
262 | assert.equal(results[0].statusCode, 200);
263 | done();
264 | })
265 | .done();
266 | });
267 | it('should create a GET endpoint which creates another GET endpoint which accepts a query string with a regex', function (done) {
268 | var first = interfake.get('/fluent');
269 | first.creates.get('/fluent').query({ page : 2 }).status(300);
270 | first.creates.get('/fluent').query({ page : 2, name : /[a-z]+/ }).status(202);
271 | interfake.listen(3000);
272 |
273 | get('http://localhost:3000/fluent')
274 | .then(function (results) {
275 | assert.equal(results[0].statusCode, 200);
276 | return get('http://localhost:3000/fluent?page=2');
277 | })
278 | .then(function (results) {
279 | assert.equal(results[0].statusCode, 300);
280 | return get('http://localhost:3000/fluent?page=2&name=whatever');
281 | })
282 | .then(function (results) {
283 | assert.equal(results[0].statusCode, 202);
284 | return get('http://localhost:3000/fluent');
285 | })
286 | .then(function (results) {
287 | assert.equal(results[0].statusCode, 200);
288 | done();
289 | })
290 | .done();
291 | });
292 | });
293 | });
294 |
295 | describe('#status()', function () {
296 | it('should create one GET endpoint with a particular status code', function (done) {
297 | interfake.get('/fluent').status(300);
298 | interfake.listen(3000);
299 |
300 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response) {
301 | assert.equal(response.statusCode, 300);
302 | done();
303 | });
304 | });
305 |
306 | it('should create a GET endpoint which accepts different querystrings', function (done) {
307 | interfake.get('/fluent?query=1').status(400);
308 | interfake.get('/fluent?query=2').status(500);
309 | interfake.listen(3000);
310 |
311 |
312 | Q.all([get({url:'http://localhost:3000/fluent?query=1',json:true}), get({url:'http://localhost:3000/fluent?query=2',json:true})])
313 | .then(function (results) {
314 | assert.equal(results[0][0].statusCode, 400);
315 | assert.equal(results[1][0].statusCode, 500);
316 | done();
317 | });
318 | });
319 | });
320 |
321 | describe('#body()', function () {
322 | it('should create one GET endpoint with a particular body', function (done) {
323 | interfake.get('/fluent').body({ fluency : 'isgreat' });
324 | interfake.listen(3000);
325 |
326 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
327 | assert.equal(response.statusCode, 200);
328 | assert.equal(body.fluency, 'isgreat');
329 | done();
330 | });
331 | });
332 |
333 | it('should create two similar GET endpoints with different querystrings and different bodies', function (done) {
334 | interfake.get('/fluent?query=1').body({ schfifty : 'five' });
335 | interfake.get('/fluent?query=2').body({ gimme : 'shelter' });
336 | interfake.listen(3000);
337 |
338 |
339 | Q.all([get({url:'http://localhost:3000/fluent?query=1',json:true}), get({url:'http://localhost:3000/fluent?query=2',json:true})])
340 | .then(function (results) {
341 | assert.equal(results[0][1].schfifty, 'five');
342 | assert.equal(results[1][1].gimme, 'shelter');
343 | done();
344 | });
345 | });
346 |
347 | describe('#status()', function () {
348 | it('should create one GET endpoint with a particular body and particular status', function (done) {
349 | interfake.get('/fluent').body({ fluency : 'isgreat' }).status(300);
350 | interfake.listen(3000);
351 |
352 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
353 | assert.equal(response.statusCode, 300);
354 | assert.equal(body.fluency, 'isgreat');
355 | done();
356 | });
357 | });
358 |
359 | it('should create two similar GET endpoints with different querystrings and different bodies and status codes', function (done) {
360 | interfake.get('/fluent?query=1&another=one').body({ schfifty : 'five' }).status(404);
361 | interfake.get('/fluent?query=2').body({ gimme : 'shelter' }).status(503);
362 | interfake.listen(3000);
363 |
364 |
365 | Q.all([get({url:'http://localhost:3000/fluent?query=1&another=one',json:true}), get({url:'http://localhost:3000/fluent?query=2',json:true})])
366 | .then(function (results) {
367 | assert.equal(results[0][1].schfifty, 'five');
368 | assert.equal(results[0][0].statusCode, 404);
369 | assert.equal(results[1][1].gimme, 'shelter');
370 | assert.equal(results[1][0].statusCode, 503);
371 | done();
372 | });
373 | });
374 |
375 | describe('#delay()', function() {
376 | it('should create one GET endpoint with a particular body, status and delay', function (done) {
377 | var enoughTimeHasPassed = false;
378 | var _this = this;
379 | this.slow(500);
380 | interfake.get('/fluent').body({ fluency : 'isgreat' }).status(300).delay(50);
381 | interfake.listen(3000);
382 | setTimeout(function() {
383 | enoughTimeHasPassed = true;
384 | }, 50);
385 |
386 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
387 | assert.equal(response.statusCode, 300);
388 | assert.equal(body.fluency, 'isgreat');
389 | if(!enoughTimeHasPassed) {
390 | throw new Error('Response wasn\'t delay for long enough');
391 | }
392 | done();
393 | });
394 | });
395 | });
396 | });
397 | });
398 | describe('#delay()', function() {
399 | it('should create one GET endpoint with a particular delay', function (done) {
400 | var enoughTimeHasPassed = false;
401 | this.slow(500);
402 | interfake.get('/fluent').delay(50);
403 | interfake.listen(3000);
404 | setTimeout(function() {
405 | enoughTimeHasPassed = true;
406 | }, 50);
407 |
408 | request({ url : 'http://localhost:3000/fluent', json : true }, function () {
409 | if(!enoughTimeHasPassed) {
410 | throw new Error('Response wasn\'t delay for long enough');
411 | }
412 | done();
413 | });
414 | });
415 |
416 | describe('#body()', function() {
417 | it('should create one GET endpoint with a particular delay and body', function (done) {
418 | var enoughTimeHasPassed = false;
419 | this.slow(500);
420 | interfake.get('/fluent').delay(50).body({
421 | ok: 'yeah'
422 | });
423 | interfake.listen(3000);
424 | setTimeout(function() {
425 | enoughTimeHasPassed = true;
426 | }, 50);
427 |
428 | request({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
429 | if(!enoughTimeHasPassed) {
430 | throw new Error('Response wasn\'t delay for long enough');
431 | }
432 | assert.equal(body.ok, 'yeah');
433 | done();
434 | });
435 | });
436 | });
437 | });
438 | });
439 |
440 | describe('#post()', function () {
441 | it('should create one POST endpoint', function (done) {
442 | interfake.post('/fluent');
443 | interfake.listen(3000);
444 |
445 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
446 | assert.equal(response.statusCode, 200);
447 | done();
448 | });
449 | });
450 |
451 | describe('#status()', function () {
452 | it('should create one POST endpoint with a particular status code', function (done) {
453 | interfake.post('/fluent').status(300);
454 | interfake.listen(3000);
455 |
456 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
457 | assert.equal(response.statusCode, 300);
458 | done();
459 | });
460 | });
461 | });
462 |
463 | describe('#echo()', function () {
464 | it('should return the request body', function (done) {
465 | interfake.post('/stuff').echo();
466 |
467 | interfake.listen(3000);
468 |
469 | request.post({
470 | url :'http://localhost:3000/stuff',
471 | json : true,
472 | body : {
473 | message : 'Echo!'
474 | },
475 | },
476 | function (error, response, body) {
477 | assert.equal(response.statusCode, 200);
478 | assert.equal(body.message, 'Echo!');
479 | done();
480 | });
481 | });
482 | });
483 |
484 | describe('#body()', function () {
485 | it('should create one POST endpoint with a particular body', function (done) {
486 | interfake.post('/fluent').body({ fluency : 'isgreat' });
487 | interfake.listen(3000);
488 |
489 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
490 | assert.equal(response.statusCode, 200);
491 | assert.equal(body.fluency, 'isgreat');
492 | done();
493 | });
494 | });
495 |
496 | describe('#status()', function () {
497 | it('should create one POST endpoint with a particular body and particular status', function (done) {
498 | interfake.post('/fluent').body({ fluency : 'isgreat' }).status(300);
499 | interfake.listen(3000);
500 |
501 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
502 | assert.equal(response.statusCode, 300);
503 | assert.equal(body.fluency, 'isgreat');
504 | done();
505 | });
506 | });
507 | describe('#delay()', function() {
508 | it('should create one POST endpoint with a particular body, status and delay', function (done) {
509 | var enoughTimeHasPassed = false;
510 | var _this = this;
511 | this.slow(500);
512 | interfake.post('/fluent').body({ fluency : 'isgreat' }).status(300).delay(50);
513 | interfake.listen(3000);
514 | setTimeout(function() {
515 | enoughTimeHasPassed = true;
516 | }, 50);
517 |
518 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
519 | assert.equal(response.statusCode, 300);
520 | assert.equal(body.fluency, 'isgreat');
521 | if(!enoughTimeHasPassed) {
522 | throw new Error('Response wasn\'t delay for long enough');
523 | }
524 | done();
525 | });
526 | });
527 | });
528 | });
529 | });
530 |
531 | describe('#delay()', function() {
532 | it('should create one POST endpoint with a particular delay', function (done) {
533 | var enoughTimeHasPassed = false;
534 | var tookTooLong = false;
535 | var _this = this;
536 |
537 | this.slow(500);
538 |
539 | interfake.post('/fluent').delay(50);
540 | interfake.listen(3000);
541 |
542 | setTimeout(function() {
543 | enoughTimeHasPassed = true;
544 | }, 50);
545 |
546 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
547 | if(!enoughTimeHasPassed) {
548 | throw new Error('Response wasn\'t delay for long enough');
549 | }
550 | done();
551 | });
552 | });
553 |
554 | it('should create one POST endpoint with a delay range', function (done) {
555 | var enoughTimeHasPassed = false;
556 | var _this = this;
557 | var tookTooLong = false;
558 | var timeout;
559 | this.slow(500);
560 | interfake.post('/fluent').delay('20..50');
561 | interfake.listen(3000);
562 |
563 | setTimeout(function() {
564 | enoughTimeHasPassed = true;
565 | }, 20);
566 |
567 | timeout = setTimeout(function() {
568 | tookTooLong = true;
569 | }, 55);
570 |
571 | request.post({ url : 'http://localhost:3000/fluent', json : true }, function () {
572 | clearTimeout(timeout);
573 | if(!enoughTimeHasPassed) {
574 | throw new Error('Response wasn\'t delay for long enough');
575 | }
576 | if(tookTooLong) {
577 | throw new Error('Response was delayed for too long');
578 | }
579 | done();
580 | });
581 | });
582 | });
583 |
584 | describe('#creates', function () {
585 | describe('#get()', function () {
586 | it('should create one POST endpoint with a particular body and afterResponse endpoint', function (done) {
587 | interfake.post('/fluent').creates.get('/fluent/1');
588 | interfake.listen(3000);
589 |
590 | get('http://localhost:3000/fluent/1')
591 | .then(function (results) {
592 | assert.equal(results[0].statusCode, 404);
593 | return post('http://localhost:3000/fluent');
594 | })
595 | .then(function (results) {
596 | assert.equal(results[0].statusCode, 200);
597 | return get('http://localhost:3000/fluent/1');
598 | })
599 | .then(function (results) {
600 | assert.equal(results[0].statusCode, 200);
601 | done();
602 | });
603 | });
604 |
605 | it('should create one POST endpoint with two afterResponse endpoints', function (done) {
606 | var postEndpoint = interfake.post('/fluent');
607 | postEndpoint.creates.get('/fluent/1');
608 | postEndpoint.creates.put('/fluent/1');
609 | interfake.listen(3000);
610 |
611 | get('http://localhost:3000/fluent/1')
612 | .then(function (results) {
613 | assert.equal(results[0].statusCode, 404);
614 | return post('http://localhost:3000/fluent');
615 | })
616 | .then(function (results) {
617 | assert.equal(results[0].statusCode, 200);
618 | return get('http://localhost:3000/fluent/1');
619 | })
620 | .then(function (results) {
621 | assert.equal(results[0].statusCode, 200);
622 | return put('http://localhost:3000/fluent/1');
623 | })
624 | .then(function (results) {
625 | assert.equal(results[0].statusCode, 200);
626 | done();
627 | });
628 | });
629 |
630 | it('should create one POST endpoint with two afterResponse endpoints which accept querystrings', function (done) {
631 | var postEndpoint = interfake.post('/fluent');
632 | postEndpoint.creates.get('/fluent?q=1');
633 | postEndpoint.creates.put('/fluent?q=1');
634 | interfake.listen(3000);
635 |
636 | get('http://localhost:3000/fluent?q=1')
637 | .then(function (results) {
638 | assert.equal(results[0].statusCode, 404);
639 | return post('http://localhost:3000/fluent');
640 | })
641 | .then(function (results) {
642 | assert.equal(results[0].statusCode, 200);
643 | return get('http://localhost:3000/fluent?q=1');
644 | })
645 | .then(function (results) {
646 | assert.equal(results[0].statusCode, 200);
647 | return put('http://localhost:3000/fluent?q=1');
648 | })
649 | .then(function (results) {
650 | assert.equal(results[0].statusCode, 200);
651 | done();
652 | });
653 | });
654 |
655 | describe('#query()', function () {
656 | it('should create multiple post-response GET endpoints which accept querystrings and also create endpoints (CRUD)', function (done) {
657 | // CREATE
658 | var postEndpoint = interfake.post('/fluent').status(201);
659 | // READ
660 | postEndpoint.creates.get('/fluent?q=1').status(200).body({ title : 'Hello!' });
661 | // UPDATE
662 | var putEndpoint = postEndpoint.creates.put('/fluent?q=1').status(200).body({ title : 'Hello again!' });
663 | putEndpoint.creates.get('/fluent?q=1').status(200).body({ title : 'Hello again!' });
664 | // DELETE
665 | var deleteEndpoint = postEndpoint.creates.delete('/fluent?q=1').status(200);
666 | deleteEndpoint.creates.get('/fluent?q=1').status(410);
667 | deleteEndpoint.creates.put('/fluent?q=1').status(410);
668 | interfake.listen(3000);
669 |
670 | get('http://localhost:3000/fluent?q=1')
671 | .then(function (results) {
672 | assert.equal(results[0].statusCode, 404, 'The GET should not exist yet');
673 | return post('http://localhost:3000/fluent');
674 | })
675 | .then(function (results) {
676 | assert.equal(results[0].statusCode, 201, 'The POST should report creation');
677 | return get({url: 'http://localhost:3000/fluent?q=1', json: true});
678 | })
679 | .then(function (results) {
680 | assert.equal(results[0].statusCode, 200, 'The GET should now exist');
681 | assert.equal(results[1].title, 'Hello!');
682 | return put({url: 'http://localhost:3000/fluent?q=1', json: true});
683 | })
684 | .then(function (results) {
685 | assert.equal(results[0].statusCode, 200, 'The PUT should have sucessfully updated');
686 | assert.equal(results[1].title, 'Hello again!');
687 | return get({url: 'http://localhost:3000/fluent?q=1', json: true});
688 | })
689 | .then(function (results) {
690 | assert.equal(results[0].statusCode, 200, 'The GET should still exist');
691 | assert.equal(results[1].title, 'Hello again!');
692 | return del({url: 'http://localhost:3000/fluent?q=1', json: true});
693 | })
694 | .then(function (results) {
695 | assert.equal(results[0].statusCode, 200, 'The DELETE should report successful deletion');
696 | return get({url: 'http://localhost:3000/fluent?q=1', json: true});
697 | })
698 | .then(function (results) {
699 | assert.equal(results[0].statusCode, 410, 'The GET should no longer exist');
700 | return put({url: 'http://localhost:3000/fluent?q=1', json: true});
701 | })
702 | .then(function (results) {
703 | assert.equal(results[0].statusCode, 410, 'The PUT should no longer exist');
704 | done();
705 | })
706 | .fail(done);
707 | });
708 | });
709 |
710 | describe('#status()', function () {
711 | it('should create a post-response GET with a particular status', function (done) {
712 | interfake.post('/fluent').creates.get('/fluent/1').status(300);
713 | interfake.listen(3000);
714 |
715 | get('http://localhost:3000/fluent/1')
716 | .then(function (results) {
717 | assert.equal(results[0].statusCode, 404);
718 | return post('http://localhost:3000/fluent');
719 | })
720 | .then(function (results) {
721 | assert.equal(results[0].statusCode, 200);
722 | return get('http://localhost:3000/fluent/1');
723 | })
724 | .then(function (results) {
725 | assert.equal(results[0].statusCode, 300);
726 | done();
727 | });
728 | });
729 | });
730 |
731 | describe('#body()', function () {
732 | it('should create a post-response GET with a particular body', function (done) {
733 | interfake.post('/fluent').creates.get('/fluent/1').body({ fluency : 'is badass' });
734 | interfake.listen(3000);
735 |
736 | get('http://localhost:3000/fluent/1')
737 | .then(function (results) {
738 | assert.equal(results[0].statusCode, 404);
739 | return post('http://localhost:3000/fluent');
740 | })
741 | .then(function (results) {
742 | assert.equal(results[0].statusCode, 200);
743 | return get({url:'http://localhost:3000/fluent/1', json:true});
744 | })
745 | .then(function (results) {
746 | assert.equal(results[1].fluency, 'is badass');
747 | done();
748 | });
749 | });
750 |
751 | describe('#status()', function() {
752 | it('should create a post-response GET with a particular and body and status', function (done) {
753 | interfake.post('/fluent').creates.get('/fluent/1').body({ fluency : 'is badass' }).status(500);
754 | interfake.listen(3000);
755 |
756 | get('http://localhost:3000/fluent/1')
757 | .then(function (results) {
758 | assert.equal(results[0].statusCode, 404);
759 | return post('http://localhost:3000/fluent');
760 | })
761 | .then(function (results) {
762 | assert.equal(results[0].statusCode, 200);
763 | return get({url:'http://localhost:3000/fluent/1', json:true});
764 | })
765 | .then(function (results) {
766 | assert.equal(results[0].statusCode, 500);
767 | assert.equal(results[1].fluency, 'is badass');
768 | done();
769 | });
770 | });
771 | });
772 | });
773 |
774 | describe('#creates', function () {
775 | it('should create a post-response GET with another post-response GET', function (done) {
776 | interfake.post('/fluent').creates.get('/fluent/1').creates.get('/fluent/2');
777 | interfake.listen(3000);
778 |
779 | get('http://localhost:3000/fluent/1')
780 | .then(function (results) {
781 | assert.equal(results[0].statusCode, 404);
782 | return post('http://localhost:3000/fluent');
783 | })
784 | .then(function (results) {
785 | assert.equal(results[0].statusCode, 200);
786 | return get('http://localhost:3000/fluent/1');
787 | })
788 | .then(function (results) {
789 | assert.equal(results[0].statusCode, 200);
790 | return get('http://localhost:3000/fluent/2');
791 | })
792 | .then(function (results) {
793 | assert.equal(results[0].statusCode, 200);
794 | done();
795 | });
796 | });
797 | });
798 | });
799 | });
800 | });
801 |
802 | describe('#patch()', function () {
803 | it('should create one PATCH endpoint', function (done) {
804 | interfake.patch('/fluent');
805 | interfake.listen(3000);
806 |
807 | request.patch({ url : 'http://localhost:3000/fluent', json : true }, function (error, response, body) {
808 | assert.equal(response.statusCode, 200);
809 | done();
810 | });
811 | });
812 |
813 | describe('#extends', function () {
814 | it('should create a PATCH endpoint which allows itself to be extended', function (done) {
815 | interfake.get('/users').body([
816 | {
817 | name: 'Max Headroom'
818 | }
819 | ]);
820 |
821 | interfake.patch('/users').extends.get('/users').body([{
822 | name: 'Min Headroom'
823 | }
824 | ]);
825 |
826 | interfake.listen(3000);
827 |
828 | get({ url : 'http://localhost:3000/users', json : true })
829 | .then(function (results) {
830 | assert.equal(results[0].statusCode, 200);
831 | assert.equal(results[1].length, 1);
832 | return patch({ url : 'http://localhost:3000/users', json : true });
833 | })
834 | .then(function (results) {
835 | assert.equal(results[0].statusCode, 200);
836 | return get({ url : 'http://localhost:3000/users', json : true });
837 | })
838 | .then(function (results) {
839 | assert.equal(results[0].statusCode, 200);
840 | assert.equal(results[1].length, 2);
841 | done();
842 | })
843 | .catch(done);
844 | });
845 | });
846 | });
847 |
848 | // Testing #extends stuff
849 | describe('#extends', function () {
850 | describe('#get()', function() {
851 | describe('#body()', function () {
852 | it('should create a GET endpoint which extends its own body when it gets called', function (done) {
853 | interfake.get('/fluent').body({ hello : 'there', goodbye: 'for now' }).extends.get('/fluent').body({ what: 'ever' });
854 | interfake.listen(3000);
855 |
856 | get({url:'http://localhost:3000/fluent',json:true})
857 | .then(function (results) {
858 | assert.equal(results[0].statusCode, 200);
859 | assert.equal(results[1].hello, 'there');
860 | assert.equal(results[1].goodbye, 'for now');
861 | assert.equal(results[1].what, undefined);
862 | return get({url:'http://localhost:3000/fluent',json:true});
863 | })
864 | .then(function (results) {
865 | assert.equal(results[0].statusCode, 200);
866 | assert.equal(results[1].hello, 'there');
867 | assert.equal(results[1].goodbye, 'for now');
868 | assert.equal(results[1].what, 'ever');
869 | done();
870 | })
871 | .done();
872 | });
873 |
874 | it('should create a POST endpoint which adds to a GET endpoint with deep array when it gets called', function (done) {
875 | interfake.get('/items').body({ items : [ { id: 1 } ] });
876 | interfake.post('/items').status(201).extends.get('/items').body({ items : [ { id : 2 } ] });
877 | interfake.listen(3000);
878 |
879 | get({url:'http://localhost:3000/items',json:true})
880 | .then(function (results) {
881 | assert.equal(results[0].statusCode, 200);
882 | assert.equal(results[1].items[0].id, 1);
883 | assert.equal(results[1].items.length, 1);
884 | return post({url:'http://localhost:3000/items',json:true});
885 | })
886 | .then(function (results) {
887 | assert.equal(results[0].statusCode, 201);
888 | return get({url:'http://localhost:3000/items',json:true});
889 | })
890 | .then(function (results) {
891 | assert.equal(results[0].statusCode, 200);
892 | assert.equal(results[1].items[0].id, 1);
893 | assert.equal(results[1].items[1].id, 2);
894 | assert.equal(results[1].items.length, 2);
895 | done();
896 | })
897 | .done();
898 | });
899 |
900 | it('should create a POST endpoint which adds to a GET endpoint with top-level array when it gets called', function (done) {
901 | interfake.get('/items').body([ { id: 1 } ]);
902 | interfake.post('/items').status(201).extends.get('/items').body([ { id : 2 } ]);
903 | interfake.listen(3000);
904 |
905 | get({url:'http://localhost:3000/items',json:true})
906 | .then(function (results) {
907 | assert.equal(results[0].statusCode, 200);
908 | assert.equal(results[1][0].id, 1);
909 | assert.equal(results[1][1], undefined);
910 | return post({url:'http://localhost:3000/items',json:true});
911 | })
912 | .then(function (results) {
913 | assert.equal(results[0].statusCode, 201);
914 | return get({url:'http://localhost:3000/items',json:true});
915 | })
916 | .then(function (results) {
917 | assert.equal(results[0].statusCode, 200);
918 | assert.equal(results[1][0].id, 1);
919 | assert.equal(results[1][1].id, 2);
920 | done();
921 | })
922 | .done();
923 | });
924 | });
925 |
926 | describe('#status()', function () {
927 | it('should create a GET endpoint which extends its own status when it gets called', function (done) {
928 | interfake.get('/fluent').body({ hello : 'there', goodbye: 'for now' }).extends.get('/fluent').status(401);
929 | interfake.listen(3000);
930 |
931 | get({url:'http://localhost:3000/fluent',json:true})
932 | .then(function (results) {
933 | assert.equal(results[0].statusCode, 200);
934 | assert.equal(results[1].hello, 'there');
935 | assert.equal(results[1].goodbye, 'for now');
936 | assert.equal(results[1].what, undefined);
937 | return get({url:'http://localhost:3000/fluent',json:true});
938 | })
939 | .then(function (results) {
940 | assert.equal(results[0].statusCode, 401);
941 | assert.equal(results[1].hello, 'there');
942 | assert.equal(results[1].goodbye, 'for now');
943 | assert.equal(results[1].what, undefined);
944 | done();
945 | })
946 | .done();
947 | });
948 | });
949 |
950 | describe('#responseHeaders()', function () {
951 | it('should create a GET endpoint which extends its own response headers when it gets called', function (done) {
952 | interfake.get('/fluent').responseHeaders({ 'Awesome-Header' : 'Awesome Value' }).extends.get('/fluent').responseHeaders({ 'Lame-Header' : 'Lame Value' });
953 | interfake.listen(3000);
954 |
955 | get({url:'http://localhost:3000/fluent',json:true})
956 | .then(function (results) {
957 | assert.equal(results[0].headers['awesome-header'], 'Awesome Value');
958 | assert.equal(results[0].headers['lame-header'], undefined);
959 | return get({url:'http://localhost:3000/fluent',json:true});
960 | })
961 | .then(function (results) {
962 | assert.equal(results[0].headers['awesome-header'], 'Awesome Value');
963 | assert.equal(results[0].headers['lame-header'], 'Lame Value');
964 | done();
965 | })
966 | .done();
967 | });
968 | });
969 |
970 | describe('#delay()', function () {
971 | it('should create a GET endpoint which adds a delay to itself when it gets called', function (done) {
972 | var enoughTimeHasPassed;
973 | interfake.get('/fluent').body({ hello : 'there', goodbye: 'for now' }).extends.get('/fluent').delay(50);
974 | interfake.listen(3000);
975 |
976 | setTimeout(function() {
977 | enoughTimeHasPassed = true;
978 | }, 50);
979 |
980 | get({url:'http://localhost:3000/fluent',json:true})
981 | .then(function (results) {
982 | assert.equal(results[0].statusCode, 200);
983 | assert.equal(results[1].hello, 'there');
984 | assert.equal(results[1].goodbye, 'for now');
985 | assert.equal(results[1].what, undefined);
986 | return get({url:'http://localhost:3000/fluent',json:true});
987 | })
988 | .then(function (results) {
989 | if(!enoughTimeHasPassed) {
990 | throw new Error('Response wasn\'t delay for long enough');
991 | }
992 | assert.equal(results[0].statusCode, 200);
993 | assert.equal(results[1].hello, 'there');
994 | assert.equal(results[1].goodbye, 'for now');
995 | assert.equal(results[1].what, undefined);
996 | done();
997 | })
998 | .done();
999 | });
1000 |
1001 | it('should create a GET endpoint which adds a delay to a different endpoint when it gets called', function (done) {
1002 | var enoughTimeHasPassed, tookTooLong;
1003 | interfake.get('/fluent').extends.get('/needs-delay').delay(50);
1004 | interfake.get('/needs-delay');
1005 | interfake.listen(3000);
1006 |
1007 | setTimeout(function() {
1008 | tookTooLong = true;
1009 | }, 50);
1010 |
1011 | get({url:'http://localhost:3000/needs-delay', json:true})
1012 | .then(function (results) {
1013 | assert.equal(results[0].statusCode, 200);
1014 | if (tookTooLong) {
1015 | throw new Error('The response took too long the first time');
1016 | }
1017 | return get({url:'http://localhost:3000/fluent', json:true});
1018 | })
1019 | .then(function (results) {
1020 | assert.equal(results[0].statusCode, 200);
1021 | setTimeout(function() {
1022 | enoughTimeHasPassed = true;
1023 | }, 50);
1024 | return get({url:'http://localhost:3000/needs-delay', json:true});
1025 | })
1026 | .then(function (results) {
1027 | assert.equal(results[0].statusCode, 200);
1028 | if(!enoughTimeHasPassed) {
1029 | throw new Error('Response wasn\'t delay for long enough');
1030 | }
1031 | done();
1032 | })
1033 | .done();
1034 | });
1035 | });
1036 |
1037 | describe('#query()', function () {
1038 | it('should create a GET endpoint with a query which extends its own status', function (done) {
1039 | interfake.get('/fluent').query({ page : 2 }).status(200).extends.get('/fluent').query({ page : 2 }).status(300);
1040 | interfake.listen(3000);
1041 |
1042 | get('http://localhost:3000/fluent?page=2')
1043 | .then(function (results) {
1044 | assert.equal(results[0].statusCode, 200);
1045 | return get('http://localhost:3000/fluent?page=2');
1046 | })
1047 | .then(function (results) {
1048 | assert.equal(results[0].statusCode, 300);
1049 | done();
1050 | })
1051 | .done();
1052 | });
1053 | });
1054 |
1055 | describe('#creates', function () {
1056 | describe('#get', function () {
1057 | it('should produce a useful error when trying to spawn a GET endpoint from a modified endpoint', function (done) {
1058 | assert.throws(function () {
1059 | interfake.get('/fluent').extends.get('/fluent').creates.get('/error');
1060 | }, function (err) {
1061 | assert.equal(err.message, 'Sorry, but modified routes cannot yet create new routes after their response. This is planned for a future version of Interfake.');
1062 | done();
1063 | return true;
1064 | });
1065 | });
1066 | });
1067 | describe('#post', function () {
1068 | it('should produce a useful error when trying to spawn a POST endpoint from a modified endpoint', function (done) {
1069 | assert.throws(function () {
1070 | interfake.get('/fluent').extends.get('/fluent').creates.post('/error');
1071 | }, function (err) {
1072 | assert.equal(err.message, 'Sorry, but modified routes cannot yet create new routes after their response. This is planned for a future version of Interfake.');
1073 | done();
1074 | return true;
1075 | });
1076 | });
1077 | });
1078 | describe('#put', function () {
1079 | it('should produce a useful error when trying to spawn a PUT endpoint from a modified endpoint', function (done) {
1080 | assert.throws(function () {
1081 | interfake.get('/fluent').extends.get('/fluent').creates.put('/error');
1082 | }, function (err) {
1083 | assert.equal(err.message, 'Sorry, but modified routes cannot yet create new routes after their response. This is planned for a future version of Interfake.');
1084 | done();
1085 | return true;
1086 | });
1087 | });
1088 | });
1089 | describe('#delete', function () {
1090 | it('should produce a useful error when trying to spawn a DELETE endpoint from a modified endpoint', function (done) {
1091 | assert.throws(function () {
1092 | interfake.get('/fluent').extends.get('/fluent').creates.delete('/error');
1093 | }, function (err) {
1094 | assert.equal(err.message, 'Sorry, but modified routes cannot yet create new routes after their response. This is planned for a future version of Interfake.');
1095 | done();
1096 | return true;
1097 | });
1098 | });
1099 | });
1100 | });
1101 |
1102 | describe('#extends', function () {
1103 | describe('#get', function () {
1104 | it('should produce a useful error when trying to modify an existing GET endpoint from a modified endpoint', function (done) {
1105 | assert.throws(function () {
1106 | interfake.get('/fluent').extends.get('/fluent').extends.get('/error');
1107 | }, function (err) {
1108 | assert.equal(err.message, 'Sorry, but modified routes cannot yet modify existing routes after their response. This is planned for a future version of Interfake.');
1109 | done();
1110 | return true;
1111 | });
1112 | });
1113 | });
1114 | describe('#post', function () {
1115 | it('should produce a useful error when trying to modify an existing POST endpoint from a modified endpoint', function (done) {
1116 | assert.throws(function () {
1117 | interfake.get('/fluent').extends.get('/fluent').extends.post('/error');
1118 | }, function (err) {
1119 | assert.equal(err.message, 'Sorry, but modified routes cannot yet modify existing routes after their response. This is planned for a future version of Interfake.');
1120 | done();
1121 | return true;
1122 | });
1123 | });
1124 | });
1125 | describe('#put', function () {
1126 | it('should produce a useful error when trying to modify an existing PUT endpoint from a modified endpoint', function (done) {
1127 | assert.throws(function () {
1128 | interfake.get('/fluent').extends.get('/fluent').extends.put('/error');
1129 | }, function (err) {
1130 | assert.equal(err.message, 'Sorry, but modified routes cannot yet modify existing routes after their response. This is planned for a future version of Interfake.');
1131 | done();
1132 | return true;
1133 | });
1134 | });
1135 | });
1136 | describe('#delete', function () {
1137 | it('should produce a useful error when trying to modify an existing DELETE endpoint from a modified endpoint', function (done) {
1138 | assert.throws(function () {
1139 | interfake.get('/fluent').extends.get('/fluent').extends.delete('/error');
1140 | }, function (err) {
1141 | assert.equal(err.message, 'Sorry, but modified routes cannot yet modify existing routes after their response. This is planned for a future version of Interfake.');
1142 | done();
1143 | return true;
1144 | });
1145 | });
1146 | });
1147 | });
1148 | });
1149 |
1150 | describe('#put()', function () {
1151 | describe('#body()', function () {
1152 | it('should create a PUT endpoint which extends its own body when it gets called', function (done) {
1153 | interfake.put('/fluent').body({ version : 1 }).extends.put('/fluent').body({ version: 2 });
1154 | interfake.listen(3000);
1155 |
1156 | put({url:'http://localhost:3000/fluent',json:true})
1157 | .then(function (results) {
1158 | assert.equal(results[0].statusCode, 200);
1159 | assert.equal(results[1].version, 1);
1160 | return put({url:'http://localhost:3000/fluent',json:true});
1161 | })
1162 | .then(function (results) {
1163 | assert.equal(results[0].statusCode, 200);
1164 | assert.equal(results[1].version, 2);
1165 | done();
1166 | })
1167 | .done();
1168 | });
1169 | });
1170 | });
1171 |
1172 | describe('#post()', function () {
1173 | describe('#body()', function () {
1174 | it('should create a POST endpoint which extends its own body when it gets called', function (done) {
1175 | interfake.post('/fluent').body({ version : 1 }).extends.post('/fluent').body({ version: 2 });
1176 | interfake.listen(3000);
1177 |
1178 | post({url:'http://localhost:3000/fluent',json:true})
1179 | .then(function (results) {
1180 | assert.equal(results[0].statusCode, 200);
1181 | assert.equal(results[1].version, 1);
1182 | return post({url:'http://localhost:3000/fluent',json:true});
1183 | })
1184 | .then(function (results) {
1185 | assert.equal(results[0].statusCode, 200);
1186 | assert.equal(results[1].version, 2);
1187 | done();
1188 | })
1189 | .done();
1190 | });
1191 | });
1192 |
1193 | describe('#echo()', function () {
1194 | it('should create a POST endpoint which becomes an echo endpoint when it gets called', function (done) {
1195 | interfake.post('/fluent').body({ version : 1 }).extends.post('/fluent').echo();
1196 | interfake.listen(3000);
1197 |
1198 | post({url:'http://localhost:3000/fluent',json:true})
1199 | .then(function (results) {
1200 | assert.equal(results[0].statusCode, 200);
1201 | assert.equal(results[1].version, 1);
1202 | return post({ url:'http://localhost:3000/fluent', json : true, body : { version : 5 } });
1203 | })
1204 | .then(function (results) {
1205 | assert.equal(results[0].statusCode, 200);
1206 | assert.equal(results[1].version, 5);
1207 | done();
1208 | })
1209 | .done();
1210 | });
1211 |
1212 | it('should create a POST endpoint which was an echo endpoint but becomes a non-echo endpoint when it gets called', function (done) {
1213 | interfake.post('/fluent').echo().extends.post('/fluent').body({ version : 2 }).echo(false);
1214 | interfake.listen(3000);
1215 |
1216 | post({ url:'http://localhost:3000/fluent', json : true, body : { version : 5 } })
1217 | .then(function (results) {
1218 | assert.equal(results[0].statusCode, 200);
1219 | assert.equal(results[1].version, 5);
1220 | return post({ url:'http://localhost:3000/fluent', json : true, body : { version : 5 } });
1221 | })
1222 | .then(function (results) {
1223 | assert.equal(results[0].statusCode, 200);
1224 | assert.equal(results[1].version, 2);
1225 | done();
1226 | })
1227 | .done();
1228 | });
1229 | });
1230 | });
1231 |
1232 | describe('#delete()', function () {
1233 | describe('#body()', function () {
1234 | it('should create a DELETE endpoint which extends its own body when it gets called', function (done) {
1235 | interfake.delete('/fluent').body({ version : 1 }).extends.delete('/fluent').body({ version: 2 });
1236 | interfake.listen(3000);
1237 |
1238 | del({url:'http://localhost:3000/fluent',json:true})
1239 | .then(function (results) {
1240 | assert.equal(results[0].statusCode, 200);
1241 | assert.equal(results[1].version, 1);
1242 | return del({url:'http://localhost:3000/fluent',json:true});
1243 | })
1244 | .then(function (results) {
1245 | assert.equal(results[0].statusCode, 200);
1246 | assert.equal(results[1].version, 2);
1247 | done();
1248 | })
1249 | .done();
1250 | });
1251 | });
1252 | });
1253 | });
1254 | });
--------------------------------------------------------------------------------
/tests/http.test.js:
--------------------------------------------------------------------------------
1 | /* globals describe, beforeEach, afterEach, it */
2 | var assert = require('assert');
3 | var request = require('request');
4 | var Q = require('q');
5 |
6 | request.defaults({
7 | json:true
8 | });
9 |
10 | var get = Q.denodeify(request.get);
11 | var post = Q.denodeify(request.post);
12 | var put = Q.denodeify(request.put);
13 |
14 | // The thing we're testing
15 | var Interfake = require('..');
16 |
17 | describe('Interfake HTTP API', function () {
18 | describe('POST /_request', function () {
19 | it('should create one GET endpoint', function (done) {
20 | var interfake = new Interfake(/*{debug:true}*/);
21 | interfake.listen(3000);
22 |
23 | var endpoint = {
24 | request: {
25 | url: '/test',
26 | method: 'get'
27 | },
28 | response: {
29 | code: 200,
30 | body: {
31 | hi: 'there'
32 | },
33 | headers: {
34 | 'foo': 'bar'
35 | }
36 | }
37 | };
38 |
39 | post({url: 'http://localhost:3000/_request', json: true, body: endpoint})
40 | .then(function (results) {
41 | assert.equal(results[0].statusCode, 201);
42 | assert.equal(results[1].done, true);
43 | return get({url:'http://localhost:3000/test', json: true});
44 | })
45 | .then(function (results) {
46 | assert.equal(results[0].headers['foo'], 'bar');
47 | // console.log(results[0].cookies)
48 | assert.equal(results[0].statusCode, 200);
49 | assert.equal(results[1].hi, 'there');
50 | interfake.stop();
51 | done();
52 | });
53 | });
54 | });
55 | });
--------------------------------------------------------------------------------
/tests/javascript.test.js:
--------------------------------------------------------------------------------
1 | /* globals describe, beforeEach, afterEach, it */
2 | var assert = require('assert');
3 | var Q = require('q');
4 | var request = require('request');
5 | var path = require('path');
6 |
7 | request = request.defaults({
8 | json: true
9 | });
10 |
11 | var get = Q.denodeify(request.get);
12 | var post = Q.denodeify(request.post);
13 | var put = Q.denodeify(request.put);
14 | var del = Q.denodeify(request.del);
15 |
16 | // The thing we're testing
17 | var Interfake = require('..');
18 | var interfake;
19 |
20 | describe('Interfake JavaScript API', function() {
21 | beforeEach(function() {
22 | interfake = new Interfake();
23 | });
24 | afterEach(function() {
25 | if (interfake) {
26 | interfake.stop();
27 | }
28 | });
29 | describe('#listen', function() {
30 | it('should should support a callback', function(done) {
31 | interfake.listen(3000, done);
32 | });
33 | });
34 | describe('#createRoute()', function() {
35 | describe('when a GET endpoint is specified', function () {
36 | beforeEach(function (done) {
37 | interfake.createRoute({
38 | request: {
39 | url: '/test/it/out',
40 | method: 'get'
41 | },
42 | response: {
43 | code: 200,
44 | body: {
45 | hi: 'there'
46 | }
47 | }
48 | });
49 | interfake.listen(3000, done);
50 | });
51 |
52 | it('should not create unexpected endpoints', function(done) {
53 | request({ url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
54 | assert.equal(response.statusCode, 404);
55 | assert(body.match('Cannot GET /donottest/it/out'));
56 | done();
57 | });
58 | });
59 |
60 | it('should not advertise unexpected endpoints', function(done) {
61 | request({ method : 'options', url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
62 | assert.equal(response.statusCode, 404);
63 | assert(body.match('Cannot OPTIONS /donottest/it/out'));
64 | done();
65 | });
66 | });
67 |
68 | it('should create one GET endpoint', function(done) {
69 | request('http://localhost:3000/test/it/out', function(error, response, body) {
70 | assert.equal(response.statusCode, 200);
71 | assert.equal(body.hi, 'there');
72 | done();
73 | });
74 | });
75 |
76 | it('should advertise the GET in an OPTIONS request', function(done) {
77 | request({ method : 'options', url : 'http://localhost:3000/test/it/out' }, function(error, response, body) {
78 | assert.equal(response.statusCode, 200);
79 | assert.equal(response.headers['access-control-allow-methods'], 'GET, OPTIONS');
80 | assert.equal(response.headers['access-control-allow-origin'], '*');
81 | done();
82 | });
83 | });
84 |
85 | describe('when a POST option is added to the same endpoint', function () {
86 | beforeEach(function () {
87 | interfake.createRoute({
88 | request: {
89 | url: '/test/it/out',
90 | method: 'post'
91 | },
92 | response: {
93 | code: 200,
94 | body: {
95 | hi: 'there'
96 | }
97 | }
98 | });
99 | });
100 |
101 | it('should advertise the GET and the POST in an OPTIONS request', function(done) {
102 | request({ method : 'options', url : 'http://localhost:3000/test/it/out' }, function(error, response, body) {
103 | assert.equal(response.statusCode, 200);
104 | assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, OPTIONS');
105 | assert.equal(response.headers['access-control-allow-origin'], '*');
106 | done();
107 | });
108 | });
109 |
110 | describe('when a POST option is added to the same endpoint', function () {
111 | beforeEach(function () {
112 | interfake.createRoute({
113 | request: {
114 | url: '/test/it/out',
115 | method: 'patch'
116 | },
117 | response: {
118 | code: 200,
119 | body: {
120 | hi: 'there'
121 | }
122 | }
123 | });
124 | });
125 |
126 | it('should advertise the GET, POST and PATCH in an OPTIONS request', function(done) {
127 | request({ method : 'options', url : 'http://localhost:3000/test/it/out' }, function(error, response, body) {
128 | assert.equal(response.statusCode, 200);
129 | assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, PATCH, OPTIONS');
130 | assert.equal(response.headers['access-control-allow-origin'], '*');
131 | done();
132 | });
133 | });
134 | });
135 | });
136 | });
137 |
138 | describe('when the URL is flagged as being a regular expression (in string format)', function () {
139 | beforeEach(function (done) {
140 | interfake.createRoute({
141 | request: {
142 | url: {
143 | pattern : '/test/it/(up|out)',
144 | regexp : true
145 | },
146 | method: 'get'
147 | },
148 | response: {
149 | code: 200,
150 | body: {
151 | hi: 'there'
152 | }
153 | }
154 | });
155 | interfake.listen(3000, done);
156 | });
157 |
158 | it('should not create unexpected endpoints', function(done) {
159 | request({ url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
160 | assert.equal(response.statusCode, 404);
161 | assert(body.match('Cannot GET /donottest/it/out'))
162 | done();
163 | });
164 | });
165 |
166 | it('should not advertise unexpected endpoints', function(done) {
167 | request({ method : 'options', url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
168 | assert.equal(response.statusCode, 404);
169 | assert(body.match('Cannot OPTIONS /donottest/it/out'))
170 | done();
171 | });
172 | });
173 |
174 | it('should allow requests to one varition on the regular expression', function(done) {
175 | request('http://localhost:3000/test/it/out', function(error, response, body) {
176 | assert.equal(response.statusCode, 200);
177 | assert.equal(body.hi, 'there');
178 | done();
179 | });
180 | });
181 |
182 | it('should allow requests to another varition on the regular expression', function(done) {
183 | request('http://localhost:3000/test/it/up', function(error, response, body) {
184 | assert.equal(response.statusCode, 200);
185 | assert.equal(body.hi, 'there');
186 | done();
187 | });
188 | });
189 |
190 | it('should advertise the GET in an OPTIONS request for the first variation', function(done) {
191 | request({ method : 'options', url : 'http://localhost:3000/test/it/out' }, function(error, response, body) {
192 | assert.equal(response.statusCode, 200);
193 | assert.equal(response.headers['access-control-allow-methods'], 'GET, OPTIONS');
194 | assert.equal(response.headers['access-control-allow-origin'], '*');
195 | done();
196 | });
197 | });
198 |
199 | it('should advertise the GET in an OPTIONS request for the second variation', function(done) {
200 | request({ method : 'options', url : 'http://localhost:3000/test/it/up' }, function(error, response, body) {
201 | assert.equal(response.statusCode, 200);
202 | assert.equal(response.headers['access-control-allow-methods'], 'GET, OPTIONS');
203 | assert.equal(response.headers['access-control-allow-origin'], '*');
204 | done();
205 | });
206 | });
207 | });
208 |
209 | describe('when the URL is already a regular expression', function () {
210 | beforeEach(function (done) {
211 | interfake.createRoute({
212 | request: {
213 | url: /\/test\/it\/(up|out)/,
214 | method: 'get'
215 | },
216 | response: {
217 | code: 200,
218 | body: {
219 | hi: 'there'
220 | }
221 | }
222 | });
223 | interfake.listen(3000, done);
224 | });
225 |
226 | it('should not create unexpected endpoints', function(done) {
227 | request({ url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
228 | assert.equal(response.statusCode, 404);
229 | assert(body.match('Cannot GET /donottest/it/out'))
230 | done();
231 | });
232 | });
233 |
234 | it('should not advertise unexpected endpoints', function(done) {
235 | request({ method : 'options', url : 'http://localhost:3000/donottest/it/out', json : false }, function(error, response, body) {
236 | assert.equal(response.statusCode, 404);
237 | assert(body.match('Cannot OPTIONS /donottest/it/out'))
238 | done();
239 | });
240 | });
241 |
242 | it('should allow requests to one varition on the regular expression', function(done) {
243 | request('http://localhost:3000/test/it/out', function(error, response, body) {
244 | assert.equal(response.statusCode, 200);
245 | assert.equal(body.hi, 'there');
246 | done();
247 | });
248 | });
249 |
250 | it('should allow requests to another varition on the regular expression', function(done) {
251 | request('http://localhost:3000/test/it/up', function(error, response, body) {
252 | assert.equal(response.statusCode, 200);
253 | assert.equal(body.hi, 'there');
254 | done();
255 | });
256 | });
257 |
258 | it('should advertise the GET in an OPTIONS request for the first variation', function(done) {
259 | request({ method : 'options', url : 'http://localhost:3000/test/it/out' }, function(error, response, body) {
260 | assert.equal(response.statusCode, 200);
261 | assert.equal(response.headers['access-control-allow-methods'], 'GET, OPTIONS');
262 | assert.equal(response.headers['access-control-allow-origin'], '*');
263 | done();
264 | });
265 | });
266 |
267 | it('should advertise the GET in an OPTIONS request for the second variation', function(done) {
268 | request({ method : 'options', url : 'http://localhost:3000/test/it/up' }, function(error, response, body) {
269 | assert.equal(response.statusCode, 200);
270 | assert.equal(response.headers['access-control-allow-methods'], 'GET, OPTIONS');
271 | assert.equal(response.headers['access-control-allow-origin'], '*');
272 | done();
273 | });
274 | });
275 | });
276 |
277 | it('should create one GET endpoint which returns custom headers', function(done) {
278 | interfake.createRoute({
279 | request: {
280 | url: '/test',
281 | method: 'get'
282 | },
283 | response: {
284 | code: 200,
285 | body: {
286 | hi: 'there'
287 | },
288 | headers: {
289 | 'X-Request-Type': 'test',
290 | 'X-lol-TEST': 'bleep'
291 | }
292 | }
293 | });
294 | interfake.listen(3000);
295 |
296 | request('http://localhost:3000/test', function(error, response, body) {
297 | assert.equal(response.statusCode, 200);
298 | assert.equal(response.headers['x-request-type'], 'test');
299 | assert.equal(response.headers['x-lol-test'], 'bleep');
300 | assert.equal(response.headers['x-undef'], undefined);
301 | assert.equal(body.hi, 'there');
302 | done();
303 | });
304 | });
305 |
306 | it('should create a GET endpoint that accepts a query parameter', function(done) {
307 | // interfake = new Interfake();
308 | interfake.createRoute({
309 | request: {
310 | url: '/wantsQueryParameter',
311 | query: {
312 | query: '1234'
313 | },
314 | method: 'get'
315 |
316 | },
317 | response: {
318 | code: 200,
319 | body: {
320 | high: 'hoe'
321 | }
322 | }
323 | });
324 | interfake.listen(3000);
325 |
326 | request('http://localhost:3000/wantsQueryParameter?query=1234', function(error, response, body) {
327 | assert.equal(error, undefined);
328 | assert.equal(response.statusCode, 200);
329 | assert.equal(body.high, 'hoe');
330 | done();
331 | });
332 | });
333 |
334 | it('should create a GET endpoint that accepts a query array parameter', function(done) {
335 | interfake.createRoute({
336 | request: {
337 | url: '/wantsQueryArrayParameter',
338 | query: {
339 | pages: ['1', '2']
340 | },
341 | method: 'get'
342 | },
343 | response: {
344 | code: 200,
345 | body: {
346 | high: 'hoe'
347 | }
348 | }
349 | });
350 | interfake.listen(3000);
351 |
352 | request('http://localhost:3000/wantsQueryArrayParameter?pages=1&pages=2', function(error, response, body) {
353 | assert.equal(error, undefined);
354 | assert.equal(response.statusCode, 200);
355 | assert.equal(body.high, 'hoe');
356 | done();
357 | });
358 | });
359 |
360 | it('should create one GET endpoint accepting query parameters with different responses', function() {
361 | interfake.createRoute({
362 | request: {
363 | url: '/wantsQueryParameter',
364 | query: {
365 | query: '1234'
366 | },
367 | method: 'get'
368 |
369 | },
370 | response: {
371 | code: 200,
372 | body: {
373 | high: 'hoe'
374 | }
375 | }
376 | });
377 | interfake.createRoute({
378 | request: {
379 | url: '/wantsQueryParameter',
380 | query: {
381 | query: '5678',
382 | anotherQuery: '4321'
383 | },
384 | method: 'get'
385 | },
386 | response: {
387 | code: 200,
388 | body: {
389 | loan: 'shark'
390 | }
391 | }
392 | });
393 | interfake.listen(3000);
394 |
395 | return Q.all([get('http://localhost:3000/wantsQueryParameter?query=1234'),
396 | get('http://localhost:3000/wantsQueryParameter?anotherQuery=4321&query=5678'),
397 | get('http://localhost:3000/wantsQueryParameter')
398 | ]).then(function(results) {
399 | assert.equal(results[0][0].statusCode, 200);
400 | assert.equal(results[0][1].high, 'hoe');
401 | assert.equal(results[1][0].statusCode, 200);
402 | assert.equal(results[1][1].loan, 'shark');
403 | assert.equal(results[2][0].statusCode, 404);
404 | });
405 | });
406 |
407 | it('should create one GET endpoint with a querystring in the url with different responses', function() {
408 | interfake.createRoute({
409 | request: {
410 | url: '/wantsQueryParameter?query=1234',
411 | method: 'get'
412 | },
413 | response: {
414 | code: 200,
415 | body: {
416 | high: 'hoe'
417 | }
418 | }
419 | });
420 | interfake.createRoute({
421 | request: {
422 | url: '/wantsQueryParameter?anotherQuery=5678',
423 | method: 'get'
424 | },
425 | response: {
426 | code: 200,
427 | body: {
428 | loan: 'shark'
429 | }
430 | }
431 | });
432 | interfake.listen(3000);
433 |
434 | return Q.all([get('http://localhost:3000/wantsQueryParameter?query=1234'),
435 | get('http://localhost:3000/wantsQueryParameter?anotherQuery=5678'),
436 | get('http://localhost:3000/wantsQueryParameter')
437 | ]).then(function(results) {
438 | assert.equal(results[0][0].statusCode, 200);
439 | assert.equal(results[0][1].high, 'hoe');
440 | assert.equal(results[1][0].statusCode, 200);
441 | assert.equal(results[1][1].loan, 'shark');
442 | assert.equal(results[2][0].statusCode, 404);
443 | });
444 | });
445 |
446 | it('should create one GET endpoint accepting query parameters using the url and options', function() {
447 | interfake.createRoute({
448 | request: {
449 | url: '/wantsQueryParameter?query=1234',
450 | query: {
451 | page: 1
452 | },
453 | method: 'get'
454 | },
455 | response: {
456 | code: 200,
457 | body: {
458 | high: 'hoe'
459 | }
460 | }
461 | });
462 | interfake.createRoute({
463 | request: {
464 | url: '/wantsQueryParameter?query=1234',
465 | query: {
466 | page: 2
467 | },
468 | method: 'get'
469 | },
470 | response: {
471 | code: 200,
472 | body: {
473 | loan: 'shark'
474 | }
475 | }
476 | });
477 | interfake.listen(3000);
478 |
479 | return Q.all([get('http://localhost:3000/wantsQueryParameter?query=1234&page=1'),
480 | get('http://localhost:3000/wantsQueryParameter?query=1234&page=2'),
481 | get('http://localhost:3000/wantsQueryParameter')
482 | ]).then(function(results) {
483 | assert.equal(results[0][0].statusCode, 200);
484 | assert.equal(results[0][1].high, 'hoe');
485 | assert.equal(results[1][0].statusCode, 200);
486 | assert.equal(results[1][1].loan, 'shark');
487 | assert.equal(results[2][0].statusCode, 404);
488 | });
489 | });
490 |
491 | it('should create three GET endpoints with different status codes', function(done) {
492 | interfake.createRoute({
493 | request: {
494 | url: '/test1',
495 | method: 'get'
496 | },
497 | response: {
498 | code: 200,
499 | body: {
500 | its: 'one'
501 | }
502 | }
503 | });
504 | interfake.createRoute({
505 | request: {
506 | url: '/test2',
507 | method: 'get'
508 | },
509 | response: {
510 | code: 300,
511 | body: {
512 | its: 'two'
513 | }
514 | }
515 | });
516 | interfake.createRoute({
517 | request: {
518 | url: '/test3',
519 | method: 'get'
520 | },
521 | response: {
522 | code: 500,
523 | body: {
524 | its: 'three'
525 | }
526 | }
527 | });
528 | interfake.listen(3000);
529 |
530 | Q.all([get('http://localhost:3000/test1'), get('http://localhost:3000/test2'), get('http://localhost:3000/test3')])
531 | .then(function(results) {
532 | assert.equal(results[0][0].statusCode, 200);
533 | assert.equal(results[0][1].its, 'one');
534 | assert.equal(results[1][0].statusCode, 300);
535 | assert.equal(results[1][1].its, 'two');
536 | assert.equal(results[2][0].statusCode, 500);
537 | assert.equal(results[2][1].its, 'three');
538 | done();
539 | });
540 | });
541 |
542 | it('should create a dynamic endpoint', function(done) {
543 | interfake.createRoute({
544 | request: {
545 | url: '/dynamic',
546 | method: 'post'
547 | },
548 | response: {
549 | code: 201,
550 | body: {}
551 | },
552 | afterResponse: {
553 | endpoints: [{
554 | request: {
555 | url: '/dynamic/1',
556 | method: 'get'
557 | },
558 | response: {
559 | code: 200,
560 | body: {}
561 | }
562 | }]
563 | }
564 | });
565 | interfake.listen(3000);
566 |
567 | get('http://localhost:3000/dynamic/1')
568 | .then(function(results) {
569 | assert.equal(results[0].statusCode, 404);
570 | return post('http://localhost:3000/dynamic');
571 | })
572 | .then(function(results) {
573 | assert.equal(results[0].statusCode, 201);
574 | return get('http://localhost:3000/dynamic/1');
575 | })
576 | .then(function(results) {
577 | assert.equal(results[0].statusCode, 200);
578 | done();
579 | })
580 | .done();
581 | });
582 |
583 | it('should create a dynamic endpoint within a dynamic endpoint', function(done) {
584 | interfake.createRoute({
585 | request: {
586 | url: '/dynamic',
587 | method: 'post'
588 | },
589 | response: {
590 | code: 201,
591 | body: {
592 | all: 'done'
593 | }
594 | },
595 | afterResponse: {
596 | endpoints: [{
597 | request: {
598 | url: '/dynamic/1',
599 | method: 'get'
600 | },
601 | response: {
602 | code: 200,
603 | body: {
604 | yes: 'indeedy'
605 | }
606 | }
607 | }, {
608 | request: {
609 | url: '/dynamic/1',
610 | method: 'put'
611 | },
612 | response: {
613 | code: 200,
614 | body: {}
615 | },
616 | afterResponse: {
617 | endpoints: [{
618 | request: {
619 | url: '/dynamic/1',
620 | method: 'get'
621 | },
622 | response: {
623 | code: 200,
624 | body: {
625 | yes: 'indiddly'
626 | }
627 | }
628 | }]
629 | }
630 | }]
631 | }
632 | });
633 | interfake.listen(3000);
634 |
635 | get('http://localhost:3000/dynamic/1')
636 | .then(function(results) {
637 | assert.equal(results[0].statusCode, 404);
638 | return post('http://localhost:3000/dynamic');
639 | })
640 | .then(function(results) {
641 | assert.equal(results[0].statusCode, 201);
642 | assert.equal(results[1].all, 'done');
643 | return get('http://localhost:3000/dynamic/1');
644 | })
645 | .then(function(results) {
646 | assert.equal(results[0].statusCode, 200);
647 | assert.equal(results[1].yes, 'indeedy');
648 | return put('http://localhost:3000/dynamic/1');
649 | })
650 | .then(function(results) {
651 | assert.equal(results[0].statusCode, 200);
652 | return get('http://localhost:3000/dynamic/1');
653 | })
654 | .then(function(results) {
655 | assert.equal(results[0].statusCode, 200);
656 | assert.equal(results[1].yes, 'indiddly');
657 | done();
658 | });
659 | });
660 |
661 | it('should return JSONP if requested', function(done) {
662 | interfake.createRoute({
663 | request: {
664 | url: '/stuff',
665 | method: 'get'
666 | },
667 | response: {
668 | code: 200,
669 | body: {
670 | stuff: 'hello'
671 | }
672 | }
673 | });
674 | interfake.listen(3000);
675 |
676 | get('http://localhost:3000/stuff?callback=yo')
677 | .then(function(results) {
678 | assert.equal('hello', 'yo(' + JSON.stringify({
679 | stuff: 'hello'
680 | }) + ');');
681 | done();
682 | });
683 |
684 | request('http://localhost:3000/stuff?callback=yo', function(error, response, body) {
685 | assert.equal(body, 'yo(' + JSON.stringify({
686 | stuff: 'hello'
687 | }) + ');');
688 | done();
689 | });
690 | });
691 |
692 | it('should create a proxy endpoint with a GET method', function(done) {
693 | var proxiedInterfake = new Interfake();
694 | proxiedInterfake.get('/whatever').status(404).body({
695 | message: 'This is something you proxied!'
696 | }).responseHeaders({
697 | 'loving-you':'Isnt the right thing to do'
698 | });
699 | proxiedInterfake.listen(3050);
700 | interfake.createRoute({
701 | request: {
702 | url: '/stuff',
703 | method: 'get'
704 | },
705 | response: {
706 | proxy: 'http://localhost:3050/whatever'
707 | }
708 | });
709 | interfake.listen(3000);
710 |
711 | request('http://localhost:3000/stuff', function (error, response, body) {
712 | assert.equal(response.statusCode, 404);
713 | assert.equal(body.message, 'This is something you proxied!');
714 | assert.equal(response.headers['loving-you'], 'Isnt the right thing to do');
715 | done();
716 | });
717 | afterEach(function () {
718 | proxiedInterfake.stop();
719 | });
720 | });
721 |
722 | it('should create a proxy endpoint with a GET method which sends the specified headers', function(done) {
723 | var proxiedInterfake = new Interfake({
724 | onRequest: function (req) {
725 | assert.equal(req.get('Authorization'), 'Basic username:password');
726 | done();
727 | }
728 | });
729 | proxiedInterfake.get('/whatever').status(404).body({
730 | message: 'This is something you proxied!'
731 | }).responseHeaders({
732 | 'loving-you':'Isnt the right thing to do'
733 | });
734 | proxiedInterfake.listen(3050);
735 | interfake.createRoute({
736 | request: {
737 | url: '/stuff',
738 | method: 'get'
739 | },
740 | response: {
741 | proxy: {
742 | url:'http://localhost:3050/whatever',
743 | headers:{
744 | 'Authorization': 'Basic username:password'
745 | }
746 | }
747 | }
748 | });
749 | interfake.listen(3000);
750 |
751 | request('http://localhost:3000/stuff', function (error, response, body) {
752 | assert.equal(response.statusCode, 404);
753 | assert.equal(body.message, 'This is something you proxied!');
754 | assert.equal(response.headers['loving-you'], 'Isnt the right thing to do');
755 | });
756 |
757 | afterEach(function () {
758 | proxiedInterfake.stop();
759 | });
760 | });
761 |
762 | it('should create a proxy endpoint with a GET method and retain query parameters', function(done) {
763 | var proxiedInterfake = new Interfake();
764 | proxiedInterfake.get('/whatever').query({
765 | q: /\w+/,
766 | isit: /\w+/,
767 | youre: /\w+/
768 | }).status(404).body({
769 | message: 'I can see it in your eyes'
770 | });
771 | proxiedInterfake.listen(3050);
772 | interfake.createRoute({
773 | request: {
774 | url: '/stuff',
775 | method: 'get',
776 | query: {
777 | q: /\w+/,
778 | isit: /\w+/,
779 | youre: /\w+/
780 | }
781 | },
782 | response: {
783 | proxy: 'http://localhost:3050/whatever'
784 | }
785 | });
786 | interfake.listen(3000);
787 |
788 | request('http://localhost:3000/stuff?q=hello&isit=me&youre=lookingfor', function (error, response, body) {
789 | assert.equal(response.statusCode, 404);
790 | assert.equal(body.message, 'I can see it in your eyes');
791 | done();
792 | });
793 | afterEach(function () {
794 | proxiedInterfake.stop();
795 | });
796 | });
797 |
798 | it('should create a proxy endpoint with a GET method and retain query parameters and their value', function(done) {
799 | var proxiedInterfake = new Interfake();
800 | proxiedInterfake.get('/whatever').query({
801 | q: 'hello',
802 | isit: 'me',
803 | youre: 'lookingfor'
804 | }).status(404).body({
805 | message: 'I can see it in your eyes'
806 | });
807 | proxiedInterfake.listen(3050);
808 | interfake.createRoute({
809 | request: {
810 | url: '/stuff',
811 | method: 'get',
812 | query: {
813 | q: /\w+/,
814 | isit: /\w+/,
815 | youre: /\w+/
816 | }
817 | },
818 | response: {
819 | proxy: 'http://localhost:3050/whatever'
820 | }
821 | });
822 | interfake.listen(3000);
823 |
824 | request('http://localhost:3000/stuff?q=hello&isit=me&youre=lookingfor', function (error, response, body) {
825 | assert.equal(response.statusCode, 404);
826 | assert.equal(body.message, 'I can see it in your eyes');
827 | done();
828 | });
829 | afterEach(function () {
830 | proxiedInterfake.stop();
831 | });
832 | });
833 |
834 | it('should create a proxy endpoint with a POST method', function(done) {
835 | var proxiedInterfake = new Interfake();
836 | proxiedInterfake.post('/whatever').status(404).body({
837 | message: 'This is something you proxied!'
838 | }).responseHeaders({
839 | 'loving-you':'Isnt the right thing to do'
840 | });
841 | proxiedInterfake.listen(3050);
842 | interfake.createRoute({
843 | request: {
844 | url: '/stuff',
845 | method: 'post'
846 | },
847 | response: {
848 | proxy: 'http://localhost:3050/whatever'
849 | }
850 | });
851 | interfake.listen(3000);
852 |
853 | request.post('http://localhost:3000/stuff', function (error, response, body) {
854 | assert.equal(response.statusCode, 404);
855 | assert.equal(body.message, 'This is something you proxied!');
856 | assert.equal(response.headers['loving-you'], 'Isnt the right thing to do');
857 | done();
858 | });
859 | afterEach(function () {
860 | proxiedInterfake.stop();
861 | });
862 | });
863 |
864 | it('should create a proxy endpoint which supports JSONP', function(done) {
865 | var proxiedInterfake = new Interfake();
866 | proxiedInterfake.get('/whatever').status(404).body({
867 | message: 'This is something you proxied!'
868 | });
869 | proxiedInterfake.listen(3050);
870 | interfake.createRoute({
871 | request: {
872 | url: '/stuff',
873 | method: 'get'
874 | },
875 | response: {
876 | proxy: 'http://localhost:3050/whatever'
877 | }
878 | });
879 | interfake.listen(3000);
880 |
881 | request('http://localhost:3000/stuff?callback=whatever', function (error, response, body) {
882 | assert.equal(response.statusCode, 404);
883 | assert.equal(body, 'whatever(' + JSON.stringify({
884 | message: 'This is something you proxied!'
885 | }) + ');');
886 | proxiedInterfake.stop();
887 | done();
888 | });
889 | });
890 |
891 | it('should create one GET endpoint with support for delaying the response', function(done) {
892 | var enoughTimeHasPassed = false;
893 | var _this = this;
894 | this.slow(500);
895 | interfake.createRoute({
896 | request: {
897 | url: '/test',
898 | method: 'get'
899 | },
900 | response: {
901 | code: 200,
902 | delay: 50,
903 | body: {
904 | hi: 'there'
905 | }
906 | }
907 | });
908 | interfake.listen(3000);
909 | setTimeout(function() {
910 | enoughTimeHasPassed = true;
911 | }, 50);
912 | request('http://localhost:3000/test', function(error, response, body) {
913 | assert.equal(response.statusCode, 200);
914 | assert.equal(body.hi, 'there');
915 | if (!enoughTimeHasPassed) {
916 | throw new Error('Response wasn\'t delay for long enough');
917 | }
918 | done();
919 | });
920 | });
921 |
922 | it('should create one GET endpoint with support for delaying the response with a delay range', function(done) {
923 | var enoughTimeHasPassed = false;
924 | var _this = this;
925 | var timeout;
926 | var tookTooLong = false;
927 | this.slow(500);
928 | interfake.createRoute({
929 | request: {
930 | url: '/test',
931 | method: 'get'
932 | },
933 | response: {
934 | code: 200,
935 | delay: '20..50',
936 | body: {
937 | hi: 'there'
938 | }
939 | }
940 | });
941 | interfake.listen(3000);
942 | setTimeout(function() {
943 | enoughTimeHasPassed = true;
944 | }, 20);
945 | timeout = setTimeout(function() {
946 | tookTooLong = true;
947 | }, 60);
948 | request('http://localhost:3000/test', function(error, response, body) {
949 | clearTimeout(timeout);
950 | if (!enoughTimeHasPassed) {
951 | throw new Error('Response wasn\'t delay for long enough');
952 | }
953 | if (tookTooLong) {
954 | throw new Error('Response was delayed for too long');
955 | }
956 | done();
957 | });
958 | });
959 |
960 | describe('response with { echo : true }', function () {
961 | it('should return the request body', function (done) {
962 | interfake.createRoute({
963 | request: {
964 | url: '/stuff',
965 | method: 'post'
966 | },
967 | response: {
968 | echo : true
969 | }
970 | });
971 |
972 | interfake.listen(3000);
973 |
974 | request.post({
975 | url :'http://localhost:3000/stuff',
976 | json : true,
977 | body : {
978 | message : 'Echo!'
979 | },
980 | },
981 | function (error, response, body) {
982 | assert.equal(response.statusCode, 200);
983 | assert.equal(body.message, 'Echo!');
984 | done();
985 | });
986 | });
987 | });
988 |
989 | describe('#removeRoute', function () {
990 | it('should remove a created route based on a reference to the Route object', function (done) {
991 | var route = interfake.createRoute({
992 | request: {
993 | url: '/remove/me',
994 | method: 'get'
995 | },
996 | response: {
997 | code: 200,
998 | body: {
999 | please: 'remove'
1000 | }
1001 | }
1002 | });
1003 | interfake.createRoute({
1004 | request: {
1005 | url: '/keep/me',
1006 | method: 'get'
1007 | },
1008 | response: {
1009 | code: 200,
1010 | body: {
1011 | please: 'keep'
1012 | }
1013 | }
1014 | });
1015 | interfake.listen(3000);
1016 |
1017 | get('http://localhost:3000/remove/me')
1018 | .then(function(results) {
1019 | assert.equal(results[0].statusCode, 200);
1020 | assert.equal(results[1].please, 'remove');
1021 | interfake.removeRoute(route);
1022 | return get('http://localhost:3000/remove/me');
1023 | })
1024 | .then(function (results) {
1025 | assert.equal(results[0].statusCode, 404);
1026 | assert(results[1].match('Cannot GET /remove/me'))
1027 | return get('http://localhost:3000/keep/me');
1028 | })
1029 | .then(function (results) {
1030 | assert.equal(results[0].statusCode, 200);
1031 | assert.equal(results[1].please, 'keep');
1032 | done();
1033 | })
1034 | .done();
1035 | });
1036 |
1037 | it('should remove a created route based on the descriptor', function (done) {
1038 | var routeDescriptor = {
1039 | request: {
1040 | url: '/remove/me',
1041 | method: 'get'
1042 | },
1043 | response: {
1044 | code: 200,
1045 | body: {
1046 | please: 'remove'
1047 | }
1048 | }
1049 | };
1050 |
1051 | interfake.createRoute(routeDescriptor);
1052 |
1053 | interfake.createRoute({
1054 | request: {
1055 | url: '/keep/me',
1056 | method: 'get'
1057 | },
1058 | response: {
1059 | code: 200,
1060 | body: {
1061 | please: 'keep'
1062 | }
1063 | }
1064 | });
1065 |
1066 | interfake.listen(3000);
1067 |
1068 | get('http://localhost:3000/remove/me')
1069 | .then(function(results) {
1070 | assert.equal(results[0].statusCode, 200);
1071 | assert.equal(results[1].please, 'remove');
1072 | interfake.removeRoute(routeDescriptor);
1073 | return get('http://localhost:3000/remove/me');
1074 | })
1075 | .then(function (results) {
1076 | assert.equal(results[0].statusCode, 404);
1077 | assert(results[1].match('Cannot GET /remove/me'))
1078 | return get('http://localhost:3000/keep/me');
1079 | })
1080 | .then(function (results) {
1081 | assert.equal(results[0].statusCode, 200);
1082 | assert.equal(results[1].please, 'keep');
1083 | done();
1084 | })
1085 | .done();
1086 | });
1087 | });
1088 | });
1089 |
1090 | // Testing the API root stuff
1091 | describe('#Interfake({ path: [String] })', function() {
1092 | it('should set the root path of the API', function(done) {
1093 | interfake = new Interfake({
1094 | path: '/api'
1095 | });
1096 | interfake.get('/endpoint').status(200).creates.get('/moar-endpoints');
1097 | interfake.listen(3000);
1098 |
1099 | Q.all([get('http://localhost:3000/api/endpoint'), get('http://localhost:3000/endpoint')])
1100 | .then(function(results) {
1101 | assert.equal(results[0][0].statusCode, 200);
1102 | assert.equal(results[1][0].statusCode, 404);
1103 | return get('http://localhost:3000/api/endpoint');
1104 | })
1105 | .then(function(results) {
1106 | assert.equal(results[0].statusCode, 200);
1107 | return get('http://localhost:3000/api/moar-endpoints');
1108 | })
1109 | .then(function(results) {
1110 | assert.equal(results[0].statusCode, 200);
1111 | done();
1112 | });
1113 | });
1114 | });
1115 |
1116 | describe('#loadFile()', function() {
1117 | it('should load a JSON file and the routes within it', function(done) {
1118 | interfake = new Interfake();
1119 | interfake.loadFile('./tests/loadFileTest-1.json');
1120 | interfake.listen(3000);
1121 |
1122 | get('http://localhost:3000/whattimeisit')
1123 | .then(function(results) {
1124 | assert.equal(results[0].statusCode, 200);
1125 | assert.equal(results[1].theTime, 'Adventure Time!');
1126 | done();
1127 | })
1128 | .done();
1129 | });
1130 |
1131 | it('should load a JSON file and the with regex routes in it', function(done) {
1132 | interfake = new Interfake();
1133 | interfake.loadFile('./tests/loadFileTest-3.json');
1134 | interfake.listen(3000);
1135 |
1136 | get('http://localhost:3000/whostheboss')
1137 | .then(function(results) {
1138 | assert.equal(results[0].statusCode, 200);
1139 | assert.equal(results[1].theBoss, 'Angela');
1140 | return get('http://localhost:3000/whostheleader');
1141 | })
1142 | .then(function(results) {
1143 | assert.equal(results[0].statusCode, 200);
1144 | assert.equal(results[1].theBoss, 'Angela');
1145 | done();
1146 | })
1147 | .done();
1148 | });
1149 |
1150 | describe('#unload', function () {
1151 | it('should unload a single file', function (done) {
1152 | interfake = new Interfake();
1153 | var firstFile = interfake.loadFile('./tests/loadFileTest-1.json');
1154 | interfake.loadFile('./tests/loadFileTest-2.json');
1155 | interfake.listen(3000);
1156 |
1157 | get('http://localhost:3000/whattimeisit')
1158 | .then(function(results) {
1159 | assert.equal(results[0].statusCode, 200);
1160 | assert.equal(results[1].theTime, 'Adventure Time!');
1161 | firstFile.unload();
1162 | return get('http://localhost:3000/whattimeisit');
1163 | })
1164 | .then(function (results) {
1165 | assert.equal(results[0].statusCode, 404);
1166 | assert(results[1].match('Cannot GET /whattimeisit'))
1167 | interfake.loadFile('./tests/loadFileTest-2.json');
1168 | return get('http://localhost:3000/whostheboss');
1169 | })
1170 | .then(function (results) {
1171 | assert.equal(results[0].statusCode, 200);
1172 | assert.equal(results[1].theBoss, 'Angela');
1173 | done();
1174 | })
1175 | .done();
1176 | });
1177 | });
1178 |
1179 | describe('#unload', function () {
1180 | it('should be able to unload a single file (note, this cannot actually test functionality, just make sure nothing breaks)', function (done) {
1181 | interfake = new Interfake();
1182 | var firstFile = interfake.loadFile('./tests/loadFileTest-1.json');
1183 | interfake.loadFile('./tests/loadFileTest-2.json');
1184 | interfake.listen(3000);
1185 |
1186 | get('http://localhost:3000/whattimeisit')
1187 | .then(function(results) {
1188 | assert.equal(results[0].statusCode, 200);
1189 | assert.equal(results[1].theTime, 'Adventure Time!');
1190 | firstFile.reload();
1191 | return get('http://localhost:3000/whattimeisit');
1192 | })
1193 | .then(function (results) {
1194 | assert.equal(results[0].statusCode, 200);
1195 | assert.equal(results[1].theTime, 'Adventure Time!');
1196 | interfake.loadFile('./tests/loadFileTest-2.json');
1197 | return get('http://localhost:3000/whostheboss');
1198 | })
1199 | .then(function (results) {
1200 | assert.equal(results[0].statusCode, 200);
1201 | assert.equal(results[1].theBoss, 'Angela');
1202 | done();
1203 | })
1204 | .done();
1205 | });
1206 | });
1207 |
1208 | describe('#clearAllRoutes()', function() {
1209 | it('should load a file and then clear the routes out', function(done) {
1210 | interfake = new Interfake();
1211 | interfake.loadFile('./tests/loadFileTest-1.json');
1212 | interfake.listen(3000);
1213 |
1214 | get('http://localhost:3000/whattimeisit')
1215 | .then(function(results) {
1216 | assert.equal(results[0].statusCode, 200);
1217 | assert.equal(results[1].theTime, 'Adventure Time!');
1218 | interfake.clearAllRoutes();
1219 | return get('http://localhost:3000/whattimeisit');
1220 | })
1221 | .then(function (results) {
1222 | assert.equal(results[0].statusCode, 404);
1223 | assert(results[1].match('Cannot GET /whattimeisit'))
1224 | done();
1225 | })
1226 | .done();
1227 | });
1228 |
1229 | it('should load a file and clear the routes out then load another one', function(done) {
1230 | interfake = new Interfake();
1231 | interfake.loadFile('./tests/loadFileTest-1.json');
1232 | interfake.listen(3000);
1233 |
1234 | get('http://localhost:3000/whattimeisit')
1235 | .then(function(results) {
1236 | assert.equal(results[0].statusCode, 200);
1237 | assert.equal(results[1].theTime, 'Adventure Time!');
1238 | interfake.clearAllRoutes();
1239 | return get('http://localhost:3000/whattimeisit');
1240 | })
1241 | .then(function (results) {
1242 | assert.equal(results[0].statusCode, 404);
1243 | assert(results[1].match('Cannot GET /whattimeisit'))
1244 | interfake.loadFile('./tests/loadFileTest-2.json');
1245 | return get('http://localhost:3000/whostheboss');
1246 | })
1247 | .then(function (results) {
1248 | assert.equal(results[0].statusCode, 200);
1249 | assert.equal(results[1].theBoss, 'Angela');
1250 | done();
1251 | })
1252 | .done();
1253 | });
1254 | });
1255 | });
1256 |
1257 | describe('#serveStatic()', function () {
1258 | it('should serve up a file in the given folder', function (done) {
1259 | var filePath = path.join(__dirname, './');
1260 | interfake.serveStatic('/static', filePath);
1261 | interfake.listen(3000);
1262 |
1263 | request({ url : 'http://localhost:3000/static/static-test.txt', json : false }, function(error, response, body) {
1264 | assert.equal(response.statusCode, 200);
1265 | assert.equal(body, 'Testing');
1266 | done();
1267 | });
1268 | });
1269 | });
1270 | });
1271 |
--------------------------------------------------------------------------------
/tests/loadFileTest-1.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/whattimeisit",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "body": {
10 | "theTime": "Adventure Time!"
11 | },
12 | "headers": {
13 | "X-Powered-By": "Interfake"
14 | }
15 | }
16 | }
17 | ]
--------------------------------------------------------------------------------
/tests/loadFileTest-2.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": "/whostheboss",
5 | "method": "get"
6 | },
7 | "response": {
8 | "code": 200,
9 | "body": {
10 | "theBoss": "Angela"
11 | },
12 | "headers": {
13 | "X-Powered-By": "Interfake"
14 | }
15 | }
16 | }
17 | ]
--------------------------------------------------------------------------------
/tests/loadFileTest-3.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "url": {
5 | "pattern" : "/whosthe(boss|leader)",
6 | "regexp" : true
7 | },
8 | "method": "get"
9 | },
10 | "response": {
11 | "code": 200,
12 | "body": {
13 | "theBoss": "Angela"
14 | },
15 | "headers": {
16 | "X-Powered-By": "Interfake"
17 | }
18 | }
19 | }
20 | ]
--------------------------------------------------------------------------------
/tests/static-test.txt:
--------------------------------------------------------------------------------
1 | Testing
--------------------------------------------------------------------------------