Comparing version 1.2.0 to 1.3.0
@@ -11,34 +11,23 @@ 'use strict' | ||
function Case() { | ||
this.finds = [] | ||
} | ||
/** @member {string} */ | ||
this.name = '' | ||
/** | ||
* @property {string} | ||
*/ | ||
Case.prototype.name | ||
/** @member {boolean} */ | ||
this.skip = false | ||
/** | ||
* @property {Obj} | ||
*/ | ||
Case.prototype.post | ||
/** @member {Obj} */ | ||
this.post = null | ||
/** | ||
* @property {string} | ||
*/ | ||
Case.prototype.postUrl | ||
/** @member {string} */ | ||
this.postUrl = '' | ||
/** | ||
* @property {Obj} | ||
*/ | ||
Case.prototype.out | ||
/* @member {Obj} */ | ||
this.out = null | ||
/** | ||
* @property {number} | ||
*/ | ||
Case.prototype.statusCode | ||
/** @member {number} */ | ||
this.statusCode = 0 | ||
/** | ||
* @property {Find[]} | ||
*/ | ||
Case.prototype.finds | ||
/** @member {Find[]} */ | ||
this.finds = [] | ||
} | ||
@@ -51,5 +40,6 @@ /** | ||
Case.prototype.execute = function (options, testName) { | ||
var that = this | ||
var that = this, | ||
it = this.skip ? options.it.skip : options.it | ||
options.it(this.name, function (done) { | ||
it(this.name, function (done) { | ||
// Prepare context | ||
@@ -56,0 +46,0 @@ options.context.prev = { |
@@ -15,12 +15,6 @@ 'use strict' | ||
* @param {Object} db the mongodb connected db | ||
* @param {string[]} cleared | ||
* @param {Object} context | ||
* @param {Function} done | ||
*/ | ||
Clear.prototype.execute = function (db, cleared, context, done) { | ||
if (cleared.indexOf(this.collection) !== -1) { | ||
return done(new Error('The collection ' + this.collection + ' was already cleared')) | ||
} | ||
cleared.push(this.collection) | ||
Clear.prototype.execute = function (db, context, done) { | ||
db.collection(this.collection).remove({}, { | ||
@@ -27,0 +21,0 @@ w: 1 |
@@ -6,7 +6,9 @@ 'use strict' | ||
* @class | ||
* @property {string} name | ||
* @property {Object} value | ||
* @param {string} name | ||
* @param {Obj} value | ||
*/ | ||
function Declaration(name, value) { | ||
/** @member {string} */ | ||
this.name = name | ||
/** @member {Obj} */ | ||
this.value = value | ||
@@ -18,7 +20,6 @@ } | ||
* @param {Object} db (not used) | ||
* @param {string[]} cleared (not used) | ||
* @param {Object} context | ||
* @param {Function} done | ||
*/ | ||
Declaration.prototype.execute = function (db, cleared, context, done) { | ||
Declaration.prototype.execute = function (db, context, done) { | ||
context[this.name] = this.value.execute(context, '<' + this.name + ' is>') | ||
@@ -25,0 +26,0 @@ done() |
@@ -6,9 +6,16 @@ 'use strict' | ||
* @property {string} collection | ||
* @property {Object} value | ||
* @property {Obj} value | ||
*/ | ||
function Find(collection, value) { | ||
/** @member {string} */ | ||
this.collection = collection | ||
/** @member {Obj} */ | ||
this.value = value | ||
} | ||
/** | ||
* @param {Object} context | ||
* @param {Object} db | ||
* @param {Function} done | ||
*/ | ||
Find.prototype.execute = function (context, db, done) { | ||
@@ -15,0 +22,0 @@ var selector = this.value.execute(context, '<find in ' + this.collection + '>'), |
@@ -9,4 +9,11 @@ 'use strict' | ||
function Header(line, sourceLine) { | ||
/** @member {number} */ | ||
this.level = line.match(/^#+/)[0].length | ||
/** @member {string} */ | ||
this.value = line.substr(this.level).trim() | ||
/** | ||
* @member {Object} | ||
* @property {number} begin | ||
* @property {number} end | ||
*/ | ||
this.source = { | ||
@@ -13,0 +20,0 @@ begin: sourceLine, |
@@ -10,7 +10,10 @@ 'use strict' | ||
* @property {string} collection | ||
* @property {Object} value | ||
* @property {Obj} value | ||
*/ | ||
function Insertion(name, collection, value) { | ||
/** @member {string} */ | ||
this.name = name | ||
/** @member {string} */ | ||
this.collection = collection | ||
/** @member {Obj} */ | ||
this.value = value | ||
@@ -22,19 +25,8 @@ } | ||
* @param {Object} db the mongodb connected db | ||
* @param {string[]} cleared | ||
* @param {Object} context | ||
* @param {Function} done | ||
*/ | ||
Insertion.prototype.execute = function (db, cleared, context, done) { | ||
Insertion.prototype.execute = function (db, context, done) { | ||
var that = this | ||
if (cleared.indexOf(this.collection) === -1) { | ||
// Clear the collection first | ||
return new Clear(this.collection).execute(db, cleared, context, function (err) { | ||
if (err) { | ||
return done(err) | ||
} | ||
that.execute(db, cleared, context, done) | ||
}) | ||
} | ||
// Prepare the document | ||
@@ -41,0 +33,0 @@ context[that.name] = this.value.execute(context, '<' + this.name + ' in ' + this.collection + '>') |
@@ -6,25 +6,26 @@ 'use strict' | ||
/** | ||
* @class | ||
* @typedef {Object} Mixin~Addition | ||
* @property {string[]} path | ||
* @property {string[]} value | ||
*/ | ||
function Mixin() { | ||
this.additions = [] | ||
this.removals = [] | ||
} | ||
/** | ||
* The base path components | ||
* @property {string[]} | ||
* @typedef {string[]} Mixin~Removal | ||
*/ | ||
Mixin.prototype.base | ||
/** | ||
* @property {{path:string[],value:string}[]} | ||
* @class | ||
*/ | ||
Mixin.prototype.additions | ||
function Mixin() { | ||
/** @member {Mixin~Addition[]} */ | ||
this.additions = [] | ||
/** | ||
* @property {string[][]} | ||
*/ | ||
Mixin.prototype.removals | ||
/** @member {Mixin~Removal[]} */ | ||
this.removals = [] | ||
/** The base path components | ||
* @member {string[]} | ||
*/ | ||
this.base = [] | ||
} | ||
@@ -66,2 +67,3 @@ /** | ||
* @returns {*} | ||
* @private | ||
*/ | ||
@@ -88,5 +90,5 @@ function copyDeep(x) { | ||
* @param {Object} obj | ||
* @param {(string|number)[]} path | ||
* @param {Array<string|number>} path | ||
* @param {number} [i] | ||
* @throws | ||
* @throws {Error} | ||
* @private | ||
@@ -136,7 +138,6 @@ */ | ||
* @param {!Object} obj | ||
* @param {(string|number)[]} path | ||
* @param {Array<string|number>} path | ||
* @param {*} value | ||
* @param {number} [i] | ||
* @throws | ||
* @private | ||
* @throws {Error} | ||
*/ | ||
@@ -143,0 +144,0 @@ function add(obj, value, path, i) { |
@@ -12,3 +12,9 @@ 'use strict' | ||
function Obj(sourceLine) { | ||
/** @param {string[]} */ | ||
this.lines = [] | ||
/** | ||
* @member {Object} | ||
* @property {number} begin | ||
* @property {number} end | ||
*/ | ||
this.source = { | ||
@@ -18,3 +24,5 @@ begin: sourceLine, | ||
} | ||
/** @member {boolean} */ | ||
this.parsed = false | ||
/** @member {Object|Array|string|Mixin} */ | ||
this.value = null | ||
@@ -21,0 +29,0 @@ } |
@@ -6,8 +6,13 @@ 'use strict' | ||
* @param {string} message | ||
* @param {(Header|Obj)} [el] The element that caused the error (null if not applicable) | ||
* @param {Header|Obj} [...el] The element that caused the error (null if not applicable) | ||
* @extends Error | ||
*/ | ||
function ParseError(message, el) { | ||
function ParseError(message) { | ||
Error.call(this) | ||
/** @member {string} */ | ||
this.message = message | ||
this.el = el | ||
/** @member {Array<Header|Obj>} */ | ||
this.els = [].slice.call(arguments, 1) | ||
} | ||
@@ -22,11 +27,22 @@ | ||
ParseError.prototype.addSourceContext = function (originalLines) { | ||
if (!this.el) { | ||
var start = Infinity, | ||
end = -Infinity, | ||
str = '\n\n-----', | ||
i, focus, checkElFocus | ||
if (!this.els.length) { | ||
return | ||
} | ||
var start = this.el.source.begin, | ||
end = this.el.source.end, | ||
str = '\n\n-----', | ||
i | ||
this.els.forEach(function (el) { | ||
start = Math.min(start, el.source.begin) | ||
end = Math.max(end, el.source.end) | ||
}) | ||
checkElFocus = function (el) { | ||
return i >= el.source.begin && i < el.source.end | ||
} | ||
for (i = Math.max(0, start - 3); i < end + 3 && i < originalLines.length; i++) { | ||
str += '\n' + (i >= start && i < end ? '>' : ' ') + ' ' + originalLines[i] | ||
focus = this.els.some(checkElFocus) | ||
str += '\n' + (focus ? '>' : ' ') + ' ' + originalLines[i] | ||
} | ||
@@ -33,0 +49,0 @@ str += '\n-----' |
@@ -7,10 +7,15 @@ 'use strict' | ||
* @class | ||
* @property {string} name | ||
* @property {Setup[]} setups | ||
* @property {Case[]} cases | ||
*/ | ||
function Test() { | ||
/** @member {string} */ | ||
this.name = '' | ||
/** @member {Array<Insertion|Clear|Declaration>} */ | ||
this.setups = [] | ||
/** @member {Case[]} */ | ||
this.cases = [] | ||
/** | ||
* A map with used collections and the element that has cleared it | ||
* @member {Object<Header>} | ||
*/ | ||
this.collections = Object.create(null) | ||
} | ||
@@ -28,5 +33,4 @@ | ||
// Insert each document | ||
var cleared = [] | ||
async.eachSeries(that.setups, function (setup, done) { | ||
setup.execute(options.db, cleared, options.context, done) | ||
setup.execute(options.db, options.context, done) | ||
}, done) | ||
@@ -33,0 +37,0 @@ }) |
{ | ||
"name": "api-test", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"author": "Sitegui <sitegui@sitegui.com.br>", | ||
@@ -5,0 +5,0 @@ "description": "API testing made simple", |
80
parse.js
/** | ||
* @file Parses simple markdown text | ||
* | ||
* The syntax is described using these notations: | ||
* * {x} represents a non-terminal symbol that can span more than one line | ||
* * <x> represents an one-line non-terminal symbol | ||
* * / represents a line-break | ||
* * _x_ represents an arbritary text field | ||
* * 'x' represents the literal x | ||
* * x? means optional | ||
* * x+ means at least once | ||
* * x* means any number of times | ||
* * x|y means either x or y | ||
* * [D] means a digit | ||
* * ... means ignored content | ||
* The syntax is described in doc-syntax.md | ||
* | ||
* {file} = <header> / ... / {setup} / {test}+ | ||
* | ||
* <header> = '# ' _testName_ | ||
* {setup} = '## Setup' / ({insertion} | {clear} | {declaration})* | ||
* {test} = '## ' _caseName_ / ('### Post' / {obj})? / {out}? / {find}* | ||
* | ||
* {insertion} = '### ' _docName_ ' in ' _collection_ / {obj} | ||
* {clear} = '### Clear ' _collection_ | ||
* {declaration} = '### ' _varName_ ' is' / {obj} | ||
* {obj} = '\t' (_value_ | {subobj} | <prop>) | ||
* {out} = '### Out' (' ' <statusCode>)? / {obj} | ||
* {find} = '### Find in ' _collection_ / {obj} | ||
* | ||
* {subobj} = _key_ ':' / '\t' {obj} | ||
* <prop> = _key_ ':' _value_ | ||
* <statusCode> = [D] [D] [D] | ||
* | ||
* Paragraph text is ignored (it can be used for documentation) | ||
@@ -166,3 +138,3 @@ */ | ||
function parseSetupItem(test, els, i) { | ||
var match, header | ||
var match, header, coll, el, msg | ||
@@ -178,5 +150,18 @@ if (checkHeader(els[i], 2)) { | ||
if ((match = header.match(/^Clear ([a-zA-Z_$][a-zA-Z0-9_$]*)$/))) { | ||
test.setups.push(new Clear(match[1])) | ||
// Clear a collection | ||
coll = match[1] | ||
if (coll in test.collections) { | ||
el = test.collections[coll] | ||
if (el.value.indexOf('Clear ') === 0) { | ||
msg = 'No need to clear the same collection twice' | ||
} else { | ||
msg = 'Clearing the collection after insertion is not a good idea' | ||
} | ||
throw new ParseError(msg, el, els[i]) | ||
} | ||
test.collections[coll] = els[i] | ||
test.setups.push(new Clear(coll)) | ||
return i + 1 | ||
} else if ((match = header.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*) is$/))) { | ||
// Declare a variable | ||
if (!(els[i + 1] instanceof Obj)) { | ||
@@ -188,6 +173,16 @@ throw new ParseError('Expected an {obj}', els[i + 1]) | ||
} else if ((match = header.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*) in ([a-zA-Z_$][a-zA-Z0-9_$]*)$/))) { | ||
// Insert a document (clear the collection implicitly) | ||
if (!(els[i + 1] instanceof Obj)) { | ||
throw new ParseError('Expected an {obj}', els[i + 1]) | ||
} | ||
test.setups.push(new Insertion(match[1], match[2], els[i + 1].parse())) | ||
coll = match[2] | ||
if (!(coll in test.collections)) { | ||
// Push implicit clear | ||
test.collections[coll] = els[i] | ||
test.setups.push(new Clear(coll)) | ||
} else if (test.collections[coll].value.indexOf('Clear ') === 0) { | ||
el = test.collections[coll] | ||
throw new ParseError('No need to clear the collection before insertion, this is done automatically for you', el, els[i]) | ||
} | ||
test.setups.push(new Insertion(match[1], coll, els[i + 1].parse())) | ||
return i + 2 | ||
@@ -214,7 +209,14 @@ } else { | ||
} | ||
testCase.name = els[i++].value | ||
if (/ \(skip\)$/.test(els[i].value)) { | ||
testCase.name = els[i].value.substr(0, els[i].value.length - 7).trimRight() | ||
testCase.skip = true | ||
} else { | ||
testCase.name = els[i].value | ||
testCase.skip = false | ||
} | ||
i++ | ||
i = parseCasePost(testCase, els, i) | ||
i = parseCaseOut(testCase, els, i) | ||
i = parseCaseFinds(testCase, els, i) | ||
i = parseCaseFinds(test.collections, testCase, els, i) | ||
@@ -272,2 +274,3 @@ test.cases.push(testCase) | ||
* Try to parse a test case finds | ||
* @param {Object<Header>} collections Cleared collections | ||
* @param {Case} testCase | ||
@@ -279,3 +282,4 @@ * @param {Object[]} els | ||
*/ | ||
function parseCaseFinds(testCase, els, i) { | ||
function parseCaseFinds(collections, testCase, els, i) { | ||
var coll | ||
while (i < els.length && !checkHeader(els[i], 2)) { | ||
@@ -287,3 +291,7 @@ if (!checkHeader(els[i], 3) || els[i].value.indexOf('Find in ') !== 0) { | ||
} | ||
testCase.finds.push(new Find(els[i].value.substr(8).trim(), els[i + 1].parse())) | ||
coll = els[i].value.substr(8).trim() | ||
if (!(coll in collections)) { | ||
throw new ParseError('You can\'t do a find in a collection that wasn\'t cleared in the setup', els[i]) | ||
} | ||
testCase.finds.push(new Find(coll, els[i + 1].parse())) | ||
i += 2 | ||
@@ -290,0 +298,0 @@ } |
@@ -93,2 +93,5 @@ # API Test | ||
## Skipping test cases | ||
By appending ` (skip)` to a test case name (see an [example](https://github.com/clubedaentrega/api-test/blob/master/test/api-test/recursive/user-login.md#wrong-password-skip)) it will be simply ignored. This puts them in a pending state, and is favoured over removing tests which you may forget to add back again. | ||
## Object syntax | ||
@@ -182,2 +185,3 @@ The syntax was designed to be concise and expressive. The values will be eval'ed as normal JS with a context with special variables (see `default context` bellow). | ||
## TODO | ||
* Make keys less restrictive | ||
* Make keys less restrictive | ||
* Ignore key ordering in find |
@@ -8,3 +8,3 @@ # user/login | ||
## Wrong password | ||
## Wrong password (skip) | ||
### Post | ||
@@ -11,0 +11,0 @@ user: |
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
Mixed license
License(Experimental) Package contains multiple licenses.
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
240572
61
2015
185
1