apirunner 0.4.10 → 0.5.0
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/Gemfile +2 -1
- data/Gemfile.lock +3 -1
- data/VERSION +1 -1
- data/apirunner.gemspec +8 -5
- data/lib/checker.rb +33 -0
- data/lib/curl_command_generator.rb +1 -1
- data/lib/http_client.rb +13 -1
- data/lib/plugins/plug03_response_header_checker.rb +5 -0
- data/spec/checker_spec.rb +61 -0
- metadata +47 -30
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
aaronh-chronic (0.3.9)
|
4
5
|
builder (2.1.2)
|
5
6
|
cucumber (0.8.5)
|
6
7
|
builder (~> 2.1.2)
|
@@ -40,10 +41,11 @@ PLATFORMS
|
|
40
41
|
ruby
|
41
42
|
|
42
43
|
DEPENDENCIES
|
44
|
+
aaronh-chronic (~> 0.3.9)
|
43
45
|
bundler (~> 1.0.0)
|
44
46
|
cucumber
|
45
47
|
jeweler (~> 1.5.0.pre3)
|
46
|
-
json
|
48
|
+
json (~> 1.4.6)
|
47
49
|
mocha (>= 0.9.8)
|
48
50
|
nokogiri (~> 1.4.3.1)
|
49
51
|
rcov
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
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.
|
8
|
+
s.version = "0.5.0"
|
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-
|
12
|
+
s.date = %q{2010-11-02}
|
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 = [
|
@@ -99,7 +99,8 @@ Gem::Specification.new do |s|
|
|
99
99
|
|
100
100
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
101
101
|
s.add_runtime_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
102
|
-
s.add_runtime_dependency(%q<json>, ["
|
102
|
+
s.add_runtime_dependency(%q<json>, ["~> 1.4.6"])
|
103
|
+
s.add_runtime_dependency(%q<aaronh-chronic>, ["~> 0.3.9"])
|
103
104
|
s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
104
105
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
105
106
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -113,7 +114,8 @@ Gem::Specification.new do |s|
|
|
113
114
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
114
115
|
else
|
115
116
|
s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
116
|
-
s.add_dependency(%q<json>, ["
|
117
|
+
s.add_dependency(%q<json>, ["~> 1.4.6"])
|
118
|
+
s.add_dependency(%q<aaronh-chronic>, ["~> 0.3.9"])
|
117
119
|
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
118
120
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
119
121
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -128,7 +130,8 @@ Gem::Specification.new do |s|
|
|
128
130
|
end
|
129
131
|
else
|
130
132
|
s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
131
|
-
s.add_dependency(%q<json>, ["
|
133
|
+
s.add_dependency(%q<json>, ["~> 1.4.6"])
|
134
|
+
s.add_dependency(%q<aaronh-chronic>, ["~> 0.3.9"])
|
132
135
|
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
133
136
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
134
137
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
data/lib/checker.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
|
1
3
|
class Checker
|
2
4
|
@@children = []
|
3
5
|
|
@@ -19,6 +21,17 @@ class Checker
|
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
24
|
+
def get_time(time)
|
25
|
+
one_day = 24 * 3600 # seconds
|
26
|
+
if time.match(/^@next_occurence_of/)
|
27
|
+
time = Chronic.parse( "next #{time.gsub(/^@next_occurence_of/, '')}" ) || Chronic.parse(time.gsub(/^@next_occurence_of/, ''))
|
28
|
+
time -= one_day if time - Time.now > one_day
|
29
|
+
else
|
30
|
+
time = Chronic.parse( time.gsub(/^@/,'') )
|
31
|
+
end
|
32
|
+
|
33
|
+
Chronic.parse(time)
|
34
|
+
end
|
22
35
|
|
23
36
|
# tracks all children of this class
|
24
37
|
# this way plugins can be loaded automagically
|
@@ -31,6 +44,26 @@ class Checker
|
|
31
44
|
@excludes.include?(item)
|
32
45
|
end
|
33
46
|
|
47
|
+
def is_time_check?(header, expectation)
|
48
|
+
return false unless header and expectation
|
49
|
+
# only support time check for certain headers + custom X-* headers
|
50
|
+
return false unless ["cache-control[max-age]", "cache-control[s-maxage]", "cache-control[min-fresh]", "retry-after", "last-modified"].include?(header.downcase) || header.downcase.match(/x-/)
|
51
|
+
return false unless expectation.strip.match(/^@/)
|
52
|
+
return true
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def compare_time(header, expectation, value)
|
57
|
+
return false unless is_time_check?(header, expectation)
|
58
|
+
if ["cache-control[max-age]", "cache-control[s-maxage]", "cache-control[min-fresh]", "retry-after"].include?(header.downcase) || header.downcase.match(/x-/)
|
59
|
+
diff = get_time(expectation) - Time.now - value.to_i
|
60
|
+
elsif header.to_s.downcase == "last-modified"
|
61
|
+
diff = get_time(expectation) - Chronic.parse(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
diff >= -5 && diff <= 5
|
65
|
+
end
|
66
|
+
|
34
67
|
def is_number_comparison?(string)
|
35
68
|
return false unless string
|
36
69
|
string.match(/^[><]\s*\d+\s*$/) || string.match(/^[<>=]=\s*\d+\s*$/)
|
@@ -11,7 +11,7 @@ module CurlCommandGenerator
|
|
11
11
|
|
12
12
|
def body2arg(body)
|
13
13
|
# TODO: format body depending on the content type that is set, not always as json
|
14
|
-
body ? "-d'#{body.to_json}'"
|
14
|
+
body.nil? || body == {} || body == "" ? "" : "-d'#{body.to_json}'"
|
15
15
|
end
|
16
16
|
|
17
17
|
def headers2args(hash)
|
data/lib/http_client.rb
CHANGED
@@ -30,7 +30,19 @@ class HttpClient
|
|
30
30
|
response.code = raw_response.code
|
31
31
|
response.message = raw_response.message
|
32
32
|
response.body = raw_response.body
|
33
|
-
response.headers = raw_response.to_hash.keys.inject({}){|hash, key|
|
33
|
+
response.headers = raw_response.to_hash.keys.inject({}){ |hash, key|
|
34
|
+
value = raw_response.to_hash[key][0]
|
35
|
+
hash[key.to_s.downcase] = value
|
36
|
+
if value =~ /=/
|
37
|
+
sub_values = value.tr(","," ").split(" ").select{|x| x =~ /=/}
|
38
|
+
sub_values.each do |sub_value|
|
39
|
+
s_key, s_value = sub_value.split("=")
|
40
|
+
hash["#{key}[#{s_key}]"] = s_value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
hash
|
45
|
+
}
|
34
46
|
response.runtime = runtime
|
35
47
|
response.fully_qualified_path = (method == "GET" ? build_uri(resource, params).request_uri : resource_path(resource))
|
36
48
|
response
|
@@ -11,6 +11,11 @@ class ResponseHeaderChecker < Checker
|
|
11
11
|
result.succeeded = false
|
12
12
|
result.error_message = " expected header identifier --#{header_name}-- to match regex --#{header_value}--\n got --#{@response.headers[header_name]}--"
|
13
13
|
end
|
14
|
+
elsif is_time_check?(header_name, header_value)
|
15
|
+
if not (excluded?(header_name) or compare_time(header_name, header_value, @response.headers[header_name]))
|
16
|
+
result.succeeded = false
|
17
|
+
result.error_message = " expected header identifier --#{header_name}-- to match time (+/- 5 seconds) --#{header_value} / #{Chronic.parse(header_value.tr('@',''))}--\n got --#{@response.headers[header_name]}--"
|
18
|
+
end
|
14
19
|
elsif is_number_comparison?(header_value)
|
15
20
|
if not (excluded?(header_name) or compare_number(header_value, @response.headers[header_name]))
|
16
21
|
result.succeeded = false
|
data/spec/checker_spec.rb
CHANGED
@@ -71,6 +71,67 @@ describe "Checker" do
|
|
71
71
|
|
72
72
|
end
|
73
73
|
|
74
|
+
describe "time_check" do
|
75
|
+
it "should perform a time check for max-age, s-maxage, min-fresh, retry-after, last-modified and X-* headers when expectation starts with @" do
|
76
|
+
["cache-control[max-age]", "cache-control[s-maxage]", "cache-control[min-fresh]", "retry-after", "Last-Modified", "X-My-Custom-Timestamp"].each do |header|
|
77
|
+
Checker.new({}, {}).send(:is_time_check?, header, "@tomorrow 4:00am").should be_true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not perform a time check for a header different to max-age, s-maxage, min-fresh, retry-after or X-* headers" do
|
82
|
+
%w{Server Content-Type Content-Length Via Connection}.each do |header|
|
83
|
+
Checker.new({}, {}).send(:is_time_check?, header, "@tomorrow 4:00am").should be_false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should not perform a time check for if header or expectations or both are nil" do
|
88
|
+
Checker.new({}, {}).send(:is_time_check?, nil, "@tomorrow 4:00am").should be_false
|
89
|
+
Checker.new({}, {}).send(:is_time_check?, "cache-control[max-age]", nil).should be_false
|
90
|
+
Checker.new({}, {}).send(:is_time_check?, nil, nil).should be_false
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should correctly interpret @next_occurence_of " do
|
94
|
+
one_hour = 3600 # seconds
|
95
|
+
current_time = Chronic.parse("today #{Time.now.hour}:00")
|
96
|
+
three_hours_ago = current_time - 3*one_hour
|
97
|
+
in_three_hours = current_time + 3*one_hour
|
98
|
+
|
99
|
+
Checker.new({}, {}).send(:get_time, "@next_occurence_of #{three_hours_ago.hour}:00").should == three_hours_ago + 24 * one_hour
|
100
|
+
Checker.new({}, {}).send(:get_time, "@next_occurence_of #{in_three_hours.hour}:00").should == in_three_hours
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should correctly compare delta-seconds for headers max-age, s-maxage, min-fresh, retry-after" do
|
104
|
+
in_five_hours = Chronic.parse("in 5 hours")
|
105
|
+
["cache-control[max-age]", "cache-control[s-maxage]", "cache-control[min-fresh]", "retry-after", "X-My-Custom-Timestamp"].each do |header|
|
106
|
+
Checker.new({}, {}).send(:compare_time, header, "@next_occurence_of #{in_five_hours}", 5 * 3600).should be_true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should correctly transform an absolute time check to date for headers Last-Modified" do
|
111
|
+
in_five_hours = Chronic.parse("in 5 hours")
|
112
|
+
Checker.new({}, {}).send(:compare_time, "Last-Modified", "@#{in_five_hours}", in_five_hours.to_s).should be_true
|
113
|
+
end
|
114
|
+
|
115
|
+
def in_five_hours
|
116
|
+
Chronic.parse("in 5 hours")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should compare with +/- 5 seconds tolarance" do
|
120
|
+
#in_five_hours = Chronic.parse("in 5 hours")
|
121
|
+
(-4..4).each do |diff|
|
122
|
+
Checker.new({}, {}).send(:compare_time, "Last-Modified", "@#{in_five_hours}", Chronic.parse( (in_five_hours + diff) ).to_s).should be_true
|
123
|
+
["cache-control[max-age]", "cache-control[s-maxage]", "cache-control[min-fresh]", "retry-after", "X-My-Custom-Timestamp"].each do |header|
|
124
|
+
Checker.new({}, {}).send(:compare_time, header, "@next_occurence_of #{in_five_hours}", 5 * 3600 + diff).should be_true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
[-6,6].each do |diff|
|
129
|
+
Checker.new({}, {}).send(:compare_time, "Last-Modified", "@#{in_five_hours}", Chronic.parse( (in_five_hours + diff) ).to_s).should be_false
|
130
|
+
Checker.new({}, {}).send(:compare_time, "Last-Modified", "@next_occurence_of #{in_five_hours}", 5 * 3600 + diff).should be_false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
74
135
|
describe "compare_number" do
|
75
136
|
it "should compare lt and lte correctly" do
|
76
137
|
Checker.new({}, {}).send(:compare_number, "<4", "5").should be_false
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 5
|
8
|
+
- 0
|
9
|
+
version: 0.5.0
|
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-
|
17
|
+
date: 2010-11-02 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -38,17 +38,34 @@ dependencies:
|
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
segments:
|
44
|
-
-
|
45
|
-
|
44
|
+
- 1
|
45
|
+
- 4
|
46
|
+
- 6
|
47
|
+
version: 1.4.6
|
46
48
|
type: :runtime
|
47
49
|
prerelease: false
|
48
50
|
version_requirements: *id002
|
49
51
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
52
|
+
name: aaronh-chronic
|
51
53
|
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
- 3
|
61
|
+
- 9
|
62
|
+
version: 0.3.9
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: rspec
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
69
|
none: false
|
53
70
|
requirements:
|
54
71
|
- - ">="
|
@@ -62,10 +79,10 @@ dependencies:
|
|
62
79
|
version: 2.0.0.beta.19
|
63
80
|
type: :development
|
64
81
|
prerelease: false
|
65
|
-
version_requirements: *
|
82
|
+
version_requirements: *id004
|
66
83
|
- !ruby/object:Gem::Dependency
|
67
84
|
name: cucumber
|
68
|
-
requirement: &
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
69
86
|
none: false
|
70
87
|
requirements:
|
71
88
|
- - ">="
|
@@ -75,10 +92,10 @@ dependencies:
|
|
75
92
|
version: "0"
|
76
93
|
type: :development
|
77
94
|
prerelease: false
|
78
|
-
version_requirements: *
|
95
|
+
version_requirements: *id005
|
79
96
|
- !ruby/object:Gem::Dependency
|
80
97
|
name: bundler
|
81
|
-
requirement: &
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
82
99
|
none: false
|
83
100
|
requirements:
|
84
101
|
- - ~>
|
@@ -90,10 +107,10 @@ dependencies:
|
|
90
107
|
version: 1.0.0
|
91
108
|
type: :development
|
92
109
|
prerelease: false
|
93
|
-
version_requirements: *
|
110
|
+
version_requirements: *id006
|
94
111
|
- !ruby/object:Gem::Dependency
|
95
112
|
name: jeweler
|
96
|
-
requirement: &
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
97
114
|
none: false
|
98
115
|
requirements:
|
99
116
|
- - ~>
|
@@ -106,10 +123,10 @@ dependencies:
|
|
106
123
|
version: 1.5.0.pre3
|
107
124
|
type: :development
|
108
125
|
prerelease: false
|
109
|
-
version_requirements: *
|
126
|
+
version_requirements: *id007
|
110
127
|
- !ruby/object:Gem::Dependency
|
111
128
|
name: rcov
|
112
|
-
requirement: &
|
129
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
113
130
|
none: false
|
114
131
|
requirements:
|
115
132
|
- - ">="
|
@@ -119,10 +136,10 @@ dependencies:
|
|
119
136
|
version: "0"
|
120
137
|
type: :development
|
121
138
|
prerelease: false
|
122
|
-
version_requirements: *
|
139
|
+
version_requirements: *id008
|
123
140
|
- !ruby/object:Gem::Dependency
|
124
141
|
name: mocha
|
125
|
-
requirement: &
|
142
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
126
143
|
none: false
|
127
144
|
requirements:
|
128
145
|
- - ">="
|
@@ -134,10 +151,10 @@ dependencies:
|
|
134
151
|
version: 0.9.8
|
135
152
|
type: :development
|
136
153
|
prerelease: false
|
137
|
-
version_requirements: *
|
154
|
+
version_requirements: *id009
|
138
155
|
- !ruby/object:Gem::Dependency
|
139
156
|
name: rspec
|
140
|
-
requirement: &
|
157
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
141
158
|
none: false
|
142
159
|
requirements:
|
143
160
|
- - ">="
|
@@ -151,10 +168,10 @@ dependencies:
|
|
151
168
|
version: 2.0.0.beta.19
|
152
169
|
type: :development
|
153
170
|
prerelease: false
|
154
|
-
version_requirements: *
|
171
|
+
version_requirements: *id010
|
155
172
|
- !ruby/object:Gem::Dependency
|
156
173
|
name: cucumber
|
157
|
-
requirement: &
|
174
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
158
175
|
none: false
|
159
176
|
requirements:
|
160
177
|
- - ">="
|
@@ -164,10 +181,10 @@ dependencies:
|
|
164
181
|
version: "0"
|
165
182
|
type: :development
|
166
183
|
prerelease: false
|
167
|
-
version_requirements: *
|
184
|
+
version_requirements: *id011
|
168
185
|
- !ruby/object:Gem::Dependency
|
169
186
|
name: bundler
|
170
|
-
requirement: &
|
187
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
171
188
|
none: false
|
172
189
|
requirements:
|
173
190
|
- - ~>
|
@@ -179,10 +196,10 @@ dependencies:
|
|
179
196
|
version: 1.0.0
|
180
197
|
type: :development
|
181
198
|
prerelease: false
|
182
|
-
version_requirements: *
|
199
|
+
version_requirements: *id012
|
183
200
|
- !ruby/object:Gem::Dependency
|
184
201
|
name: jeweler
|
185
|
-
requirement: &
|
202
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
186
203
|
none: false
|
187
204
|
requirements:
|
188
205
|
- - ~>
|
@@ -195,10 +212,10 @@ dependencies:
|
|
195
212
|
version: 1.5.0.pre3
|
196
213
|
type: :development
|
197
214
|
prerelease: false
|
198
|
-
version_requirements: *
|
215
|
+
version_requirements: *id013
|
199
216
|
- !ruby/object:Gem::Dependency
|
200
217
|
name: rcov
|
201
|
-
requirement: &
|
218
|
+
requirement: &id014 !ruby/object:Gem::Requirement
|
202
219
|
none: false
|
203
220
|
requirements:
|
204
221
|
- - ">="
|
@@ -208,7 +225,7 @@ dependencies:
|
|
208
225
|
version: "0"
|
209
226
|
type: :development
|
210
227
|
prerelease: false
|
211
|
-
version_requirements: *
|
228
|
+
version_requirements: *id014
|
212
229
|
description: apirunner is a testsuite to query your RESTful JSON API and match response with your defined expectations
|
213
230
|
email: developers@moviepilot.com
|
214
231
|
executables: []
|
@@ -292,7 +309,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
292
309
|
requirements:
|
293
310
|
- - ">="
|
294
311
|
- !ruby/object:Gem::Version
|
295
|
-
hash: -
|
312
|
+
hash: -4037813036394983070
|
296
313
|
segments:
|
297
314
|
- 0
|
298
315
|
version: "0"
|