apirunner 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/VERSION +1 -1
- data/apirunner.gemspec +4 -2
- data/examples/config/api_runner.yml +6 -0
- data/lib/api_configuration.rb +6 -0
- data/lib/api_runner.rb +19 -13
- data/lib/expectation_matcher.rb +29 -33
- data/lib/result.rb +69 -0
- metadata +6 -4
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.9
|
data/apirunner.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{apirunner}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.9"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["jan@moviepilot.com"]
|
12
|
-
s.date = %q{2010-09-
|
12
|
+
s.date = %q{2010-09-27}
|
13
13
|
s.description = %q{apirunner is a testsuite to query your RESTful JSON API and match response with your defined expectations}
|
14
14
|
s.email = %q{developers@moviepilot.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -34,11 +34,13 @@ Gem::Specification.new do |s|
|
|
34
34
|
"features/apirunner.feature",
|
35
35
|
"features/step_definitions/apirunner_steps.rb",
|
36
36
|
"features/support/env.rb",
|
37
|
+
"lib/api_configuration.rb",
|
37
38
|
"lib/api_runner.rb",
|
38
39
|
"lib/apirunner.rb",
|
39
40
|
"lib/apirunner/railtie.rb",
|
40
41
|
"lib/expectation_matcher.rb",
|
41
42
|
"lib/http_client.rb",
|
43
|
+
"lib/result.rb",
|
42
44
|
"lib/tasks/api.rake",
|
43
45
|
"spec/.rspec",
|
44
46
|
"spec/api_runner_spec.rb",
|
data/lib/api_runner.rb
CHANGED
@@ -2,6 +2,7 @@ class ApiRunner
|
|
2
2
|
require 'yaml'
|
3
3
|
require 'expectation_matcher'
|
4
4
|
require 'http_client'
|
5
|
+
require 'api_configuration'
|
5
6
|
|
6
7
|
CONFIG_FILE = "config/api_runner.yml"
|
7
8
|
SPEC_PATH = "test/api_runner/"
|
@@ -10,12 +11,13 @@ class ApiRunner
|
|
10
11
|
# initializes the object, loads environment, build base_uri
|
11
12
|
def initialize(env)
|
12
13
|
@spec = []
|
13
|
-
@
|
14
|
+
@results = []
|
14
15
|
@excludes = []
|
16
|
+
@configuration = ApiConfiguration.new
|
15
17
|
load_config(env)
|
16
18
|
load_excludes(env)
|
17
19
|
load_url_spec
|
18
|
-
@http_client = HttpClient.new(@host, @port, @namespace)
|
20
|
+
@http_client = HttpClient.new(@configuration.host, @configuration.port, @configuration.namespace)
|
19
21
|
@expectation = ExpectationMatcher.new(@excludes)
|
20
22
|
end
|
21
23
|
|
@@ -23,11 +25,11 @@ class ApiRunner
|
|
23
25
|
def run
|
24
26
|
if server_is_available?
|
25
27
|
run_tests
|
26
|
-
@
|
27
|
-
|
28
|
-
end unless @
|
28
|
+
@results.each_with_index do |result, index|
|
29
|
+
result.honk_in(@configuration.verbosity, index)
|
30
|
+
end unless @results.empty?
|
29
31
|
else
|
30
|
-
puts("Server #{@host} seems to be unavailable!")
|
32
|
+
puts("Server #{@configuration.host} seems to be unavailable!")
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -38,13 +40,16 @@ class ApiRunner
|
|
38
40
|
@spec.each do |test_case|
|
39
41
|
response = send_request(test_case['request']['method'].downcase.to_sym, test_case['request']['path'], test_case['request']['body'])
|
40
42
|
@expectation.test_types.each do |test_type|
|
41
|
-
|
42
|
-
if not
|
43
|
-
@errors << test.error
|
43
|
+
result = @expectation.check(test_type, response, test_case)
|
44
|
+
if not result.succeeded
|
44
45
|
putc "F"
|
46
|
+
@results << result
|
45
47
|
break
|
46
48
|
else
|
47
|
-
|
49
|
+
if test_type == @expectation.test_types.last
|
50
|
+
@results << result
|
51
|
+
putc "."
|
52
|
+
end
|
48
53
|
end
|
49
54
|
end
|
50
55
|
end
|
@@ -57,19 +62,20 @@ class ApiRunner
|
|
57
62
|
|
58
63
|
# builds target uri from base uri generated of host port and namespace as well as the ressource path
|
59
64
|
def target_uri
|
60
|
-
"#{@protocol}://#{@host}"
|
65
|
+
"#{@configuration.protocol}://#{@configuration.host}"
|
61
66
|
end
|
62
67
|
|
63
68
|
# returns true if server is available
|
64
69
|
def server_is_available?
|
65
70
|
return true
|
66
|
-
!@http_client.send_request(:get, "#{@protocol}://#{@host}:#{@port}", {:timeout => 5}).nil?
|
71
|
+
!@http_client.send_request(:get, "#{@configuration.protocol}://#{@configuration.host}:#{@configuration.port}", {:timeout => 5}).nil?
|
67
72
|
end
|
68
73
|
|
69
74
|
# loads environment config data from yaml file
|
70
75
|
def load_config(env)
|
71
76
|
config = YAML.load_file(self.class.config_file)
|
72
|
-
config[env.to_s].each { |key, value| instance_variable_set("@#{key}", value) }
|
77
|
+
config[env.to_s].each { |key, value| @configuration.instance_variable_set("@#{key}", value) }
|
78
|
+
@configuration.verbosity = config['general']['verbosity'].first
|
73
79
|
end
|
74
80
|
|
75
81
|
# loads spec cases from yaml files
|
data/lib/expectation_matcher.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
class ExpectationMatcher
|
2
|
+
require 'result'
|
2
3
|
require 'nokogiri'
|
3
4
|
require 'JSON'
|
4
5
|
|
@@ -21,13 +22,12 @@ class ExpectationMatcher
|
|
21
22
|
|
22
23
|
# matches the given response code
|
23
24
|
def response_code(response, testcase)
|
24
|
-
|
25
|
-
results = result_struct.new(:succeeded => true, :error => nil)
|
25
|
+
result = Result.new(testcase, response)
|
26
26
|
if not testcase['response_expectation']['status_code'].to_s == response.code.to_s
|
27
|
-
|
28
|
-
|
27
|
+
result.succeeded = false
|
28
|
+
result.error_message = " expected response code --#{testcase['response_expectation']['status_code']}--\n got response code --#{response.code}--"
|
29
29
|
end
|
30
|
-
|
30
|
+
result
|
31
31
|
end
|
32
32
|
|
33
33
|
# checks the format of the given data of JSON conformity
|
@@ -35,55 +35,51 @@ class ExpectationMatcher
|
|
35
35
|
def response_body_format(response, testcase)
|
36
36
|
result_struct = Struct.new(:succeeded, :error)
|
37
37
|
results = result_struct.new(:succeeded => true, :error => nil)
|
38
|
+
result = Result.new(testcase, response)
|
38
39
|
if not valid_json?(response.body)
|
39
|
-
|
40
|
-
|
40
|
+
result.succeeded = false
|
41
|
+
result.error_message = "expected valid JSON in body\n got --#{response.body[1..400]}--"
|
41
42
|
end
|
42
|
-
|
43
|
+
result
|
43
44
|
end
|
44
45
|
|
45
46
|
# matches the given response header
|
46
47
|
def response_headers(response, testcase)
|
47
|
-
|
48
|
-
results = result_struct.new(:succeeded => true, :error => nil)
|
48
|
+
result = Result.new(testcase, response)
|
49
49
|
|
50
50
|
testcase['response_expectation']['headers'].each_pair do |header_name, header_value|
|
51
51
|
if is_regex?(header_value)
|
52
52
|
if not (excluded?(header_name) or regex_matches?(header_value, response.headers[header_name]))
|
53
|
-
|
54
|
-
|
53
|
+
result.succeeded = false
|
54
|
+
result.error_message = " expected header identifier --#{header_name}-- to match regex --#{header_value}--\n got --#{response.headers[header_name]}--"
|
55
55
|
end
|
56
56
|
else
|
57
57
|
if not (excluded?(header_name) or string_matches?(header_value, response.headers[header_name]))
|
58
|
-
|
59
|
-
|
58
|
+
result.succeeded = false
|
59
|
+
result.error_message = " expected header identifier --#{header_name}-- to match --#{header_value}--\n got --#{response.headers[header_name]}--"
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end unless (testcase['response_expectation']['headers'].nil? or testcase['response_expectation']['headers'].empty?)
|
63
|
-
return
|
63
|
+
return result
|
64
64
|
end
|
65
65
|
|
66
66
|
# matches the given attributes and values against the ones from the response body
|
67
67
|
def response_body(response, testcase)
|
68
|
-
|
69
|
-
puts("got response --#{response.body}--\n\n")
|
70
|
-
puts("exp response --#{testcase['response_expectation']['body']}--\n")
|
71
|
-
puts("___________________________\n\n\n")
|
72
|
-
result_struct = Struct.new(:succeeded, :error)
|
73
|
-
results = result_struct.new(:succeeded => true, :error => nil)
|
68
|
+
result = Result.new(testcase, response)
|
74
69
|
|
75
70
|
expected_body_hash = testcase['response_expectation']['body']
|
76
71
|
|
77
72
|
# in case we have no body expectation we simply return success
|
78
|
-
return
|
73
|
+
return result if expected_body_hash.nil?
|
79
74
|
|
80
75
|
# in case the response body is nil or damaged we return an error
|
81
76
|
begin
|
82
77
|
responded_body_hash = JSON.parse(response.body)
|
83
78
|
rescue
|
84
|
-
|
85
|
-
|
86
|
-
|
79
|
+
result = Result.new(testcase, response)
|
80
|
+
result.success = false
|
81
|
+
result.error_message = " expected response to have a body\n got raw body --#{response.body}-- which is nil or an unparseable hash"
|
82
|
+
return result
|
87
83
|
end
|
88
84
|
|
89
85
|
# else we build trees from both body structures...
|
@@ -97,25 +93,25 @@ class ExpectationMatcher
|
|
97
93
|
|
98
94
|
# return error if response body does not have the expected entry
|
99
95
|
if response_node.nil?
|
100
|
-
|
101
|
-
|
102
|
-
return
|
96
|
+
result.succeeded = false
|
97
|
+
result.error_message = " expected body to have identifier --#{expectation_node.name}--\n got nil"
|
98
|
+
return result
|
103
99
|
end
|
104
100
|
|
105
101
|
# last but not least try the regex or direct match and return errors in case of any
|
106
102
|
if is_regex?(expectation_node.text)
|
107
103
|
if not (excluded?(expectation_node.name) or regex_matches?(expectation_node.text, response_node.text))
|
108
|
-
|
109
|
-
|
104
|
+
result.succeeded = false
|
105
|
+
result.error_message = " expected body identifier --#{expectation_node.name}-- to match regex --#{expectation_node.text}--\n got --#{response_node.text}--"
|
110
106
|
end
|
111
107
|
else
|
112
108
|
if not (excluded?(expectation_node.name) or string_matches?(expectation_node.text, response_node.text))
|
113
|
-
|
114
|
-
|
109
|
+
result.succeeded = false
|
110
|
+
result.error_message = " expected body identifier --#{expectation_node.name}-- to match --#{expectation_node.text}--\n got --#{response_node.text}--"
|
115
111
|
end
|
116
112
|
end
|
117
113
|
end
|
118
|
-
|
114
|
+
result
|
119
115
|
end
|
120
116
|
|
121
117
|
# recursively parses the tree and returns a set of relative pathes
|
data/lib/result.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
class Result
|
2
|
+
|
3
|
+
attr_accessor :succeeded
|
4
|
+
attr_accessor :error_message
|
5
|
+
|
6
|
+
def initialize(testcase, response)
|
7
|
+
@succeeded = true
|
8
|
+
@testcase = testcase
|
9
|
+
@response = response
|
10
|
+
@error_message = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# honk out the errors message corresponding to the verbosity configuration the user took
|
14
|
+
def honk_in(verbosity="rspec", index)
|
15
|
+
self.send(verbosity.to_sym, index)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# yields out rspec like error messages only in case of an error
|
21
|
+
def rspec(index)
|
22
|
+
if not @succeeded
|
23
|
+
puts "\nError (#{index}) - \"#{@testcase['name']}\""
|
24
|
+
puts @error_message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# yields a more verbose error message only in case of an error
|
29
|
+
def verbose_on_error(index)
|
30
|
+
if not @succeeded
|
31
|
+
be_verbose(index)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# yields a more verbose message in case of an error AND success
|
36
|
+
def verbose_on_success(index)
|
37
|
+
be_verbose(index)
|
38
|
+
end
|
39
|
+
|
40
|
+
# yields a more verbose message in any case and includes a curl command to manually simulate the testcase
|
41
|
+
def verbose_with_curl(index)
|
42
|
+
be_verbose(index)
|
43
|
+
puts "\n simulate this call with: \"curl TODO\""
|
44
|
+
end
|
45
|
+
|
46
|
+
# yields the verbose error messages
|
47
|
+
def be_verbose(index)
|
48
|
+
puts "\n#{result_case} (#{index+1}) - \"#{@testcase['name']}\""
|
49
|
+
puts @error_message
|
50
|
+
puts (" More more more verbosity\n")
|
51
|
+
puts (" request method: #{@testcase['request']['method']}")
|
52
|
+
puts (" resource path: #{@testcase['request']['path']}")
|
53
|
+
puts (" request headers: #{@testcase['request']['headers']}")
|
54
|
+
puts (" JSON body sent: #{@testcase['request']['body']}")
|
55
|
+
puts (" response status code: #{@testcase['response_expectation']['status_code']}")
|
56
|
+
puts (" response headers: #{@testcase['response_expectation']['headers']}")
|
57
|
+
puts (" response body: #{@testcase['response_expectation']['body']}")
|
58
|
+
end
|
59
|
+
|
60
|
+
# returns the result case for interpolation in the output message header
|
61
|
+
def result_case
|
62
|
+
if @succeeded
|
63
|
+
"Success"
|
64
|
+
else
|
65
|
+
"Error"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 9
|
9
|
+
version: 0.1.9
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- jan@moviepilot.com
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
17
|
+
date: 2010-09-27 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -238,11 +238,13 @@ files:
|
|
238
238
|
- features/apirunner.feature
|
239
239
|
- features/step_definitions/apirunner_steps.rb
|
240
240
|
- features/support/env.rb
|
241
|
+
- lib/api_configuration.rb
|
241
242
|
- lib/api_runner.rb
|
242
243
|
- lib/apirunner.rb
|
243
244
|
- lib/apirunner/railtie.rb
|
244
245
|
- lib/expectation_matcher.rb
|
245
246
|
- lib/http_client.rb
|
247
|
+
- lib/result.rb
|
246
248
|
- lib/tasks/api.rake
|
247
249
|
- spec/.rspec
|
248
250
|
- spec/api_runner_spec.rb
|
@@ -263,7 +265,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
265
|
requirements:
|
264
266
|
- - ">="
|
265
267
|
- !ruby/object:Gem::Version
|
266
|
-
hash:
|
268
|
+
hash: -2943715373765808099
|
267
269
|
segments:
|
268
270
|
- 0
|
269
271
|
version: "0"
|