apirunner 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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"