Comparing version 0.1.0 to 0.2.0
@@ -6,13 +6,59 @@ /** | ||
var crypto = require('crypto') | ||
var ObjectID = require('mongodb').ObjectID | ||
module.exports = { | ||
randomId: function () { | ||
return crypto.pseudoRandomBytes(12).toString('hex') | ||
}, | ||
randomStr: function (len) { | ||
len = len || 7 | ||
return crypto.pseudoRandomBytes(Math.ceil(len * 3 / 4)).toString('base64').substr(0, len) | ||
}, | ||
empty: {} | ||
} | ||
/** | ||
* Generate a random mongo objectId | ||
* @returns {string} | ||
*/ | ||
module.exports.randomId = function () { | ||
return new ObjectID().toHexString() | ||
} | ||
/** | ||
* Generate a random string with base64 chars (A-Za-z0-9+/) | ||
* @param {number} [len=7] | ||
* @param {string} [alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'] | ||
* @returns {string} | ||
*/ | ||
module.exports.randomStr = function (len, alphabet) { | ||
var i, str = '' | ||
len = len || 7 | ||
alphabet = alphabet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' | ||
for (i = 0; i < len; i++) { | ||
str += alphabet[Math.floor(Math.random() * alphabet.length)] | ||
} | ||
return str | ||
} | ||
/** | ||
* Generate a random string with hex chars (0-9a-f) | ||
* @param {number} [len=7] | ||
* @returns {string} | ||
*/ | ||
module.exports.randomHex = function (len) { | ||
return module.exports.randomStr(len, '0123456789abcdef') | ||
} | ||
/** | ||
* Generate a random string with digits (0-9) | ||
* @param {number} [len=7] | ||
* @returns {string} | ||
*/ | ||
module.exports.randomCode = function (len) { | ||
return module.exports.randomStr(len, '0123456789') | ||
} | ||
/** | ||
* Generate a random valid email address | ||
* @param {string} [domain='example.com'] | ||
* @returns {string} | ||
*/ | ||
module.exports.randomEmail = function (domain) { | ||
domain = domain || 'example.com' | ||
return 'test-' + module.exports.randomId() + '@' + domain | ||
} | ||
/** | ||
* The empty object | ||
*/ | ||
module.exports.empty = {} |
10
Case.js
@@ -13,8 +13,10 @@ 'use strict' | ||
* @property {Object} out | ||
* @property {number} statusCode | ||
* @property {Find[]} finds | ||
*/ | ||
function Case(name, post, out) { | ||
function Case(name, post, out, statusCode) { | ||
this.name = name | ||
this.post = post | ||
this.out = out | ||
this.statusCode = statusCode | ||
this.finds = [] | ||
@@ -24,3 +26,3 @@ } | ||
Case.prototype.execute = function (url, context, db, done) { | ||
var post = execute(this.post, context), | ||
var post = execute(this.post, context, '<post>'), | ||
that = this | ||
@@ -38,4 +40,4 @@ context.post = post | ||
res.statusCode.should.be.equal(200) | ||
check(out, execute(that.out, context)) | ||
res.statusCode.should.be.equal(that.statusCode) | ||
check(out, execute(that.out, context, '<out>')) | ||
async.each(that.finds, function (find, done) { | ||
@@ -42,0 +44,0 @@ find.execute(context, db, done) |
// This module uses 'with', so it can't be strict | ||
function __exec(value, context) { | ||
/** | ||
* Eval the given value in the given context | ||
* @param {(Object|string)} value | ||
* @param {Object} context | ||
* @param {string} path A string like '<' + description + '>' to be part of a thrown execption | ||
* @returns {*} | ||
* @throws | ||
*/ | ||
module.exports = function (value, context, path) { | ||
'use strict' | ||
var key, r | ||
path = path || '' | ||
if (typeof value === 'string') { | ||
return __eval(value, context) | ||
return __eval(value, context, path) | ||
} else { | ||
r = Object.create(null) | ||
for (key in value) { | ||
r[key] = __exec(value[key], context) | ||
r[key] = module.exports(value[key], context, path + '.' + key) | ||
} | ||
@@ -17,8 +26,11 @@ return r | ||
function __eval(__str, __context) { | ||
with(__context) { | ||
return eval(__str) | ||
function __eval(__str, __context, __path) { | ||
try { | ||
with(__context) { | ||
return eval(__str) | ||
} | ||
} catch (e) { | ||
e.message += ' in ' + __path | ||
throw e | ||
} | ||
} | ||
module.exports = __exec | ||
} |
@@ -16,3 +16,3 @@ 'use strict' | ||
Find.prototype.execute = function (context, db, done) { | ||
var selector = execute(this.value, context), | ||
var selector = execute(this.value, context, '<find in ' + this.collection + '>'), | ||
that = this | ||
@@ -19,0 +19,0 @@ db.collection(this.collection).findOne(selector, function (err, doc) { |
@@ -48,3 +48,3 @@ 'use strict' | ||
// Prepare the document | ||
context[that.name] = execute(this.value, context) | ||
context[that.name] = execute(this.value, context, '<' + this.name + ' in ' + this.collection + '>') | ||
db.collection(this.collection).insert(context[that.name], { | ||
@@ -51,0 +51,0 @@ w: 1 |
{ | ||
"name": "api-test", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"author": "Sitegui <sitegui@sitegui.com.br>", | ||
@@ -5,0 +5,0 @@ "description": "API testing made simple", |
48
parse.js
@@ -14,2 +14,3 @@ /** | ||
* * x|y means either x or y | ||
* * [D] means a digit | ||
* | ||
@@ -20,3 +21,3 @@ * {file} = <header> / {fixup}? / {test}+ | ||
* {fixup} = '## DB' / ({insertion} | {clear})* | ||
* {test} = '## ' _caseName_ / '### Post' / {obj} / '### Out' / {obj} / {find}* | ||
* {test} = '## ' _caseName_ / ('### Post' / {obj})? / {out}? / {find}* | ||
* | ||
@@ -26,2 +27,3 @@ * {insertion} = '### ' _docName_ ' in ' _collection_ / {obj} | ||
* {obj} = '\t' (_value_ | {subobj} | <prop>) | ||
* {out} = '### Out' (' ' <statusCode>)? / {obj} | ||
* {find} = '### Find in ' _collection_ / {obj} | ||
@@ -31,2 +33,3 @@ * | ||
* <prop> = _key_ ':' _value_ | ||
* <statusCode> = [D] [D] [D] | ||
* | ||
@@ -138,18 +141,39 @@ * Paragraph text is ignored (it can be used for documentation) | ||
function parseCase(test, lines, i, originalLines) { | ||
var name, post, out, statusCode | ||
// Test case name | ||
if (!checkHeader(lines[i], 2)) { | ||
throwSyntaxError('Expected "## _caseName_"', lines[i], originalLines) | ||
} else if (!checkHeader(lines[i + 1], 3, 'Post')) { | ||
throwSyntaxError('Expected "### Post"', lines[i + 1], originalLines) | ||
} else if (!(lines[i + 2] instanceof Obj)) { | ||
throwSyntaxError('Expected an {obj}', lines[i + 2], originalLines) | ||
} else if (!checkHeader(lines[i + 3], 3, 'Out')) { | ||
throwSyntaxError('Expected "### Out"', lines[i + 3], originalLines) | ||
} else if (!(lines[i + 4] instanceof Obj)) { | ||
throwSyntaxError('Expected an {obj}', lines[i + 4], originalLines) | ||
} | ||
name = lines[i].value | ||
i++ | ||
var testCase = new Case(lines[i].value, lines[i + 2].value, lines[i + 4].value) | ||
// Post | ||
if (checkHeader(lines[i], 3, 'Post')) { | ||
if (!(lines[i + 1] instanceof Obj)) { | ||
throwSyntaxError('Expected an {obj}', lines[i + 1], originalLines) | ||
} | ||
post = lines[i + 1].value | ||
i += 2 | ||
} else { | ||
post = {} | ||
} | ||
// Out | ||
if (checkHeader(lines[i], 3) && lines[i].value.match(/^Out( \d{3})?$/)) { | ||
if (!(lines[i + 1] instanceof Obj)) { | ||
throwSyntaxError('Expected an {obj}', lines[i + 1], originalLines) | ||
} | ||
out = lines[i + 1].value | ||
statusCode = lines[i].value === 'Out' ? 200 : Number(lines[i].value.substr(4)) | ||
i += 2 | ||
} else { | ||
out = {} | ||
statusCode = 200 | ||
} | ||
var testCase = new Case(name, post, out, statusCode) | ||
test.cases.push(testCase) | ||
i += 5 | ||
// Finds | ||
while (i < lines.length && !checkHeader(lines[i], 2)) { | ||
@@ -198,2 +222,3 @@ if (!checkHeader(lines[i], 3) || lines[i].value.indexOf('Find in ') !== 0) { | ||
* @param {number} [context=3] | ||
* @returns {string} | ||
*/ | ||
@@ -215,2 +240,3 @@ function getSourceContext(originalLines, start, end, context) { | ||
* @param {string[]} originalLines | ||
* @throws | ||
*/ | ||
@@ -217,0 +243,0 @@ function throwSyntaxError(msg, token, originalLines) { |
@@ -6,3 +6,3 @@ # API Test | ||
## Install | ||
`npm install api-test --save` | ||
`npm install api-test --save-dev` | ||
@@ -20,3 +20,3 @@ ## Usage | ||
randomId() | ||
### Out | ||
### Out 400 | ||
error: | ||
@@ -78,6 +78,6 @@ code: 200 | ||
### Test cases | ||
A test case has three sections: | ||
A test case has three optional sections: | ||
* `Post`: the JSON body to send by POST. Must start with a header like `### Post` | ||
* `Out`: the expected JSON output. Must start with a header like `### Out` | ||
* `Post`: the JSON body to send by POST. Must start with a header like `### Post`. Default: empty JSON object `{}` | ||
* `Out`: the expected JSON output. Must start with a header like `### Out [_statusCode_]`. Default: no output checking. The _statusCode_ is optional and default to 200 | ||
* `Finds`: optional DB assertions. Must start with a header like `### Find in _collection_` | ||
@@ -108,3 +108,6 @@ | ||
* `randomId()`: return a random mongo-id as a 24-hex-char string | ||
* `randomStr(len)`: return a random string with length `len` | ||
* `randomStr([len=7], [alphabet=a-zA-Z0-9+/])` | ||
* `randomHex([len=7])` | ||
* `randomCode([len=7])` | ||
* `randomEmail([domain='example.com'])` | ||
* `empty`: the empty object `{}` | ||
@@ -121,4 +124,38 @@ * `post`: the request body of the current test case | ||
* `describe`, `it`, `before`: (optional) the mocha interface. Defaults to global mocha functions | ||
* `context`: (optional) define your own variables/functions accessible from object definitions, to help writing tests. | ||
* `context`: (optional) define your own variables/functions accessible to object definitions | ||
## Custom context | ||
You can use custom context to help writing tests. All default context variables and methods will still be accessible (unless overwritten). | ||
For example: if all endpoints return errors like this: `{error: {code: _code_, message: _aDebugString_}}`, you can pass as context: | ||
``` | ||
options.context = { | ||
error: function (code) { | ||
return { | ||
error: { | ||
code: code, | ||
message: String | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
And then write a test case like this: | ||
``` | ||
## Invalid email should give error 200 | ||
### Post | ||
user: | ||
email: randomEmail() | ||
### Out | ||
error(200) | ||
``` | ||
Instead of repeating youself with: | ||
``` | ||
error: | ||
code: 200 | ||
message: String | ||
``` | ||
## Examples | ||
@@ -130,4 +167,4 @@ See more test examples in the folder 'test/api-test' | ||
## Road map | ||
## TODO | ||
* Array notation: there is no way to declare an array yet | ||
* Better failure message | ||
* Make request to arbitrary endpoints in a test case |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28432
21
699
164