Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

elbow

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

elbow - npm Package Compare versions

Comparing version 1.0.0 to 1.1.0

20

CHANGELOG.md

@@ -14,2 +14,20 @@ # Change Log

## 1.1.0 - 2017-07-31
Added:
* Added `options.headers`, `options.query`, `options.body` and `options.extensions`
* Added `headers`, `query`, `body`, `export` extensions in JSON schema
* Added variable expansion for `endpoint`, `headers`, `query` and `body`
extensions in JSON schema
* Added setup hook (`options.before`, `options.beforeUrl`)
* Added support for Node.js v5, v7 and v8
Changed:
* Deprecate `params` extension in JSON schema
* Replaced [JaySchema](https://github.com/natesilva/jayschema) with [Ajv](https://github.com/epoberezkin/ajv) for schema validation
* Updated dependencies
## 1.0.0 - 2016-11-16

@@ -19,3 +37,3 @@

* Added support for Node.js v4.x series
* Added support for Node.js v6.x series

@@ -22,0 +40,0 @@ Changed:

4

Gruntfile.js

@@ -40,6 +40,6 @@ "use strict";

* The MIT License (MIT)
* Copyright (c) 2015-2016 GochoMugo <mugo@forfuture.co.ke>
* Copyright (c) 2015-2017 GochoMugo <mugo@forfuture.co.ke>
*/
// npm-installed modules
// installed modules
module.exports = exports["default"];

@@ -19,5 +19,5 @@ "use strict";

var _index = require("./index");
var _2 = require(".");
var _index2 = _interopRequireDefault(_index);
var _3 = _interopRequireDefault(_2);

@@ -35,3 +35,3 @@ var _package = require("../package.json");

* The MIT License (MIT)
* Copyright (c) 2015-2016 GochoMugo <mugo@forfuture.co.ke>
* Copyright (c) 2015-2017 GochoMugo <mugo@forfuture.co.ke>
*/

@@ -42,3 +42,3 @@

// built-in modules
_commander2.default.version(_package2.default.version).option("-l, --list [schema-dir]", "list schemas").parse(process.argv);
_commander2.default.version(_package2.default.version).option("-l, --list [schema-dir]", "list schemas [./schema]").parse(process.argv);

@@ -48,3 +48,3 @@ // listing schemas

// npm-installed modules
// installed modules
if (_commander2.default.list) {

@@ -59,5 +59,5 @@ var dirpath = _commander2.default.list;

// listing schemas
_index2.default.schemas(dirpath, function (err, schemas) {
if (err) {
_cliOutput2.default.error("error listing schemas: " + err);
_3.default.schemas(dirpath, function (error, schemas) {
if (error) {
_cliOutput2.default.error("error listing schemas: " + error);
return process.exit(1);

@@ -64,0 +64,0 @@ }

@@ -23,2 +23,6 @@ "use strict";

var _ajv = require("ajv");
var _ajv2 = _interopRequireDefault(_ajv);
var _debug = require("debug");

@@ -28,5 +32,5 @@

var _jayschema = require("jayschema");
var _depd = require("depd");
var _jayschema2 = _interopRequireDefault(_jayschema);
var _depd2 = _interopRequireDefault(_depd);

@@ -47,3 +51,3 @@ var _superagent = require("superagent");

* The MIT License (MIT)
* Copyright (c) 2015-2016 GochoMugo <mugo@forfuture.co.ke>
* Copyright (c) 2015-2017 GochoMugo <mugo@forfuture.co.ke>
*/

@@ -59,23 +63,53 @@

// npm-installed modules
// installed modules
// module variables
var debug = (0, _debug2.default)("elbow:main");
var validator = new _jayschema2.default(_jayschema2.default.loaders.http);
var deprecate = (0, _depd2.default)("elbow");
var validator = new _ajv2.default({ loadSchema: loadSchema });
/*
* Loads all the Schemas into memory
/**
* Loads schema from remote server using a HTTP URI.
*
* @private
* @param {String} uri - HTTP URI to schema
* @return {Promise}
* @see https://github.com/epoberezkin/ajv#asynchronous-schema-compilation
*/
function loadSchema(uri) {
return new Promise(function (resolve, reject) {
_superagent2.default.get(uri).end(function (error, response) {
if (error || !response.ok) {
error = error || new Error(response.body);
debug("error fetching remote schema:", error);
return reject(error);
}
return resolve(response.body);
});
});
}
/**
* Loads all the Schemas into memory.
*
* @param {String} schemaDir - path to directory holding schemas
* @param {Object} [options]
* @param {String[]} [options.extensions=["json"]] Extension of schema files
* @param {Function} callback - callback(err, schemas)
*/
function requireAll(schemaDir, callback) {
function requireAll(schemaDir, options, callback) {
debug("loading schemas");
if (!callback) {
callback = options;
options = {};
}
var opts = _lodash2.default.assign({
extensions: ["json"]
}, options);
var files = void 0;
try {
files = _fs2.default.readdirSync(schemaDir);
} catch (readdirErr) {
return callback(readdirErr);
} catch (errReaddir) {
return callback(errReaddir);
}

@@ -87,5 +121,5 @@

var file = files[index];
var ext = _path2.default.extname(file).slice(1);
// if it is NOT a json file, ignore it
if (_path2.default.extname(file) !== ".json") {
if (opts.extensions.indexOf(ext) === -1) {
continue;

@@ -100,4 +134,4 @@ }

schema = require(abspath);
} catch (requireErr) {
return callback(requireErr);
} catch (errRequire) {
return callback(errRequire);
}

@@ -112,5 +146,9 @@

/*
* Determine name of param-sending function to use (on superagent) from the method
/**
* Determine name of method function to use (on superagent) from
* the method.
* This handles mapping the methods we allow in our schemas
* to actual functions on the superagent object.
*
* @private
* @param {String} method - http method e.g. "get", "post"

@@ -129,5 +167,9 @@ * @return {String} function name e.g. "send"

/*
* Determine name of param-sending function to use (on superagent) from the method
/**
* Determine name of param-sending function to use (on superagent) from
* the method in the schema.
* This handles choosing how the parameters are sent using the
* superagent instance.
*
* @private
* @param {String} method - http method e.g. "get", "post"

@@ -148,5 +190,6 @@ * @return {String} function name e.g. "send"

/*
* Create test case label
/**
* Create test case label that is shown in the Mocha UI.
*
* @private
* @param {String} method - method used in test case e.g. "get"

@@ -157,13 +200,58 @@ * @param {Object} schema - schema used in test case

function createTestCaseLabel(method, schema) {
return method.toUpperCase() + " " + (schema.endpoint || "") + " (" + schema.description + ") [" + schema.filepath + "]";
return [method.toUpperCase(), schema.endpoint, "(" + schema.description + ")", "[" + schema.filepath + "]"].join(" ");
}
/*
* Validate a Http response using schema
/**
* Expand variables, using variables from the `options` object,
* or process environment.
* Modifies the passed object in place.
*
* @private
* @param {String|Object} target - object with parameters
* @param {Object} options - test configurations
* @return {String|Object}
*/
function expandVars(target, options) {
var vars = options.vars || {};
var regexp = /\$\{(\w+)\}/g;
if (typeof target === "string") {
return _expand(target);
}
for (var key in target) {
target[key] = _expand(target[key]);
}
return target;
function _expand(val) {
var expanded = val;
var match = void 0;
while (match = regexp.exec(val)) {
var varname = match[1];
var varval = vars[varname] || process.env[varname];
if (!varval) {
debug("could not expand variable ${" + varname + "}");
continue;
}
expanded = expanded.replace("${" + varname + "}", varval);
}
return expanded;
}
}
/**
* Validate a Http response using schema.
* This handles the actual JSON schema validation.
*
* @private
* @param {Object} schema - schema used to validate against
* @param {*} response - http response
* @param {Object} options - test configurations
* @param {Function} done - called once validation is completed
*
* @TODO Remove our "extensions" i.e. any custom properties in the
* schema that we have added for the purpose of the elbow utility.
*/
function validateResponse(schema, response, done) {
function validateResponse(schema, response, options, done) {
debug("validating response for " + schema.endpoint);

@@ -174,33 +262,77 @@ // testing status code

}
return validator.validate(response.body, schema, function (errs) {
(0, _should2.default)(errs).not.be.ok();
return done();
});
return validator.compileAsync(schema).then(function (validate) {
var valid = validate(response.body);
if (!valid) {
var errors = validate.errors;
(0, _should2.default)(errors).not.be.ok();
return done(errors);
}
if (schema.export) {
var body = response.body;
options.vars = options.vars || {};
for (var dest in schema.export) {
var propPath = schema.export[dest];
_lodash2.default.set(options.vars, dest, _lodash2.default.get(body, propPath));
}
}
return done(null, response);
}).catch(done);
}
/*
* Make a Http request with objective of validating its response
/**
* Make a HTTP request against the remote server and validate the
* response.
*
* @private
* @param {String} baseUrl - base url e.g. "http://localhost:9090/"
* @param {String} method - http method to use for request e.g. "get"
* @param {Object} schema - schema used to validate response
* @param {Object} options - test configurations
* @param {Function} done - function called once request is completed
*/
function makeRequest(baseUrl, method, schema, done) {
function makeRequest(baseUrl, method, schema, options, done) {
debug("making " + method.toUpperCase() + " request to " + schema.endpoint);
var endpoint = _url2.default.resolve(baseUrl + "/", _lodash2.default.trimStart(schema.endpoint, "/"));
var endpoint = expandVars(schema.endpoint, options);
var apiPath = _url2.default.resolve(baseUrl + "/", _lodash2.default.trimStart(endpoint, "/"));
var headers = Object.assign({}, options.headers, schema.headers);
var query = Object.assign({}, options.query, schema.query);
var body = Object.assign({}, options.body, schema.body);
return _superagent2.default[getMethodFuncName(method)](endpoint)[getParamFuncName(method)](schema.params || {}).end(function (err, response) {
var req = _superagent2.default[getMethodFuncName(method)](apiPath);
// NOTE/deprecate: params
if (schema.params) {
deprecate("'params' property/extension in schema is deprecated. Use 'headers', 'query' or 'body' instead.");
req = req[getParamFuncName(method)](schema.params);
}
if (Object.keys(headers).length) {
expandVars(headers, options);
for (var key in headers) {
req = req.set(key, headers[key]);
}
}
if (Object.keys(query).length) {
expandVars(query, options);
req = req.query(query);
}
if (Object.keys(body).length) {
expandVars(body, options);
req = req.send(body);
}
return req.end(function (error, response) {
// catch network errors, etc.
if (!response) {
(0, _should2.default)(err).not.be.ok();
(0, _should2.default)(error).not.be.ok();
}
(0, _should2.default)(response).be.ok();
return validateResponse(schema, response, done);
(0, _should2.default)(response.body).be.ok();
return validateResponse(schema, response, options, done);
});
}
/*
* Create a test case (using "it" from mocha)
/**
* Create a test case (using "it" from mocha).
*
* @private
* @param {Function} it - it from mocha, for test cases

@@ -220,9 +352,10 @@ * @param {String} baseUrl - base url e.g. "http://localhost:9090/"

}
makeRequest(baseUrl, method, schema, done);
makeRequest(baseUrl, method, schema, options, done);
});
}
/*
* Create test cases (to be used in describe)
/**
* Create test cases.
*
* @private
* @param {Function} it - it from mocha, for test cases

@@ -242,3 +375,44 @@ * @param {String} baseUrl - base url e.g. "http://localhost:9090/"

/*
/**
* Create test setup hook (using "before" from mocha).
*
* @private
* @param {Function} before - 'before' from mocha, for setup hook
* @param {String} baseUrl - base URL e.g. "http://localhost:9090/"
* @param {String} schemaDir - directory containing schemas
* @param {Object} options - test configurations
*/
function createSetupHook(before, baseUrl, schemaDir, options) {
before("setup", function (done) {
debug("running test setup");
if (options.timeout) {
this.timeout(options.timeout);
}
var setupPath = _path2.default.join(schemaDir, "setup");
requireAll(setupPath, options, function (error, schemas) {
if (error) return done(error);
var promise = Promise.resolve();
schemas.forEach(function (schema) {
promise = promise.then(function () {
return _setup(schema);
});
});
promise.then(done).catch(done);
});
});
function _setup(schema) {
return new Promise(function (resolve, reject) {
var method = schema.methods[0];
makeRequest(baseUrl, method, schema, options, function (error, response) {
if (error) {
return reject(error);
}
return resolve();
});
});
}
}
/**
* Create test suite

@@ -248,6 +422,13 @@ *

* @param {String} baseUrl - base url e.g. "http://localhost:9090/"
* @param {String} string - directory containing schemas
* @param {String} schemaDir - directory containing schemas
* @param {Object} [options] - test configurations
* @param {Integer} [options.timeout] - timeout used in test cases
* @param {Function} [options.label] - returns a `it` label
* @param {Object} [options.headers] - headers sent on each request
* @param {Object} [options.query] - query parameters sent on each request
* @param {Object} [options.body] - body sent on each request
* @param {Object} [options.vars] - variables used in expansion
* @param {Function} [options.before] - 'before' from mocha, for setup
* @param {String} [options.beforeBaseUrl] - base url, for setup
* @param {String[]} [options.extensions] - extensions of schemas
*/

@@ -257,5 +438,9 @@ function createTestSuite(it, baseUrl, schemaDir) {

if (options.before) {
debug("creating test setup for schemas in " + schemaDir);
createSetupHook(options.before, options.beforeBaseUrl || baseUrl, schemaDir, options);
}
debug("creating test suite for schemas in " + schemaDir);
return requireAll(schemaDir, function (err, schemas) {
(0, _should2.default)(err).not.be.ok();
return requireAll(schemaDir, options, function (error, schemas) {
(0, _should2.default)(error).not.be.ok();

@@ -262,0 +447,0 @@ // each schema

{
"name": "elbow",
"version": "1.0.0",
"version": "1.1.0",
"description": "An easy way to test REST API responses with Mocha",
"main": "lib/index.js",
"scripts": {
"prebuild": "npm run clean",
"build": "babel -D -q src --out-dir .",

@@ -11,3 +12,3 @@ "pretest": "npm run build",

"test-coverage": "istanbul cover _mocha --report lcovonly -- -R spec test/test.*.js",
"prepublish": "npm run build",
"prepublish": "npm run clean && npm run build",
"clean": "rm -rf Gruntfile.js lib/ test/"

@@ -19,3 +20,3 @@ },

},
"engine": {
"engines": {
"node": ">=4"

@@ -39,26 +40,28 @@ },

"dependencies": {
"ajv": "^5.1.4",
"cli-output": "^1.0.0",
"commander": "^2.9.0",
"debug": "^2.3.2",
"jayschema": "^0.3.1",
"lodash": "^4.17.2",
"should": "^11.1.1",
"superagent": "^2.3.0"
"debug": "^2.6.8",
"depd": "^1.1.1",
"lodash": "^4.17.4",
"should": "^11.2.1",
"superagent": "^3.5.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-cli": "^6.24.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-preset-es2015": "^6.18.0",
"body-parser": "^1.15.2",
"coveralls": "^2.11.15",
"express": "^4.14.0",
"babel-preset-es2015": "^6.24.1",
"body-parser": "^1.17.2",
"coveralls": "^2.13.1",
"express": "^4.15.3",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-eslint": "^19.0.0",
"grunt-eslint": "^20.0.0",
"grunt-mocha-test": "^0.13.2",
"istanbul": "^0.4.5",
"js-yaml": "^3.9.1",
"load-grunt-tasks": "^3.5.2",
"mocha": "^3.1.2",
"mocha-lcov-reporter": "^1.0.0"
"mocha": "^3.4.2",
"mocha-lcov-reporter": "^1.3.0"
}
}

@@ -1,2 +0,1 @@

# elbow

@@ -12,3 +11,3 @@

elbow = [mocha](http://mochajs.org/) + [superagent](http://visionmedia.github.io/superagent/) + [jayschema](https://github.com/natesilva/jayschema) + [awesomeness](https://www.dropbox.com/s/flwsp52rm1r9xrw/awesomeness.jpg?dl=0)
elbow = [mocha](http://mochajs.org/) + [superagent](http://visionmedia.github.io/superagent/) + [ajv](https://github.com/epoberezkin/ajv)

@@ -18,5 +17,7 @@

1. you only write one, short test file with one test suite
1. your schemas define what endpoints they are tested against
1. it fits just right in your work flow
1. Utilizes the power of [JSON Schema](http://json-schema.org/) (see [also](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf)).
1. You do not need to write code for interacting with the API.
1. Your schemas define what endpoints they are tested against, along with
parameters, such as request headers and body.
1. Offers a gentle learning curve.

@@ -29,6 +30,6 @@

```js
var elbow = require("elbow");
const elbow = require("elbow");
describe("testing Http Responses", function() {
elbow.run(it, "http://localhost:9090/", __dirname + "/../schema", {
elbow.run(it, "http://localhost:9090/", `${__dirname}/../schema`, {
timeout: 5000,

@@ -54,3 +55,3 @@ });

```js
var elbow = require("elbow");
const elbow = require("elbow");
```

@@ -62,6 +63,6 @@

* `it` (Function): it provided by Mocha.
* `baseUrl` (String): base url of the server. This is used to resolve the relative urls (endpoints).
* `it` (Function): `it` provided by Mocha.
* `baseUrl` (String): base URL of the server. This is used to resolve the relative urls (endpoints).
* `schemaDir` (String): path to the directory holding your schemas.
* `options` (Object): test configurations
* `options` (Object): test configurations <a name="options"></a>
* `options.timeout` (Integer): test-specific timeout

@@ -71,2 +72,10 @@ * `options.label` (Function):

* signature: `function(method, schema)`
* `options.headers` (Object): headers sent on each request. Merged with headers found in schema.
* `options.query` (Object): query parameters sent on each request. Merged with query found in schema.
* `options.body` (Object): body parameters sent on each request. Merged with body found in schema.
* `options.vars` (Object): variables used in [variable expansions](#vars-expansion).
* `options.before` (Function): `before` by Mocha; Makes elbow look for **setup** schemas
in the `setup` directory in `schemaDir`. These schemas are run before any test cases.
* `options.beforeBaseUrl` (String): base URL used in setup. Otherwise `baseUrl` is used.
* `options.extensions` (String[]): extensions of schema files to be used. Defaults to `["json"]`. See [using other file formats](#file-formats).

@@ -86,3 +95,3 @@

Schemas, as defined in its [specification](http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf), are valid JSON documents.
Schemas, as defined in its [specification](http://json-schema.org/), are valid JSON documents.

@@ -98,9 +107,17 @@ All the schemas should be placed in a single directory. They should have the extension `.json`.

"endpoint": "/transactions/transfers/charges",
"description": "transfer charges",
"methods": ["get", "post"],
"endpoint": "/test/endpoint",
"description": "test endpoint",
"methods": ["post"],
"params": {
"to": "registered",
"amount": 5000
"key": "value"
},
"headers": {
"Authorization": "${OAUTH_TOKEN}"
},
"query": {
"key": "value"
},
"body": {
"key": "value"
},

@@ -110,7 +127,11 @@ "status": 200,

"properties": {
"charge": {
"type": "string"
"ok": {
"type": "boolean"
}
},
"required": ["charge"]
"required": ["ok"],
"export": {
"var_name": "ok"
}
}

@@ -125,8 +146,84 @@ ```

* possible values: `"get"`, `"post"`, `"put"`, `"delete"`
* `params` (Object): parameters to pass to endpoint. e.g. `{ "query": "name" }`
Optional key-value pairs include:
* `headers` (Object): headers to send in request
* `query` (Object): query parameters to send in request
* `body` (Object): body to send in request. Only applied if method is `"post"` or `"put"`
* `status` (Number): response status code. e.g. `201`
* `export` (Object): variables to be exported. See [exporting variables](#vars-export)
* `params` (Object): **DEPRECATED: Use `headers`, `query` or `body` instead!**
* parameters to pass to endpoint. e.g. `{ "query": "name" }`
<a name="vars-expansion"></a>
##### variable expansion:
The `endpoint`, `headers`, `query` and `body` parameters can contain variables, in the
form, `${VARIABLE_NAME}`, that will be expanded as necessary. The value
is determined from `options.vars` (see [above](#options)) or from the process environment.
If the value could **not** be determined, the variable is **not** expanded
i.e. is ignored.
<a name="vars-export"></a>
##### variable exports:
The `export` parameter is used to export variables from the test case making
them available for any following test cases. The key-value pairs under
`export` are such that: they key defines the name of the variable and
the value defines the **path in the response body** to the property to
be used. For example, if response body was:
```json
{
"setup": {
"token": "am.a.token"
}
}
```
and the `export` parameter was:
```json
{
"export": {
"setup_token": "setup.token"
}
}
```
would export the variable `${setup_token}` with value `"am.a.token"` at
path `setup.token`. Any following schemas [sic: read test cases] can access
`${setup_token}` and it'll resolve successfully.
See [lodash.get](https://lodash.com/docs/#get)/[lodash.set](https://lodash.com/docs/#set).
<a name="file-formats"></a>
##### using other file formats:
You can use other file formats such as [JSON5](http://json5.org/) and [YAML](http://yaml.org/).
Before using `describe()` you need to install the `require` extension
for your file format. For example,
```js
const fs = require("fs");
const yaml = require("js-yaml");
const elbow = require("elbow");
require.extensions[".yml"] = function(mod, filename) {
mod.exports = yaml.safeLoad(fs.readFileSync(filename, "utf8"));
};
describe("using yaml", function() {
elbow.run(it, "http://localhost:8080", path.join(__dirname, "example"), {
extensions: ["yml"],
});
});
```
You can now write your schema files in YAML e.g. `example.yml`.
---
The rest of the document will be used *as is* in validation.

@@ -179,3 +276,3 @@

1. synchronous file operations are used internally to ensure test cases are executed in correct order by mocha.
1. **Synchronous** file operations are used internally to ensure test cases are executed in correct order by mocha.

@@ -187,2 +284,2 @@

Copyright (c) 2015-2016 GochoMugo <mugo@forfuture.co.ke>
Copyright (c) 2015-2017 GochoMugo <mugo@forfuture.co.ke>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc