apirunner 0.3.3 → 0.3.4

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.3.3
1
+ 0.3.4
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.3.3"
8
+ s.version = "0.3.4"
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-10-01}
12
+ s.date = %q{2010-10-07}
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 = [
@@ -45,6 +45,7 @@ Gem::Specification.new do |s|
45
45
  "lib/apirunner.rb",
46
46
  "lib/apirunner/railtie.rb",
47
47
  "lib/checker.rb",
48
+ "lib/core_extensions.rb",
48
49
  "lib/expectation_matcher.rb",
49
50
  "lib/http_client.rb",
50
51
  "lib/plugins/response_body_checker.rb",
data/lib/api_runner.rb CHANGED
@@ -4,6 +4,8 @@ class ApiRunner
4
4
  require 'http_client'
5
5
  require 'api_configuration'
6
6
  require 'testcase'
7
+ require 'core_extensions'
8
+ include CoreExtensions
7
9
 
8
10
  CONFIG_FILE = "config/api_runner.yml"
9
11
  SPEC_PATH = "test/api_runner/"
@@ -41,15 +43,16 @@ class ApiRunner
41
43
  def run_tests
42
44
  puts "Running exactly #{@spec.size} tests."
43
45
  @spec.each do |test_case|
46
+ sleep test_case.wait_before_request
44
47
  response = send_request_for(test_case)
45
- @expectation.test_types.each do |test_type|
46
- result = @expectation.check(test_type, response, test_case)
47
- if not result.succeeded
48
+ Checker.available_plugins.each do |plugin|
49
+ result = @expectation.check(plugin, response, test_case)
50
+ if not result.success?
48
51
  putc "F"
49
52
  @results << result
50
53
  break
51
54
  else
52
- if test_type == @expectation.test_types.last
55
+ if plugin == Checker.available_plugins.last
53
56
  @results << result
54
57
  putc "."
55
58
  end
data/lib/checker.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  class Checker
2
2
 
3
+ @@children = []
4
+
3
5
  def initialize(testcase, response, excludes=nil)
4
6
  @testcase = testcase
5
7
  @response = response
@@ -12,7 +14,18 @@ class Checker
12
14
  result = Result.new(@testcase, @response)
13
15
  end
14
16
 
15
- private
17
+ # returns a list of symbolized plugin names
18
+ def self.available_plugins
19
+ return @@children.map{ |child| child.to_s }
20
+ end
21
+
22
+ private
23
+
24
+ # tracks all children of this class
25
+ # this way plugins can be loaded automagically
26
+ def self.inherited(child)
27
+ @@children << child
28
+ end
16
29
 
17
30
  # recursively parses the tree and returns a set of relative pathes
18
31
  # that can be used to match the both trees leafs
@@ -0,0 +1,17 @@
1
+ module CoreExtensions
2
+ class String
3
+ # generates filenames from classnames the rails way
4
+ def underscore(string)
5
+ string.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
6
+ end
7
+
8
+ # opposites underscore defined above
9
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
10
+ if first_letter_in_uppercase
11
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
12
+ else
13
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,45 +1,30 @@
1
1
  class ExpectationMatcher
2
2
  require 'result'
3
3
  require 'checker'
4
- require 'plugins/response_json_syntax_checker'
5
- require 'plugins/response_header_checker'
6
- require 'plugins/response_code_checker'
7
- require 'plugins/response_body_checker'
4
+
5
+ # dynamically load plugins
6
+ dir = File.dirname(__FILE__) + '/plugins/'
7
+ $LOAD_PATH.unshift(dir)
8
+ Dir[File.join(dir, "*.rb")].each {|file| require File.basename(file) }
8
9
 
9
10
  def initialize(excludes=nil)
10
- @test_types = [:response_code, :response_json_syntax, :response_header, :response_body]
11
11
  @excludes = excludes || []
12
12
  end
13
13
 
14
- # returns the available test types if this matcher class
15
- def test_types
16
- return @test_types
17
- end
18
-
19
14
  # dispatches incoming matching requests
20
- def check(method, response, testcase)
21
- self.send(method, response, testcase)
15
+ def check(plugin, response, testcase)
16
+ self.send(plugin.underscore, response, testcase)
22
17
  end
23
18
 
24
- private
25
-
26
- # matches the given response code
27
- def response_code(response, testcase)
28
- ResponseCodeChecker.new(testcase, response).check
19
+ # dynamically generates methods that invoke "check" on all available plugins
20
+ def self.initialize_plugins
21
+ Checker.available_plugins.each do |plugin|
22
+ define_method(plugin.underscore) do |response, testcase|
23
+ eval(plugin).new(testcase, response, @excludes).check
24
+ end
25
+ private plugin.underscore.to_sym
26
+ end
29
27
  end
30
28
 
31
- # checks the format of the given data of JSON conformity
32
- def response_json_syntax(response, testcase)
33
- ResponseJsonSyntaxChecker.new(testcase, response).check
34
- end
35
-
36
- # matches the given response header
37
- def response_header(response, testcase)
38
- ResponseHeaderChecker.new(testcase, response, @excludes).check
39
- end
40
-
41
- # matches the given attributes and values against the ones from the response body
42
- def response_body(response, testcase)
43
- ResponseBodyChecker.new(testcase, response, @excludes).check
44
- end
29
+ initialize_plugins
45
30
  end
@@ -1,64 +1,68 @@
1
1
  class ResponseBodyChecker < Checker
2
- require 'nokogiri'
2
+ require 'nokogiri'
3
3
 
4
4
  def check
5
5
  result = Result.new(@testcase, @response)
6
-
7
- # special case: the whole body has to be matched via a regular expression
8
- if is_regex?(@testcase.response_expectation['body'])
9
- if not regex_matches?(@testcase.response_expectation['body'], @response.body)
10
- result.succeeded = false
11
- result.error_message = " expected the whole body to match regex --#{@testcase.response_expectation['body']}--\n got --#{@response.body}--"
12
- end
13
- return result
14
- end
15
-
16
- expected_body_hash = @testcase.response_expectation['body']
17
-
18
- # in case we have no body expectation we simply return success
19
- return result if expected_body_hash.nil?
20
-
21
- # in case the response body is nil or damaged we return an error
22
6
  begin
23
- responded_body_hash = JSON.parse(@response.body)
24
- rescue
25
- result = Result.new(@testcase, @response)
26
- result.succeeded = false
27
- result.error_message = " expected response to have a body\n got raw body --#{@response.body}-- which is nil or an unparseable hash"
28
- return result
29
- end
30
-
31
- # else we build trees from both body structures...
32
- expectation_tree = Nokogiri::XML(expected_body_hash.to_xml({ :indent => 0 }))
33
- response_tree = Nokogiri::XML(responded_body_hash.to_xml({ :indent => 0 }))
7
+ # special case: the whole body has to be matched via a regular expression
8
+ if is_regex?(@testcase.response_expectation['body'])
9
+ if not regex_matches?(@testcase.response_expectation['body'], @response.body)
10
+ result.succeeded = false
11
+ result.error_message = " expected the whole body to match regex --#{@testcase.response_expectation['body']}--\n got --#{@response.body}--"
12
+ end
13
+ return result
14
+ end
34
15
 
35
- # retrieve all the leafs pathes and match the leafs values using xpath
36
- matcher_pathes_from(expectation_tree).each do |path|
37
- expectation_node = expectation_tree.xpath(path).first
38
- response_node = response_tree.xpath(path).first
16
+ expected_body_hash = @testcase.response_expectation['body']
39
17
 
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"
18
+ # in case we have no body expectation we simply return success
19
+ return result if expected_body_hash.nil?
42
20
 
43
- # return error if response body does not have the expected entry
44
- if response_node.nil?
21
+ # in case the response body is nil or damaged we return an error
22
+ begin
23
+ responded_body_hash = JSON.parse(@response.body)
24
+ rescue
25
+ result = Result.new(@testcase, @response)
45
26
  result.succeeded = false
46
- result.error_message = " expected body to have identifier --#{expectation_node.name}--\n got nil"
27
+ result.error_message = " expected response to have a body\n got raw body --#{@response.body}-- which is nil or an unparseable hash"
47
28
  return result
48
29
  end
49
30
 
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))
31
+ # else we build trees from both body structures...
32
+ expectation_tree = Nokogiri::XML(expected_body_hash.to_xml({ :indent => 0 }))
33
+ response_tree = Nokogiri::XML(responded_body_hash.to_xml({ :indent => 0 }))
34
+
35
+ # retrieve all the leafs pathes and match the leafs values using xpath
36
+ matcher_pathes_from(expectation_tree).each do |path|
37
+ expectation_node = expectation_tree.xpath(path).first
38
+ response_node = response_tree.xpath(path).first
39
+
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?
53
45
  result.succeeded = false
