| Line | Hits | Source |
|---|---|---|
| 1 | 1 | sms = require("source-map-support").install({handleUncaughtExceptions: false}) |
| 2 | 1 | ramlParser = require 'raml-parser' |
| 3 | 1 | async = require 'async' |
| 4 | ||
| 5 | 1 | options = require './options' |
| 6 | 1 | addTests = require './add-tests' |
| 7 | 1 | TestFactory = require './test' |
| 8 | 1 | addHooks = require './add-hooks' |
| 9 | 1 | Runner = require './test-runner' |
| 10 | 1 | applyConfiguration = require './apply-configuration' |
| 11 | 1 | hooks = require './hooks' |
| 12 | ||
| 13 | ||
| 14 | 1 | class Abao |
| 15 | 1 | constructor: (config) -> |
| 16 | 2 | @configuration = applyConfiguration(config) |
| 17 | 2 | @tests = [] |
| 18 | 2 | @hooks = hooks |
| 19 | ||
| 20 | run: (done) -> | |
| 21 | 1 | config = @configuration |
| 22 | 1 | tests = @tests |
| 23 | 1 | hooks = @hooks |
| 24 | ||
| 25 | # Inject the JSON refs schemas | |
| 26 | 1 | factory = new TestFactory(config.options.schemas) |
| 27 | ||
| 28 | 1 | async.waterfall [ |
| 29 | # Parse hooks | |
| 30 | (callback) -> | |
| 31 | 1 | addHooks hooks, config.options.hookfiles |
| 32 | 1 | callback() |
| 33 | , | |
| 34 | # Load RAML | |
| 35 | (callback) -> | |
| 36 | 1 | ramlParser.loadFile(config.ramlPath).then (raml) -> |
| 37 | 0 | callback(null, raml) |
| 38 | , callback | |
| 39 | , | |
| 40 | # Parse tests from RAML | |
| 41 | (raml, callback) -> | |
| 42 | 0 | if !config.options.server |
| 43 | 0 | if raml.baseUri |
| 44 | 0 | config.options.server = raml.baseUri |
| 45 | 0 | addTests raml, tests, hooks, callback, factory, config.options.sorted |
| 46 | , | |
| 47 | # Run tests | |
| 48 | (callback) -> | |
| 49 | 0 | runner = new Runner config.options, config.ramlPath |
| 50 | 0 | runner.run tests, hooks, callback |
| 51 | ], done | |
| 52 | ||
| 53 | ||
| 54 | 1 | module.exports = Abao |
| 55 | 1 | module.exports.options = options |
| 56 | ||
| 57 |
| Line | Hits | Source |
|---|---|---|
| 1 | 2 | require 'coffee-script/register' |
| 2 | 2 | proxyquire = require('proxyquire').noCallThru() |
| 3 | 2 | glob = require 'glob' |
| 4 | 2 | path = require 'path' |
| 5 | ||
| 6 | ||
| 7 | 2 | addHooks = (hooks, pattern) -> |
| 8 | ||
| 9 | 7 | return unless pattern |
| 10 | ||
| 11 | 5 | files = glob.sync pattern |
| 12 | ||
| 13 | 5 | console.error 'Found Hookfiles: ' + files |
| 14 | ||
| 15 | 5 | try |
| 16 | 5 | for file in files |
| 17 | 8 | proxyquire path.resolve(process.cwd(), file), { |
| 18 | 'hooks': hooks | |
| 19 | } | |
| 20 | catch error | |
| 21 | 2 | console.error 'Skipping hook loading...' |
| 22 | 2 | console.error 'Error reading hook files (' + files + ')' |
| 23 | 2 | console.error 'This probably means one or more of your hookfiles is invalid.' |
| 24 | 2 | console.error 'Message: ' + error.message if error.message? |
| 25 | 2 | console.error 'Stack: ' + error.stack if error.stack? |
| 26 | 2 | return |
| 27 | ||
| 28 | ||
| 29 | 2 | module.exports = addHooks |
| 30 | ||
| 31 |
| Line | Hits | Source |
|---|---|---|
| 1 | 2 | async = require 'async' |
| 2 | 2 | _ = require 'lodash' |
| 3 | 2 | csonschema = require 'csonschema' |
| 4 | ||
| 5 | ||
| 6 | 2 | parseSchema = (source) -> |
| 7 | 13 | if source.contains('$schema') |
| 8 | #jsonschema | |
| 9 | # @response.schema = JSON.parse @response.schema | |
| 10 | 3 | JSON.parse source |
| 11 | else | |
| 12 | 10 | csonschema.parse source |
| 13 | # @response.schema = csonschema.parse @response.schema | |
| 14 | ||
| 15 | 2 | parseHeaders = (raml) -> |
| 16 | 15 | return {} unless raml |
| 17 | ||
| 18 | 1 | headers = {} |
| 19 | 1 | for key, v of raml |
| 20 | 1 | headers[key] = v.example |
| 21 | ||
| 22 | 1 | headers |
| 23 | ||
| 24 | 2 | addTests = (raml, tests, hooks, parent, callback, testFactory, sortFirst) -> |
| 25 | ||
| 26 | # Handle 4th optional param | |
| 27 | 25 | if _.isFunction(parent) |
| 28 | 10 | sortFirst = testFactory |
| 29 | 10 | testFactory = callback |
| 30 | 10 | callback = parent |
| 31 | 10 | parent = null |
| 32 | ||
| 33 | 25 | return callback() unless raml.resources |
| 34 | ||
| 35 | # Iterate endpoint | |
| 36 | 14 | async.each raml.resources, (resource, callback) -> |
| 37 | 14 | path = resource.relativeUri |
| 38 | 14 | params = {} |
| 39 | 14 | query = {} |
| 40 | ||
| 41 | # Apply parent properties | |
| 42 | 14 | if parent |
| 43 | 3 | path = parent.path + path |
| 44 | 3 | params = _.clone parent.params # shallow copy |
| 45 | ||
| 46 | # Setup param | |
| 47 | 14 | if resource.uriParameters |
| 48 | 2 | for key, param of resource.uriParameters |
| 49 | 2 | params[key] = param.example |
| 50 | ||
| 51 | ||
| 52 | # In case of issue #8, resource does not define methods | |
| 53 | 14 | resource.methods ?= [] |
| 54 | ||
| 55 | 14 | if sortFirst && resource.methods.length > 1 |
| 56 | 1 | methodTests = [ |
| 57 | method: 'CONNECT', tests: [] | |
| 58 | , | |
| 59 | method: 'OPTIONS', tests: [] | |
| 60 | , | |
| 61 | method: 'POST', tests: [] | |
| 62 | , | |
| 63 | method: 'GET', tests: [] | |
| 64 | , | |
| 65 | method: 'HEAD', tests: [] | |
| 66 | , | |
| 67 | method: 'PUT', tests: [] | |
| 68 | , | |
| 69 | method: 'PATCH', tests: [] | |
| 70 | , | |
| 71 | method: 'DELETE', tests: [] | |
| 72 | , | |
| 73 | method: 'TRACE', tests: [] | |
| 74 | ] | |
| 75 | ||
| 76 | # Group endpoint tests by method name | |
| 77 | 1 | _.each methodTests, (methodTest) -> |
| 78 | 9 | isSameMethod = (test) -> |
| 79 | 7 | return methodTest.method == test.method.toUpperCase() |
| 80 | ||
| 81 | 9 | ans = _.partition resource.methods, isSameMethod |
| 82 | 9 | if ans[0].length != 0 |
| 83 | 2 | _.each ans[0], (test) -> methodTest.tests.push test |
| 84 | 2 | resource.methods = ans[1] |
| 85 | ||
| 86 | # Shouldn't happen unless new HTTP method introduced... | |
| 87 | 1 | leftovers = resource.methods |
| 88 | 1 | if leftovers.length > 1 |
| 89 | 0 | console.error 'unknown method calls present!', leftovers |
| 90 | ||
| 91 | # Now put them back, but in order of methods listed above | |
| 92 | 1 | sortedTests = _.map methodTests, (methodTest) -> return methodTest.tests |
| 93 | 1 | leftoverTests = _.map leftovers, (leftover) -> return leftover |
| 94 | 1 | reassembled = _.flattenDeep [_.reject sortedTests, _.isEmpty, |
| 95 | _.reject leftoverTests, _.isEmpty] | |
| 96 | 1 | resource.methods = reassembled |
| 97 | ||
| 98 | # Iterate response method | |
| 99 | 14 | async.each resource.methods, (api, callback) -> |
| 100 | 15 | method = api.method.toUpperCase() |
| 101 | ||
| 102 | # Setup query | |
| 103 | 15 | if api.queryParameters |
| 104 | 2 | for qkey, qvalue of api.queryParameters |
| 105 | 2 | if (!!qvalue.required) |
| 106 | 1 | query[qkey] = qvalue.example |
| 107 | ||
| 108 | ||
| 109 | # Iterate response status | |
| 110 | 15 | for status, res of api.responses |
| 111 | ||
| 112 | 15 | testName = "#{method} #{path} -> #{status}" |
| 113 | ||
| 114 | # Append new test to tests | |
| 115 | 15 | test = testFactory.create(testName, hooks.contentTests[testName]) |
| 116 | 15 | tests.push test |
| 117 | ||
| 118 | # Update test.request | |
| 119 | 15 | test.request.path = path |
| 120 | 15 | test.request.method = method |
| 121 | 15 | test.request.headers = parseHeaders(api.headers) |
| 122 | ||
| 123 | # select compatible content-type in request body (to support vendor tree types, i.e. application/vnd.api+json) | |
| 124 | 15 | contentType = (type for type of api.body when type.match(/^application\/(.*\+)?json/i))?[0] |
| 125 | 15 | if contentType |
| 126 | 4 | test.request.headers['Content-Type'] = contentType |
| 127 | 4 | try |
| 128 | 4 | test.request.body = JSON.parse api.body[contentType]?.example |
| 129 | catch | |
| 130 | 1 | console.warn "cannot parse JSON example request body for #{test.name}" |
| 131 | 15 | test.request.params = params |
| 132 | 15 | test.request.query = query |
| 133 | ||
| 134 | # Update test.response | |
| 135 | 15 | test.response.status = status |
| 136 | 15 | test.response.schema = null |
| 137 | ||
| 138 | 15 | if res?.body |
| 139 | # expect content-type of response body to be identical to request body | |
| 140 | 13 | if contentType && res.body[contentType]?.schema |
| 141 | 3 | test.response.schema = parseSchema res.body[contentType].schema |
| 142 | # otherwise filter in responses section for compatible content-types | |
| 143 | # (vendor tree, i.e. application/vnd.api+json) | |
| 144 | else | |
| 145 | 10 | contentType = (type for type of res.body when type.match(/^application\/(.*\+)?json/i))?[0] |
| 146 | 10 | if res.body[contentType]?.schema |
| 147 | 10 | test.response.schema = parseSchema res.body[contentType].schema |
| 148 | ||
| 149 | 15 | callback() |
| 150 | , (err) -> | |
| 151 | 14 | return callback(err) if err |
| 152 | ||
| 153 | # Recursive | |
| 154 | 14 | addTests resource, tests, hooks, {path, params}, callback, testFactory, sortFirst |
| 155 | , callback | |
| 156 | ||
| 157 | ||
| 158 | 2 | module.exports = addTests |
| 159 | ||
| 160 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | applyConfiguration = (config) -> |
| 2 | ||
| 3 | 2 | coerceToArray = (value) -> |
| 4 | 2 | if typeof value is 'string' |
| 5 | 0 | value = [value] |
| 6 | 2 | else if !value? |
| 7 | 2 | value = [] |
| 8 | 0 | else if value instanceof Array |
| 9 | 0 | value |
| 10 | 0 | else value |
| 11 | ||
| 12 | 2 | coerceToDict = (value) -> |
| 13 | 2 | array = coerceToArray value |
| 14 | 2 | @dict = {} |
| 15 | ||
| 16 | 2 | if array.length > 0 |
| 17 | 0 | for item in array |
| 18 | 0 | splitItem = item.split(':') |
| 19 | 0 | @dict[splitItem[0]] = splitItem[1] |
| 20 | ||
| 21 | 2 | return @dict |
| 22 | ||
| 23 | 2 | configuration = |
| 24 | ramlPath: null | |
| 25 | options: | |
| 26 | server: null | |
| 27 | schemas: null | |
| 28 | reporters: false | |
| 29 | reporter: null | |
| 30 | header: null | |
| 31 | names: false | |
| 32 | hookfiles: null | |
| 33 | grep: '' | |
| 34 | invert: false | |
| 35 | 'hooks-only': false | |
| 36 | sorted: false | |
| 37 | ||
| 38 | # Normalize options and config | |
| 39 | 2 | for own key, value of config |
| 40 | 0 | configuration[key] = value |
| 41 | ||
| 42 | # Coerce some options into an dict | |
| 43 | 2 | configuration.options.header = coerceToDict(configuration.options.header) |
| 44 | ||
| 45 | # TODO(quanlong): OAuth2 Bearer Token | |
| 46 | 2 | if configuration.options.oauth2Token? |
| 47 | 0 | configuration.options.headers['Authorization'] = "Bearer #{configuration.options.oauth2Token}" |
| 48 | ||
| 49 | 2 | return configuration |
| 50 | ||
| 51 | ||
| 52 | 1 | module.exports = applyConfiguration |
| 53 | ||
| 54 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | fs = require 'fs' |
| 2 | 1 | Mustache = require 'mustache' |
| 3 | ||
| 4 | 1 | generateHooks = (names, ramlFile, templateFile, callback) -> |
| 5 | 0 | if !names |
| 6 | 0 | callback new Error 'no names found for which to generate hooks' |
| 7 | ||
| 8 | 0 | if !templateFile |
| 9 | 0 | callback new Error 'missing template file' |
| 10 | ||
| 11 | 0 | try |
| 12 | 0 | template = fs.readFileSync templateFile, 'utf8' |
| 13 | 0 | datetime = new Date().toISOString().replace('T', ' ').substr(0, 19) |
| 14 | 0 | view = |
| 15 | ramlFile: ramlFile | |
| 16 | timestamp: datetime | |
| 17 | hooks: | |
| 18 | 0 | { 'name': name } for name in names |
| 19 | 0 | view.hooks[0].comment = true |
| 20 | ||
| 21 | 0 | content = Mustache.render template, view |
| 22 | 0 | console.log content |
| 23 | catch error | |
| 24 | 0 | console.error 'failed to generate skeleton hooks' |
| 25 | 0 | callback error |
| 26 | ||
| 27 | 0 | callback |
| 28 | ||
| 29 | 1 | module.exports = generateHooks |
| 30 | ||
| 31 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | async = require 'async' |
| 2 | 1 | _ = require 'underscore' |
| 3 | ||
| 4 | ||
| 5 | 1 | class Hooks |
| 6 | 1 | constructor: () -> |
| 7 | 1 | @beforeHooks = {} |
| 8 | 1 | @afterHooks = {} |
| 9 | 1 | @beforeAllHooks = [] |
| 10 | 1 | @afterAllHooks = [] |
| 11 | 1 | @beforeEachHooks = [] |
| 12 | 1 | @afterEachHooks = [] |
| 13 | 1 | @contentTests = {} |
| 14 | 1 | @skippedTests = [] |
| 15 | ||
| 16 | before: (name, hook) => | |
| 17 | 5 | @addHook(@beforeHooks, name, hook) |
| 18 | ||
| 19 | after: (name, hook) => | |
| 20 | 5 | @addHook(@afterHooks, name, hook) |
| 21 | ||
| 22 | beforeAll: (hook) => | |
| 23 | 2 | @beforeAllHooks.push hook |
| 24 | ||
| 25 | afterAll: (hook) => | |
| 26 | 2 | @afterAllHooks.push hook |
| 27 | ||
| 28 | beforeEach: (hook) => | |
| 29 | 3 | @beforeEachHooks.push(hook) |
| 30 | ||
| 31 | afterEach: (hook) => | |
| 32 | 3 | @afterEachHooks.push(hook) |
| 33 | ||
| 34 | addHook: (hooks, name, hook) -> | |
| 35 | 10 | if hooks[name] |
| 36 | 4 | hooks[name].push hook |
| 37 | else | |
| 38 | 6 | hooks[name] = [hook] |
| 39 | ||
| 40 | test: (name, hook) => | |
| 41 | 3 | if @contentTests[name]? |
| 42 | 1 | throw new Error("Cannot have more than one test with the name: #{name}") |
| 43 | 2 | @contentTests[name] = hook |
| 44 | ||
| 45 | runBeforeAll: (callback) => | |
| 46 | 5 | async.series @beforeAllHooks, (err, results) -> |
| 47 | 4 | callback(err) |
| 48 | ||
| 49 | runAfterAll: (callback) => | |
| 50 | 5 | async.series @afterAllHooks, (err, results) -> |
| 51 | 5 | callback(err) |
| 52 | ||
| 53 | runBefore: (test, callback) => | |
| 54 | 7 | return callback() unless (@beforeHooks[test.name] or @beforeEachHooks) |
| 55 | ||
| 56 | 7 | hooks = @beforeEachHooks.concat(@beforeHooks[test.name] ? []) |
| 57 | 7 | async.eachSeries hooks, (hook, callback) -> |
| 58 | 7 | hook test, callback |
| 59 | , callback | |
| 60 | ||
| 61 | runAfter: (test, callback) => | |
| 62 | 7 | return callback() unless (@afterHooks[test.name] or @afterEachHooks) |
| 63 | ||
| 64 | 7 | hooks = (@afterHooks[test.name] ? []).concat(@afterEachHooks) |
| 65 | 7 | async.eachSeries hooks, (hook, callback) -> |
| 66 | 5 | hook test, callback |
| 67 | , callback | |
| 68 | ||
| 69 | skip: (name) => | |
| 70 | 1 | @skippedTests.push name |
| 71 | ||
| 72 | hasName: (name) => | |
| 73 | 12 | _.has(@beforeHooks, name) || _.has(@afterHooks, name) |
| 74 | ||
| 75 | skipped: (name) => | |
| 76 | 10 | @skippedTests.indexOf(name) != -1 |
| 77 | ||
| 78 | ||
| 79 | 1 | module.exports = new Hooks() |
| 80 | ||
| 81 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | abao = require './abao' |
| 2 | ||
| 3 | 1 | module.exports = abao |
| 4 | ||
| 5 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | options = |
| 2 | server: | |
| 3 | description: 'Specify API endpoint to use. The RAML-specified baseUri value will be used if not provided' | |
| 4 | type: 'string' | |
| 5 | ||
| 6 | hookfiles: | |
| 7 | alias: 'f' | |
| 8 | description: 'Specify pattern to match files with before/after hooks for running tests' | |
| 9 | type: 'string' | |
| 10 | ||
| 11 | schemas: | |
| 12 | alias: 's' | |
| 13 | description: 'Specify pattern to match schema files to be loaded for use as JSON refs' | |
| 14 | type: 'string' | |
| 15 | ||
| 16 | reporter: | |
| 17 | alias: 'r' | |
| 18 | description: 'Specify reporter to use' | |
| 19 | type: 'string' | |
| 20 | default: 'spec' | |
| 21 | ||
| 22 | header: | |
| 23 | alias: 'h' | |
| 24 | description: 'Add header to include in each request. Header must be in KEY:VALUE format ' + | |
| 25 | '(e.g., "-h Accept:application/json").\nReuse option to add multiple headers' | |
| 26 | type: 'string' | |
| 27 | ||
| 28 | 'hooks-only': | |
| 29 | alias: 'H' | |
| 30 | description: 'Run test only if defined either before or after hooks' | |
| 31 | type: 'boolean' | |
| 32 | ||
| 33 | grep: | |
| 34 | alias: 'g' | |
| 35 | description: 'Only run tests matching <pattern>' | |
| 36 | type: 'string' | |
| 37 | ||
| 38 | invert: | |
| 39 | alias: 'i' | |
| 40 | description: 'Invert --grep matches' | |
| 41 | type: 'boolean' | |
| 42 | ||
| 43 | sorted: | |
| 44 | description: 'Sorts requests in a sensible way so that objects are not ' + | |
| 45 | 'modified before they are created.\nOrder: ' + | |
| 46 | 'CONNECT, OPTIONS, POST, GET, HEAD, PUT, PATCH, DELETE, TRACE.' | |
| 47 | type: 'boolean' | |
| 48 | ||
| 49 | timeout: | |
| 50 | alias: 't' | |
| 51 | description: 'Set test-case timeout in milliseconds' | |
| 52 | type: 'number' | |
| 53 | default: 2000 | |
| 54 | ||
| 55 | template: | |
| 56 | description: 'Specify template file to use for generating hooks' | |
| 57 | type: 'string' | |
| 58 | normalize: true | |
| 59 | ||
| 60 | names: | |
| 61 | alias: 'n' | |
| 62 | description: 'List names of requests and exit' | |
| 63 | type: 'boolean' | |
| 64 | ||
| 65 | 'generate-hooks': | |
| 66 | description: 'Output hooks generated from template file and exit' | |
| 67 | type: 'boolean' | |
| 68 | ||
| 69 | reporters: | |
| 70 | description: 'Display available reporters and exit' | |
| 71 | type: 'boolean' | |
| 72 | ||
| 73 | 1 | module.exports = options |
| 74 | ||
| 75 |
| Line | Hits | Source |
|---|---|---|
| 1 | 2 | Mocha = require 'mocha' |
| 2 | 2 | async = require 'async' |
| 3 | 2 | path = require 'path' |
| 4 | 2 | _ = require 'underscore' |
| 5 | 2 | generateHooks = require './generate-hooks' |
| 6 | ||
| 7 | ||
| 8 | 2 | class TestRunner |
| 9 | 2 | constructor: (options, ramlFile) -> |
| 10 | 10 | @server = options.server |
| 11 | 10 | delete options.server |
| 12 | 10 | @options = options |
| 13 | 10 | @mocha = new Mocha options |
| 14 | 10 | @ramlFile = ramlFile |
| 15 | ||
| 16 | addTestToMocha: (test, hooks) => | |
| 17 | 9 | mocha = @mocha |
| 18 | 9 | options = @options |
| 19 | ||
| 20 | # Generate Test Suite | |
| 21 | 9 | suite = Mocha.Suite.create mocha.suite, test.name |
| 22 | ||
| 23 | # No Response defined | |
| 24 | 9 | if !test.response.status |
| 25 | 1 | suite.addTest new Mocha.Test 'Skip as no response code defined' |
| 26 | 1 | return |
| 27 | ||
| 28 | # No Hooks for this test | |
| 29 | 8 | if not hooks.hasName(test.name) and options['hooks-only'] |
| 30 | 0 | suite.addTest new Mocha.Test 'Skip as no hooks defined' |
| 31 | 0 | return |
| 32 | ||
| 33 | # Test skipped in hook file | |
| 34 | 8 | if hooks.skipped(test.name) |
| 35 | 1 | suite.addTest new Mocha.Test 'Skipped in hooks' |
| 36 | 1 | return |
| 37 | ||
| 38 | # Setup hooks | |
| 39 | 7 | if hooks |
| 40 | 7 | suite.beforeAll _.bind (done) -> |
| 41 | 2 | @hooks.runBefore @test, done |
| 42 | , {hooks, test} | |
| 43 | ||
| 44 | 7 | suite.afterAll _.bind (done) -> |
| 45 | 2 | @hooks.runAfter @test, done |
| 46 | , {hooks, test} | |
| 47 | ||
| 48 | # Setup test | |
| 49 | # Vote test name | |
| 50 | 7 | title = if test.response.schema |
| 51 | 4 | 'Validate response code and body' |
| 52 | else | |
| 53 | 3 | 'Validate response code only' |
| 54 | 7 | suite.addTest new Mocha.Test title, _.bind (done) -> |
| 55 | 2 | @test.run done |
| 56 | , {test} | |
| 57 | ||
| 58 | run: (tests, hooks, done) -> | |
| 59 | 10 | server = @server |
| 60 | 10 | options = @options |
| 61 | 10 | addTestToMocha = @addTestToMocha |
| 62 | 10 | mocha = @mocha |
| 63 | 10 | ramlFile = path.basename @ramlFile |
| 64 | 10 | names = [] |
| 65 | ||
| 66 | 10 | async.waterfall [ |
| 67 | (callback) -> | |
| 68 | 10 | async.each tests, (test, cb) -> |
| 69 | 10 | if options.names || options['generate-hooks'] |
| 70 | # Save test names for use by next step | |
| 71 | 1 | names.push test.name |
| 72 | 1 | return cb() |
| 73 | ||
| 74 | # None shall pass without... | |
| 75 | 9 | return callback(new Error 'no API endpoint specified') if !server |
| 76 | ||
| 77 | # Update test.request | |
| 78 | 9 | test.request.server = server |
| 79 | 9 | _.extend(test.request.headers, options.header) |
| 80 | ||
| 81 | 9 | addTestToMocha test, hooks |
| 82 | 9 | cb() |
| 83 | , callback | |
| 84 | , # Handle options that don't run tests | |
| 85 | (callback) -> | |
| 86 | 10 | if options['generate-hooks'] |
| 87 | # Generate hooks skeleton file | |
| 88 | 0 | templateFile = if options.template |
| 89 | 0 | options.template |
| 90 | else | |
| 91 | 0 | path.join 'templates', 'hookfile.js' |
| 92 | 0 | generateHooks names, ramlFile, templateFile, done |
| 93 | 10 | else if options.names |
| 94 | # Write names to console | |
| 95 | 1 | console.log name for name in names |
| 96 | 1 | return done(null, 0) |
| 97 | else | |
| 98 | 9 | return callback() |
| 99 | , # Run mocha | |
| 100 | (callback) -> | |
| 101 | 9 | mocha.suite.beforeAll _.bind (done) -> |
| 102 | 3 | @hooks.runBeforeAll done |
| 103 | , {hooks} | |
| 104 | 9 | mocha.suite.afterAll _.bind (done) -> |
| 105 | 3 | @hooks.runAfterAll done |
| 106 | , {hooks} | |
| 107 | ||
| 108 | 9 | mocha.run (failures) -> |
| 109 | 9 | callback(null, failures) |
| 110 | ], done | |
| 111 | ||
| 112 | ||
| 113 | 2 | module.exports = TestRunner |
| 114 | ||
| 115 |
| Line | Hits | Source |
|---|---|---|
| 1 | 3 | chai = require 'chai' |
| 2 | 3 | request = require 'request' |
| 3 | 3 | _ = require 'underscore' |
| 4 | 3 | async = require 'async' |
| 5 | 3 | tv4 = require 'tv4' |
| 6 | 3 | fs = require 'fs' |
| 7 | 3 | glob = require 'glob' |
| 8 | ||
| 9 | 3 | assert = chai.assert |
| 10 | ||
| 11 | ||
| 12 | 3 | String::contains = (it) -> |
| 13 | 13 | @indexOf(it) != -1 |
| 14 | ||
| 15 | ||
| 16 | 3 | class TestFactory |
| 17 | 3 | constructor: (schemaLocation) -> |
| 18 | 32 | if schemaLocation |
| 19 | ||
| 20 | 2 | files = glob.sync schemaLocation |
| 21 | 2 | console.log '\tJSON ref schemas: ' + files.join(', ') |
| 22 | ||
| 23 | 2 | tv4.banUnknown = true |
| 24 | ||
| 25 | 2 | for file in files |
| 26 | 2 | tv4.addSchema(JSON.parse(fs.readFileSync(file, 'utf8'))) |
| 27 | ||
| 28 | create: (name, contentTest) -> | |
| 29 | 32 | return new Test(name, contentTest) |
| 30 | ||
| 31 | ||
| 32 | 3 | class Test |
| 33 | 3 | constructor: (@name, @contentTest) -> |
| 34 | 32 | @name ?= '' |
| 35 | 32 | @skip = false |
| 36 | ||
| 37 | 32 | @request = |
| 38 | server: '' | |
| 39 | path: '' | |
| 40 | method: 'GET' | |
| 41 | params: {} | |
| 42 | query: {} | |
| 43 | headers: {} | |
| 44 | body: '' | |
| 45 | ||
| 46 | 32 | @response = |
| 47 | status: '' | |
| 48 | schema: null | |
| 49 | headers: null | |
| 50 | body: null | |
| 51 | ||
| 52 | 32 | @contentTest ?= (response, body, done) -> |
| 53 | 1 | done() |
| 54 | ||
| 55 | url: () -> | |
| 56 | 4 | path = @request.server + @request.path |
| 57 | ||
| 58 | 4 | for key, value of @request.params |
| 59 | 4 | path = path.replace "{#{key}}", value |
| 60 | 4 | return path |
| 61 | ||
| 62 | run: (callback) -> | |
| 63 | 2 | assertResponse = @assertResponse |
| 64 | 2 | contentTest = @contentTest |
| 65 | ||
| 66 | 2 | options = _.pick @request, 'headers', 'method' |
| 67 | 2 | options['url'] = @url() |
| 68 | 2 | if typeof @request.body is 'string' |
| 69 | 0 | options['body'] = @request.body |
| 70 | else | |
| 71 | 2 | options['body'] = JSON.stringify @request.body |
| 72 | 2 | options['qs'] = @request.query |
| 73 | ||
| 74 | 2 | async.waterfall [ |
| 75 | (callback) -> | |
| 76 | 2 | request options, (error, response, body) -> |
| 77 | 2 | callback null, error, response, body |
| 78 | , | |
| 79 | (error, response, body, callback) -> | |
| 80 | 2 | assertResponse(error, response, body) |
| 81 | 2 | contentTest(response, body, callback) |
| 82 | ], callback | |
| 83 | ||
| 84 | assertResponse: (error, response, body) => | |
| 85 | 5 | assert.isNull error |
| 86 | 5 | assert.isNotNull response, 'Response' |
| 87 | ||
| 88 | # Headers | |
| 89 | 5 | @response.headers = response.headers |
| 90 | ||
| 91 | # Status code | |
| 92 | 5 | assert.equal response.statusCode, @response.status, """ |
| 93 | Got unexpected response code: | |
| 94 | 5 | #{body} |
| 95 | Error | |
| 96 | """ | |
| 97 | 5 | response.status = response.statusCode |
| 98 | ||
| 99 | # Body | |
| 100 | 5 | if @response.schema |
| 101 | 5 | schema = @response.schema |
| 102 | 5 | validateJson = _.partial JSON.parse, body |
| 103 | 5 | body = '[empty]' if body is '' |
| 104 | 5 | assert.doesNotThrow validateJson, JSON.SyntaxError, """ |
| 105 | Invalid JSON: | |
| 106 | 5 | #{body} |
| 107 | Error | |
| 108 | """ | |
| 109 | ||
| 110 | 4 | json = validateJson() |
| 111 | 4 | result = tv4.validateResult json, schema |
| 112 | 4 | assert.lengthOf result.missing, 0, """ |
| 113 | 4 | Missing/unresolved JSON schema $refs (#{result.missing?.join(', ')}) in schema: |
| 114 | 4 | #{JSON.stringify(schema, null, 4)} |
| 115 | Error | |
| 116 | """ | |
| 117 | 4 | assert.ok result.valid, """ |
| 118 | 4 | Got unexpected response body: #{result.error?.message} |
| 119 | 4 | #{JSON.stringify(json, null, 4)} |
| 120 | Error | |
| 121 | """ | |
| 122 | ||
| 123 | # Update @response | |
| 124 | 3 | @response.body = json |
| 125 | ||
| 126 | ||
| 127 | 3 | module.exports = TestFactory |
| 128 | ||
| 129 |