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 |