apirunner 0.3.3 → 0.3.4

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