54
- result.error_message = " expected body identifier --#{expectation_node.name}-- to match regex --#{expectation_node.text}--\n got --#{response_node.text}--"
46
+ result.error_message = " expected body to have identifier --#{expectation_node.name}--\n got nil"
47
+ return result
55
48
  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}--"
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
60
61
  end
61
62
  end
63
+ rescue
64
+ result.succeeded = false
65
+ result.error_message = " unexpected error while parsing testcase/response. Check your testcase format!"
62
66
  end
63
67
  result
64
68
  end
@@ -3,9 +3,14 @@ class ResponseCodeChecker < Checker
3
3
  # checks the given responses status code against the one in the expectation and returns result object
4
4
  def check
5
5
  result = Result.new(@testcase, @response)
6
- if not @testcase.response_expectation['status_code'].to_s == @response.code.to_s
6
+ begin
7
+ if not @testcase.response_expectation['status_code'].to_s == @response.code.to_s
8
+ result.succeeded = false
9
+ result.error_message = " expected response code --#{@testcase.response_expectation['status_code']}--\n got response code --#{@response.code}--"
10
+ end
11
+ rescue
7
12
  result.succeeded = false
8
- result.error_message = " expected response code --#{@testcase.response_expectation['status_code']}--\n got response code --#{@response.code}--"
13
+ result.error_message = " unexpected error while parsing testcase/response. Check your testcase format!"
9
14
  end
