apirunner 0.2.4 → 0.2.5

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 CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.2.5
data/apirunner.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{apirunner}
8
- s.version = "0.2.4"
8
+ s.version = "0.2.5"
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"]
@@ -44,8 +44,13 @@ Gem::Specification.new do |s|
44
44
  "lib/api_runner.rb",
45
45
  "lib/apirunner.rb",
46
46
  "lib/apirunner/railtie.rb",
47
+ "lib/body_checker.rb",
48
+ "lib/checker.rb",
47
49
  "lib/expectation_matcher.rb",
50
+ "lib/header_checker.rb",
48
51
  "lib/http_client.rb",
52
+ "lib/json_syntax_checker.rb",
53
+ "lib/response_code_checker.rb",
49
54
  "lib/result.rb",
50
55
  "lib/tasks/api.rake",
51
56
  "lib/testcase.rb",
@@ -0,0 +1,65 @@
1
+ class BodyChecker < Checker
2
+
3
+ def check
4
+ result = Result.new(@testcase, @response)
5
+
6
+ # special case: the whole body has to be matched via a regular expression
7
+ if is_regex?(@testcase.response_expectation['body'])
8
+ if not regex_matches?(@testcase.response_expectation['body'], @response.body)
9
+ result.succeeded = false
10
+ result.error_message = " expected the whole body to match regex --#{@testcase.response_expectation['body']}--\n got --#{@response.body}--"
11
+ end
12
+ return result
13
+ end
14
+
15
+ expected_body_hash = @testcase.response_expectation['body']
16
+
17
+ # in case we have no body expectation we simply return success
18
+ return result if expected_body_hash.nil?
19
+
20
+ # in case the response body is nil or damaged we return an error
21
+ begin
22
+ responded_body_hash = JSON.parse(@response.body)
23
+ rescue
24
+ result = Result.new(@testcase, @response)
25
+ result.succeeded = false
26
+ result.error_message = " expected response to have a body\n got raw body --#{@response.body}-- which is nil or an unparseable hash"
27
+ return result
28
+ end
29
+
30
+ # else we build trees from both body structures...
31
+ expectation_tree = Nokogiri::XML(expected_body_hash.to_xml({ :indent => 0 }))
32
+ response_tree = Nokogiri::XML(responded_body_hash.to_xml({ :indent => 0 }))
33
+
34
+ # retrieve all the leafs pathes and match the leafs values using xpath
35
+ matcher_pathes_from(expectation_tree).each do |path|
36
+ expectation_node = expectation_tree.xpath(path).first
37
+ response_node = response_tree.xpath(path).first
38
+
39
+ debugger
40
+ # in some (not awesome) cases the root node occures as leaf, so we have to skip him here
41
+ next if expectation_node.name == "hash"
42
+
43
+ # return error if response body does not have the expected entry
44
+ if response_node.nil?
45
+ result.succeeded = false
46
+ result.error_message = " expected body to have identifier --#{expectation_node.name}--\n got nil"
47
+ return result
48
+ end
49
+
50
+ # last but not least try the regex or direct match and return errors in case of any
51
+ if is_regex?(expectation_node.text)
52
+ if not (excluded?(expectation_node.name) or regex_matches?(expectation_node.text, response_node.text))
53
+ result.succeeded = false
54
+ result.error_message = " expected body identifier --#{expectation_node.name}-- to match regex --#{expectation_node.text}--\n got --#{response_node.text}--"
55
+ end
56
+ else
57
+ if not (excluded?(expectation_node.name) or string_matches?(expectation_node.text, response_node.text))
58
+ result.succeeded = false
59
+ result.error_message = " expected body identifier --#{expectation_node.name}-- to match --#{expectation_node.text}--\n got --#{response_node.text}--"
60
+ end
61
+ end
62
+ end
63
+ result
64
+ end
65
+ end
data/lib/checker.rb ADDED
@@ -0,0 +1,66 @@
1
+ class Checker
2
+
3
+ def initialize(testcase, response, excludes=nil)
4
+ @testcase = testcase
5
+ @response = response
6
+ @excludes = excludes
7
+ end
8
+
9
+ # executes the checking routine and returns a result object
10
+ # to be overwritten in child classes
11
+ def check
12
+ result = Result.new(@testcase, @response)
13
+ end
14
+
15
+ private
16
+
17
+ # recursively parses the tree and returns a set of relative pathes
18
+ # that can be used to match the both trees leafs
19
+ def matcher_pathes_from(node, pathes = nil)
20
+ pathes ||= []
21
+ if not node.children.blank?
22
+ node.children.each do |sub_node|
23
+ matcher_pathes_from(sub_node, pathes)
24
+ end
25
+ else
26
+ pathes << relative_path(node.parent.path)
27
+ end
28
+ pathes
29
+ end
30
+
31
+ # returns relative path for matching the target tree of the response body
32
+ # explicit array adressing is replaced by *
33
+ def relative_path(path)
34
+ path.gsub(/\/([^\/]+)\[\d+\]\//i,"/*/")
35
+ end
36
+
37
+ # returns true if given attributes is an excluded item that does not have to be evaluated in this environment
38
+ def excluded?(item)
39
+ @excludes.include?(item)
40
+ end
41
+
42
+ # returns true if given string seems to be a regular expression
43
+ def is_regex?(string)
44
+ string.to_s.match(/^\/.+\/$/)
45
+ end
46
+
47
+ # returns true if the given regular expression matches the given value
48
+ def regex_matches?(regex, value)
49
+ regex = Regexp.compile( regex.gsub(/^\//, '').gsub(/\/$/,'') )
50
+ !!value.to_s.match(regex)
51
+ end
52
+
53
+ # returns true if the given string exactly matches the given value
54
+ def string_matches?(string, value)
55
+ string.to_s == value.to_s
56
+ end
57
+
58
+ # parses output into JSON object
59
+ def valid_json?(response_body)
60
+ # responses may be nil, return true then
61
+ return true if response_body.blank?
62
+ # returns true if given response is valid json, else false
63
+ JSON.parse(response_body.to_s) rescue false
64
+ end
65
+
66
+ end
@@ -2,6 +2,11 @@ class ExpectationMatcher
2
2
  require 'result'
3
3
  require 'nokogiri'
4
4
  require 'JSON'
5
+ require 'checker'
6
+ require 'json_syntax_checker'
7
+ require 'header_checker'
8
+ require 'response_code_checker'
9
+ require 'body_checker'
5
10
 
6
11
  def initialize(excludes=nil)
7
12
  @test_types = [:response_code, :response_body_format, :response_headers, :response_body]
@@ -22,156 +27,21 @@ class ExpectationMatcher
22
27
 
23
28
  # matches the given response code
24
29
  def response_code(response, testcase)
25
- result = Result.new(testcase, response)
26
- if not testcase.response_expectation['status_code'].to_s == response.code.to_s
27
- result.succeeded = false
28
- result.error_message = " expected response code --#{testcase.response_expectation['status_code']}--\n got response code --#{response.code}--"
29
- end
30
- result
30
+ ResponseCodeChecker.new(testcase, response).check
31
31
  end
32
32
 
33
33
  # checks the format of the given data of JSON conformity
34
34
  def response_body_format(response, testcase)
35
- result_struct = Struct.new(:succeeded, :error)
36
- results = result_struct.new(:succeeded => true, :error => nil)
37
- result = Result.new(testcase, response)
38
- if not valid_json?(response.body)
39
- result.succeeded = false
40
- result.error_message = "expected valid JSON in body\n got --#{response.body[1..400]}--"
41
- end
42
- result
35
+ JsonSyntaxChecker.new(testcase, response).check
43
36
  end
44
37
 
45
38
  # matches the given response header
46
39
  def response_headers(response, testcase)
47
- result = Result.new(testcase, response)
48
-
49
- testcase.response_expectation['headers'].each_pair do |header_name, header_value|
50
- if is_regex?(header_value)
51
- if not (excluded?(header_name) or regex_matches?(header_value, response.headers[header_name]))
52
- result.succeeded = false
53
- result.error_message = " expected header identifier --#{header_name}-- to match regex --#{header_value}--\n got --#{response.headers[header_name]}--"
54
- end
55
- else
56
- if not (excluded?(header_name) or string_matches?(header_value, response.headers[header_name]))
57
- result.succeeded = false
58
- result.error_message = " expected header identifier --#{header_name}-- to match --#{header_value}--\n got --#{response.headers[header_name]}--"
59
- end
60
- end
61
- end unless (testcase.response_expectation['headers'].nil? or testcase.response_expectation['headers'].empty?)
62
- return result
40
+ HeaderChecker.new(testcase, response, @excludes).check
63
41
  end
64
42
 
65
43
  # matches the given attributes and values against the ones from the response body
66
44
  def response_body(response, testcase)
67
- result = Result.new(testcase, response)
68
-
69
- # special case: the whole body has to be matched via a regular expression
70
- if is_regex?(testcase.response_expectation['body'])
71
- if not regex_matches?(testcase.response_expectation['body'], response.body)
72
- result.succeeded = false
73
- result.error_message = " expected the whole body to match regex --#{testcase.response_expectation['body']}--\n got --#{response.body}--"
74
- end
75
- return result
76
- end
77
-
78
- expected_body_hash = testcase.response_expectation['body']
79
-
80
- # in case we have no body expectation we simply return success
81
- return result if expected_body_hash.nil?
82
-
83
- # in case the response body is nil or damaged we return an error
84
- begin
85
- responded_body_hash = JSON.parse(response.body)
86
- rescue
87
- result = Result.new(testcase, response)
88
- result.succeeded = false
89
- result.error_message = " expected response to have a body\n got raw body --#{response.body}-- which is nil or an unparseable hash"
90
- return result
91
- end
92
-
93
- # else we build trees from both body structures...
94
- expectation_tree = Nokogiri::XML(expected_body_hash.to_xml({ :indent => 0 }))
95
- response_tree = Nokogiri::XML(responded_body_hash.to_xml({ :indent => 0 }))
96
-
97
- # retrieve all the leafs pathes and match the leafs values using xpath
98
- matcher_pathes_from(expectation_tree).each do |path|
99
- expectation_node = expectation_tree.xpath(path).first
100
- response_node = response_tree.xpath(path).first
101
-
102
- debugger
103
- # in some (not awesome) cases the root node occures as leaf, so we have to skip him here
104
- next if expectation_node.name == "hash"
105
-
106
- # return error if response body does not have the expected entry
107
- if response_node.nil?
108
- result.succeeded = false
109
- result.error_message = " expected body to have identifier --#{expectation_node.name}--\n got nil"
110
- return result
111
- end
112
-
113
- # last but not least try the regex or direct match and return errors in case of any
114
- if is_regex?(expectation_node.text)
115
- if not (excluded?(expectation_node.name) or regex_matches?(expectation_node.text, response_node.text))
116
- result.succeeded = false
117
- result.error_message = " expected body identifier --#{expectation_node.name}-- to match regex --#{expectation_node.text}--\n got --#{response_node.text}--"
118
- end
119
- else
120
- if not (excluded?(expectation_node.name) or string_matches?(expectation_node.text, response_node.text))
121
- result.succeeded = false
122
- result.error_message = " expected body identifier --#{expectation_node.name}-- to match --#{expectation_node.text}--\n got --#{response_node.text}--"
123
- end
124
- end
125
- end
126
- result
127
- end
128
-
129
- # recursively parses the tree and returns a set of relative pathes
130
- # that can be used to match the both trees leafs
131
- def matcher_pathes_from(node, pathes = nil)
132
- pathes ||= []
133
- if not node.children.blank?
134
- node.children.each do |sub_node|
135
- matcher_pathes_from(sub_node, pathes)
136
- end
137
- else
138
- pathes << relative_path(node.parent.path)
139
- end
140
- pathes
141
- end
142
-
143
- # returns relative path for matching the target tree of the response body
144
- # explicit array adressing is replaced by *
145
- def relative_path(path)
146
- path.gsub(/\/([^\/]+)\[\d+\]\//i,"/*/")
147
- end
148
-
149
- # returns true if given attributes is an excluded item that does not have to be evaluated in this environment
150
- def excluded?(item)
151
- @excludes.include?(item)
152
- end
153
-
154
- # returns true if given string seems to be a regular expression
155
- def is_regex?(string)
156
- string.to_s.match(/^\/.+\/$/)
157
- end
158
-
159
- # returns true if the given regular expression matches the given value
160
- def regex_matches?(regex, value)
161
- regex = Regexp.compile( regex.gsub(/^\//, '').gsub(/\/$/,'') )
162
- !!value.to_s.match(regex)
163
- end
164
-
165
- # returns true if the given string exactly matches the given value
166
- def string_matches?(string, value)
167
- string.to_s == value.to_s
168
- end
169
-
170
- # parses output into JSON object
171
- def valid_json?(response_body)
172
- # responses may be nil, return true then
173
- return true if response_body.blank?
174
- # returns true if given response is valid json, else false
175
- JSON.parse(response_body.to_s) rescue false
45
+ BodyChecker.new(testcase, response, @excludes).check
176
46
  end
177
47
  end
@@ -0,0 +1,22 @@
1
+ class HeaderChecker < Checker
2
+
3
+ # checks given header against the given expepctation and returns a result object
4
+ def check
5
+ result = Result.new(@testcase, @response)
6
+ @testcase.response_expectation['headers'].each_pair do |header_name, header_value|
7
+ if is_regex?(header_value)
8
+ if not (excluded?(header_name) or regex_matches?(header_value, @response.headers[header_name]))
9
+ result.succeeded = false
10
+ result.error_message = " expected header identifier --#{header_name}-- to match regex --#{header_value}--\n got --#{@response.headers[header_name]}--"
11
+ end
12
+ else
13
+ if not (excluded?(header_name) or string_matches?(header_value, @response.headers[header_name]))
14
+ result.succeeded = false
15
+ result.error_message = " expected header identifier --#{header_name}-- to match --#{header_value}--\n got --#{@response.headers[header_name]}--"
16
+ end
17
+ end
18
+ end unless (@testcase.response_expectation['headers'].nil? or @testcase.response_expectation['headers'].empty?)
19
+ result
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
1
+ class JsonSyntaxChecker < Checker
2
+
3
+ # checks if the given testcase body represents syntactically valid JSON
4
+ def check
5
+ result = Result.new(@testcase, @response)
6
+ if not valid_json?(@response.body)
7
+ result.succeeded = false
8
+ result.error_message = "expected valid JSON in body\n got --#{@response.body[1..400]}--"
9
+ end
10
+ result
11
+ end
12
+
13
+ end
@@ -0,0 +1,13 @@
1
+ class ResponseCodeChecker < Checker
2
+
3
+ # checks the given responses status code against the one in the expectation and returns result object
4
+ def check
5
+ result = Result.new(@testcase, @response)
6
+ if not @testcase.response_expectation['status_code'].to_s == @response.code.to_s
7
+ result.succeeded = false
8
+ result.error_message = " expected response code --#{@testcase.response_expectation['status_code']}--\n got response code --#{@response.code}--"
9
+ end
10
+ result
11
+ end
12
+
13
+ end
data/lib/result.rb CHANGED
@@ -43,17 +43,17 @@ class Result
43
43
 
44
44
  # yields the verbose error messages
45
45
  def be_verbose(index)
46
- puts "\n#{result_case} (#{index+1}) - \"#{@testcase['name']}\""
46
+ puts "\n#{result_case} (#{index+1}) - \"#{@testcase.name}\""
47
47
  puts @error_message
48
48
  puts(" More more more verbosity\n")
49
- puts(" request method: #{@testcase['request']['method']}")
50
- puts(" resource path: #{@testcase['request']['path']}")
51
- puts(" request headers: #{@testcase['request']['headers']}")
52
- puts(" JSON body sent: #{@testcase['request']['body']}")
49
+ puts(" request method: #{@testcase.request['method']}")
50
+ puts(" resource path: #{@testcase.request['path']}")
51
+ puts(" request headers: #{@testcase.request['headers']}")
52
+ puts(" JSON body sent: #{@testcase.request['body']}")
53
53
  puts(" expectation:")
54
- puts(" response status code: #{@testcase['response_expectation']['status_code']}")
55
- puts(" response headers: #{@testcase['response_expectation']['headers']}")
56
- puts(" response body: #{@testcase['response_expectation']['body']}")
54
+ puts(" response status code: #{@testcase.response_expectation['status_code']}")
55
+ puts(" response headers: #{@testcase.response_expectation['headers']}")
56
+ puts(" response body: #{@testcase.response_expectation['body']}")
57
57
  puts(" result:")
58
58
  puts(" response status code: #{@response.code}")
59
59
  puts(" response headers: #{@response.headers}")
data/lib/testcase.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  class Testcase
2
2
 
3
- attr_reader :raw, :request, :response_expectation
3
+ attr_reader :raw, :name, :request, :response_expectation
4
4
 
5
5
  def initialize(raw)
6
6
  @raw = raw
7
+ @name = raw['name']
7
8
  @request = @raw['request']
8
9
  @response_expectation = @raw['response_expectation']
9
10
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 4
9
- version: 0.2.4
8
+ - 5
9
+ version: 0.2.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - jan@moviepilot.com
@@ -248,8 +248,13 @@ files:
248
248
  - lib/api_runner.rb
249
249
  - lib/apirunner.rb
250
250
  - lib/apirunner/railtie.rb
251
+ - lib/body_checker.rb
252
+ - lib/checker.rb
251
253
  - lib/expectation_matcher.rb
254
+ - lib/header_checker.rb
252
255
  - lib/http_client.rb
256
+ - lib/json_syntax_checker.rb
257
+ - lib/response_code_checker.rb
253
258
  - lib/result.rb
254
259
  - lib/tasks/api.rake
255
260
  - lib/testcase.rb
@@ -272,7 +277,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
272
277
  requirements:
273
278
  - - ">="
274
279
  - !ruby/object:Gem::Version
275
- hash: -4241672918987621375
280
+ hash: 197723826225118285
276
281
  segments:
277
282
  - 0
278
283
  version: "0"