10
15
  result
11
16
  end
@@ -3,19 +3,24 @@ class ResponseHeaderChecker < Checker
3
3
  # checks given header against the given expepctation and returns a result object
4
4
  def check
5
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]}--"
6
+ begin
7
+ @testcase.response_expectation['headers'].each_pair do |header_name, header_value|
8
+ if is_regex?(header_value)
9
+ if not (excluded?(header_name) or regex_matches?(header_value, @response.headers[header_name]))
10
+ result.succeeded = false
11
+ result.error_message = " expected header identifier --#{header_name}-- to match regex --#{header_value}--\n got --#{@response.headers[header_name]}--"
12
+ end
13
+ else
14
+ if not (excluded?(header_name) or string_matches?(header_value, @response.headers[header_name]))
15
+ result.succeeded = false
16
+ result.error_message = " expected header identifier --#{header_name}-- to match --#{header_value}--\n got --#{@response.headers[header_name]}--"
17
+ end
11
18
  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
+ end unless (@testcase.response_expectation['headers'].nil? or @testcase.response_expectation['headers'].empty?)
20
+ rescue
21
+ result.succeeded = false
22
+ result.error_message = " unexpected error while parsing testcase/response. Check your testcase format!"
23
+ end
19
24
  result
20
25
  end
21
26
 
data/lib/result.rb CHANGED
@@ -15,6 +15,10 @@ class Result
15
15
  self.send(verbosity.to_sym, index)
16
16
  end
17
17
 
18
+ def success?
19
+ return @succeeded
20
+ end
21
+
18
22
  private
19
23
 
20
24
  # yields out rspec like error messages only in case of an error
data/lib/testcase.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  class Testcase
2
2
 
3
- attr_reader :raw, :name, :request, :response_expectation
3
+ attr_reader :raw, :name, :request, :response_expectation, :wait_before_request
4
4
 
5
5
  def initialize(raw)
6
6
  @raw = raw
7
7
  @name = raw['name']
8
8
  @request = @raw['request']
9
9
  @response_expectation = @raw['response_expectation']
10
+ @wait_before_request = @raw['wait_before_request'].nil? ? 0 : @raw['wait_before_request']
10
11
  end
11
12
 
12
13
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 3
9
- version: 0.3.3
8
+ - 4
9
+ version: 0.3.4
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-10-01 00:00:00 +02:00
17
+ date: 2010-10-07 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -234,6 +234,7 @@ files:
234
234
  - lib/apirunner.rb
235
235
  - lib/apirunner/railtie.rb
236
236
  - lib/checker.rb
237
+ - lib/core_extensions.rb
237
238
  - lib/expectation_matcher.rb
238
239
  - lib/http_client.rb
239
240
  - lib/plugins/response_body_checker.rb
@@ -262,7 +263,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
262
263
  requirements:
263
264
  - - ">="
264
265
  - !ruby/object:Gem::Version
265
- hash: 4136234197952524477
266
+ hash: -3679568694912151594
266
267
  segments:
267
268
  - 0
268
269
  version: "0"