apirunner 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -1
- data/Gemfile.lock +4 -4
- data/README.rdoc +51 -0
- data/VERSION +1 -1
- data/apirunner.gemspec +5 -5
- data/examples/test/api_runner/001_create_ressource.yml +102 -2
- data/lib/api_runner.rb +6 -5
- data/lib/expectation_matcher.rb +5 -1
- data/lib/http_client.rb +61 -1
- data/lib/tasks/api.rake +5 -3
- metadata +8 -8
data/Gemfile
CHANGED
@@ -7,7 +7,8 @@ source "http://rubygems.org"
|
|
7
7
|
# Include everything needed to run rake, tests, features, etc.
|
8
8
|
|
9
9
|
gem 'nokogiri', '~> 1.4.3.1'
|
10
|
-
gem 'httparty', '~> 0.6.1'
|
10
|
+
#gem 'httparty', '~> 0.6.1'
|
11
|
+
gem 'rest-client', '>= 1.6.1'
|
11
12
|
|
12
13
|
group :development do
|
13
14
|
gem "rspec", ">= 2.0.0.beta.19"
|
data/Gemfile.lock
CHANGED
@@ -2,7 +2,6 @@ GEM
|
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
4
|
builder (2.1.2)
|
5
|
-
crack (0.1.8)
|
6
5
|
cucumber (0.8.5)
|
7
6
|
builder (~> 2.1.2)
|
8
7
|
diff-lcs (~> 1.1.2)
|
@@ -13,18 +12,19 @@ GEM
|
|
13
12
|
gherkin (2.1.5)
|
14
13
|
trollop (~> 1.16.2)
|
15
14
|
git (1.2.5)
|
16
|
-
httparty (0.6.1)
|
17
|
-
crack (= 0.1.8)
|
18
15
|
jeweler (1.5.0.pre3)
|
19
16
|
bundler (~> 1.0.0)
|
20
17
|
git (>= 1.2.5)
|
21
18
|
rake
|
22
19
|
json_pure (1.4.6)
|
20
|
+
mime-types (1.16)
|
23
21
|
mocha (0.9.8)
|
24
22
|
rake
|
25
23
|
nokogiri (1.4.3.1)
|
26
24
|
rake (0.8.7)
|
27
25
|
rcov (0.9.9)
|
26
|
+
rest-client (1.6.1)
|
27
|
+
mime-types (>= 1.16)
|
28
28
|
rspec (2.0.0.beta.22)
|
29
29
|
rspec-core (= 2.0.0.beta.22)
|
30
30
|
rspec-expectations (= 2.0.0.beta.22)
|
@@ -44,9 +44,9 @@ PLATFORMS
|
|
44
44
|
DEPENDENCIES
|
45
45
|
bundler (~> 1.0.0)
|
46
46
|
cucumber
|
47
|
-
httparty (~> 0.6.1)
|
48
47
|
jeweler (~> 1.5.0.pre3)
|
49
48
|
mocha (>= 0.9.8)
|
50
49
|
nokogiri (~> 1.4.3.1)
|
51
50
|
rcov
|
51
|
+
rest-client (>= 1.6.1)
|
52
52
|
rspec (>= 2.0.0.beta.19)
|
data/README.rdoc
CHANGED
@@ -26,6 +26,7 @@ apirunner was initially developed for testing of the mighty (m8ty) i18n recommen
|
|
26
26
|
* print out a nice error report (that you as a awesome ruby coder will never see)
|
27
27
|
* be invoked from within rake to generate some example configuration and testcase files
|
28
28
|
* be invoked also from within rake to run your test's
|
29
|
+
* not travel to Ibiza
|
29
30
|
|
30
31
|
== Installation
|
31
32
|
|
@@ -45,6 +46,44 @@ Additionally there will be some example testcases which can be found in:
|
|
45
46
|
test/apirunner/002_delete_ressource.yml
|
46
47
|
test/apirunner/excludes.yml
|
47
48
|
|
49
|
+
At first take some time and change config/api_runner.yml to your needs. You might for example want to test your app locally on localhost:3000, on staging machine and on production environment too. So your api_runner.yml could look like that:
|
50
|
+
|
51
|
+
local:
|
52
|
+
protocol: http
|
53
|
+
host: localhost
|
54
|
+
port: 3000
|
55
|
+
namespace: api
|
56
|
+
staging:
|
57
|
+
protocol: http
|
58
|
+
host: staging.yourstagingdomain.dom
|
59
|
+
port: 80
|
60
|
+
namespace: api
|
61
|
+
production:
|
62
|
+
protocol: https
|
63
|
+
host: www.yourproductiondomain.dom
|
64
|
+
port: 80
|
65
|
+
namespace: prod_api
|
66
|
+
|
67
|
+
Take a look at "namespace" here. It makes the expectation matcher build ressource URI's like so:
|
68
|
+
|
69
|
+
http://localhost:3000/api
|
70
|
+
http://staging.yourstagingdomain.dom/api
|
71
|
+
http://www.yourproductiondomain.dom/prod_api
|
72
|
+
|
73
|
+
The ressource pathes are simply appended before the request is sent.
|
74
|
+
|
75
|
+
The file also generates your rake tasks dynamically. The above config will generate 3 new tasks:
|
76
|
+
|
77
|
+
api:run:local
|
78
|
+
api:run:staging
|
79
|
+
api:run:production
|
80
|
+
|
81
|
+
== Excludes
|
82
|
+
|
83
|
+
You may also want to define some excludes for some of your environment. Imageine on your localhost's server there is no "Last-Modified" present in the header, but you would like to check that on staging and production boxes.
|
84
|
+
|
85
|
+
Simply define your story to check "Last-Modified" generally and exclude it for
|
86
|
+
|
48
87
|
== Invocation
|
49
88
|
|
50
89
|
Assuming you defined an environment "local" and "staging" you can invoke your masterpiece with:
|
@@ -68,6 +107,18 @@ apirunner heavily depends on the following great GEM's:
|
|
68
107
|
1) nokogiri
|
69
108
|
2) httparty
|
70
109
|
|
110
|
+
== Examples
|
111
|
+
|
112
|
+
After invoking:
|
113
|
+
|
114
|
+
rake api:scaffold
|
115
|
+
|
116
|
+
you will find some YAML example files for request and expectation generation in test/api_runner. You can create as many story files here as you like, they are executed in the order they are read from the filesystem, so you should name them like 000_create_some_ressource.yml, 001_read_some_ressource.yml and so on.
|
117
|
+
|
118
|
+
Alternatively you can place all your stories into one single file.
|
119
|
+
|
120
|
+
Addition
|
121
|
+
|
71
122
|
== Authors
|
72
123
|
|
73
124
|
apirunner was written by:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.6
|
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.1.
|
8
|
+
s.version = "0.1.6"
|
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-09-
|
12
|
+
s.date = %q{2010-09-23}
|
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 = [
|
@@ -63,7 +63,7 @@ Gem::Specification.new do |s|
|
|
63
63
|
|
64
64
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
65
|
s.add_runtime_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
66
|
-
s.add_runtime_dependency(%q<
|
66
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 1.6.1"])
|
67
67
|
s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
68
68
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
69
69
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -77,7 +77,7 @@ Gem::Specification.new do |s|
|
|
77
77
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
78
78
|
else
|
79
79
|
s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
80
|
-
s.add_dependency(%q<
|
80
|
+
s.add_dependency(%q<rest-client>, [">= 1.6.1"])
|
81
81
|
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
82
82
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
83
83
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -92,7 +92,7 @@ Gem::Specification.new do |s|
|
|
92
92
|
end
|
93
93
|
else
|
94
94
|
s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
|
95
|
-
s.add_dependency(%q<
|
95
|
+
s.add_dependency(%q<rest-client>, [">= 1.6.1"])
|
96
96
|
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
|
97
97
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
98
98
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -5,13 +5,42 @@
|
|
5
5
|
method: 'PUT'
|
6
6
|
body:
|
7
7
|
username: 'duffyduck'
|
8
|
+
watchlist:
|
9
|
+
- m333
|
10
|
+
- m79
|
11
|
+
blacklist:
|
12
|
+
- m334
|
13
|
+
- m77
|
14
|
+
skiplist:
|
15
|
+
- m335
|
16
|
+
- m78
|
17
|
+
ratings:
|
18
|
+
m336: 4
|
19
|
+
m79: 2.5
|
20
|
+
m777: 3.0
|
21
|
+
m567: 4.0
|
22
|
+
m354: 5.0
|
23
|
+
expires_at: 2011-09-09T22:41:50+00:00
|
8
24
|
response_expectation:
|
9
25
|
status_code: 201
|
10
26
|
headers:
|
11
27
|
Last-Modified: /.*/
|
12
28
|
body:
|
13
29
|
username: 'duffyduck'
|
14
|
-
|
30
|
+
watchlist:
|
31
|
+
- m333
|
32
|
+
- m79
|
33
|
+
blacklist:
|
34
|
+
- m334
|
35
|
+
- m77
|
36
|
+
skiplist:
|
37
|
+
- m335
|
38
|
+
- m78
|
39
|
+
ratings:
|
40
|
+
m336: 4.0
|
41
|
+
m79: 2.5
|
42
|
+
fsk: "18"
|
43
|
+
expires_at: Fri, 09 Sep 2011 22:41:50 +0000
|
15
44
|
- name: 'Update existing User - Update watchlist'
|
16
45
|
request:
|
17
46
|
path: '/users/duffyduck/watchlist'
|
@@ -22,4 +51,75 @@
|
|
22
51
|
response_expectation:
|
23
52
|
status_code: 204
|
24
53
|
body:
|
25
|
-
|
54
|
+
- name: 'Check User FSK,Watchlist'
|
55
|
+
request:
|
56
|
+
path: '/users/duffyduck'
|
57
|
+
method: 'GET'
|
58
|
+
response_expectation:
|
59
|
+
status_code: 200
|
60
|
+
headers:
|
61
|
+
Last-Modified: /.*/
|
62
|
+
body:
|
63
|
+
username: 'duffyduck'
|
64
|
+
fsk: "18"
|
65
|
+
watchlist:
|
66
|
+
- m367
|
67
|
+
- m73
|
68
|
+
blacklist:
|
69
|
+
- m334
|
70
|
+
- m77
|
71
|
+
skiplist:
|
72
|
+
- m335
|
73
|
+
- m78
|
74
|
+
ratings:
|
75
|
+
m336: 4.0
|
76
|
+
m79: 2.5
|
77
|
+
- name: 'Set 10 Ratings'
|
78
|
+
request:
|
79
|
+
path: '/users/duffyduck/ratings'
|
80
|
+
method: 'PUT'
|
81
|
+
body:
|
82
|
+
"m1035": 4
|
83
|
+
"m2087": 3
|
84
|
+
"m1554": 2
|
85
|
+
"m2981": 1
|
86
|
+
"m1590": 2
|
87
|
+
"m1056": 3
|
88
|
+
"m12493": 4
|
89
|
+
"m1875": 5
|
90
|
+
"m7258": 2.5
|
91
|
+
"m7339": 3.5
|
92
|
+
response_expectation:
|
93
|
+
status_code: 204
|
94
|
+
headers:
|
95
|
+
Last-Modified: /.*/
|
96
|
+
- name: 'Check User Ratings Update'
|
97
|
+
request:
|
98
|
+
path: '/users/duffyduck'
|
99
|
+
method: 'GET'
|
100
|
+
response_expectation:
|
101
|
+
status_code: 200
|
102
|
+
headers:
|
103
|
+
Last-Modified: /.*/
|
104
|
+
body:
|
105
|
+
username: 'duffyduck'
|
106
|
+
fsk: "18"
|
107
|
+
watchlist:
|
108
|
+
- m367
|
109
|
+
- m73
|
110
|
+
blacklist:
|
111
|
+
- m334
|
112
|
+
- m77
|
113
|
+
skiplist:
|
114
|
+
- m335
|
115
|
+
- m78
|
116
|
+
ratings:
|
117
|
+
"m1035": "1.0"
|
118
|
+
"m2087": "3.0"
|
119
|
+
"m1554": "2.0"
|
120
|
+
"m1590": "2.0"
|
121
|
+
"m1056": "3.0"
|
122
|
+
"m12493": "4.0"
|
123
|
+
"m1875": "5.0"
|
124
|
+
"m7258": "2.5"
|
125
|
+
"m7339": "3.5"
|
data/lib/api_runner.rb
CHANGED
@@ -10,13 +10,13 @@ class ApiRunner
|
|
10
10
|
|
11
11
|
# initializes the object, loads environment, build base_uri
|
12
12
|
def initialize(env)
|
13
|
-
@http_client = HttpClient.new
|
14
13
|
@spec = []
|
15
14
|
@errors = []
|
16
15
|
@excludes = []
|
17
16
|
load_config(env)
|
18
17
|
load_excludes(env)
|
19
18
|
load_url_spec
|
19
|
+
@http_client = HttpClient.new(@host, @port, @namespace)
|
20
20
|
@expectation = ExpectationMatcher.new(@excludes)
|
21
21
|
end
|
22
22
|
|
@@ -37,7 +37,7 @@ class ApiRunner
|
|
37
37
|
# runs all testcases that are provided by the testclass an fills errors if there are any
|
38
38
|
def run_tests
|
39
39
|
@spec.each do |test_case|
|
40
|
-
response = send_request(test_case['request']['method'].downcase.to_sym,
|
40
|
+
response = send_request(test_case['request']['method'].downcase.to_sym, test_case['request']['path'], test_case['request']['body'])
|
41
41
|
@expectation.test_types.each do |test_type|
|
42
42
|
test = @expectation.check(test_type, response, test_case)
|
43
43
|
if not test.succeeded
|
@@ -57,12 +57,13 @@ class ApiRunner
|
|
57
57
|
end
|
58
58
|
|
59
59
|
# builds target uri from base uri generated of host port and namespace as well as the ressource path
|
60
|
-
def target_uri
|
61
|
-
"#{@protocol}://#{@host}
|
60
|
+
def target_uri
|
61
|
+
"#{@protocol}://#{@host}"
|
62
62
|
end
|
63
63
|
|
64
64
|
# returns true if server is available
|
65
65
|
def server_is_available?
|
66
|
+
return true
|
66
67
|
!@http_client.send_request(:get, "#{@protocol}://#{@host}:#{@port}", {:timeout => 5}).nil?
|
67
68
|
end
|
68
69
|
|
@@ -83,7 +84,7 @@ class ApiRunner
|
|
83
84
|
# loads and parses items that need to be excluded from the checks in certain environments
|
84
85
|
def load_excludes(env)
|
85
86
|
excludes_file = self.class.excludes_file
|
86
|
-
@excludes = YAML.load_file(excludes_file).detect{ |a| a.first == env.to_s }[1]["excludes"]
|
87
|
+
@excludes = YAML.load_file(excludes_file).detect{ |a| a.first == env.to_s }[1]["excludes"] rescue nil
|
87
88
|
end
|
88
89
|
|
89
90
|
# returns config files path and can be stubbed this way
|
data/lib/expectation_matcher.rb
CHANGED
@@ -22,7 +22,7 @@ class ExpectationMatcher
|
|
22
22
|
def response_code(response, testcase)
|
23
23
|
result_struct = Struct.new(:succeeded, :error)
|
24
24
|
results = result_struct.new(:succeeded => true, :error => nil)
|
25
|
-
if not testcase['response_expectation']['status_code'] == response.code
|
25
|
+
if not testcase['response_expectation']['status_code'].to_s == response.code.to_s
|
26
26
|
results.succeeded = false
|
27
27
|
results.error = "testcase '#{testcase['name']}'\n expected response code --#{testcase['response_expectation']['status_code']}--\n got response code --#{response.code}--"
|
28
28
|
end
|
@@ -64,6 +64,10 @@ class ExpectationMatcher
|
|
64
64
|
|
65
65
|
# matches the given attributes and values against the ones from the response body
|
66
66
|
def response_body(response, testcase)
|
67
|
+
puts("Testcase #{testcase['name']}\n")
|
68
|
+
puts("got response --#{response.body}--\n\n")
|
69
|
+
puts("exp response --#{testcase['response_expectation']['body']}--\n")
|
70
|
+
puts("___________________________\n\n\n")
|
67
71
|
result_struct = Struct.new(:succeeded, :error)
|
68
72
|
results = result_struct.new(:succeeded => true, :error => nil)
|
69
73
|
|
data/lib/http_client.rb
CHANGED
@@ -1,10 +1,70 @@
|
|
1
1
|
class HttpClient
|
2
|
+
require 'net/http'
|
3
|
+
require 'JSON'
|
4
|
+
|
5
|
+
def initialize(host, port, namespace)
|
6
|
+
@http = Net::HTTP.new(host, port)
|
7
|
+
@host = host
|
8
|
+
@port = port
|
9
|
+
@namespace = namespace
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_request(method, resource, data=nil)
|
13
|
+
build_response(self.send(method.to_s.downcase, resource, data))
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
# returns struct containing response.code, headers, body and message
|
19
|
+
# this is only for easily interfaceing another http client
|
20
|
+
def build_response(raw_response)
|
21
|
+
response_struct = Struct.new(:code, :message, :headers, :body)
|
22
|
+
response = response_struct.new
|
23
|
+
response.code = raw_response.code
|
24
|
+
response.message = raw_response.message
|
25
|
+
response.body = raw_response.body
|
26
|
+
response.headers = raw_response.header
|
27
|
+
response
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(resource, params)
|
31
|
+
request = Net::HTTP::Get.new(resource_path(resource), initheader = {'Content-Type' =>'application/json'})
|
32
|
+
response = @http.request(request)
|
33
|
+
return response
|
34
|
+
end
|
35
|
+
|
36
|
+
def put(resource, data)
|
37
|
+
request = Net::HTTP::Put.new(resource_path(resource), initheader = {'Content-Type' =>'application/json'})
|
38
|
+
request.body = data.to_json
|
39
|
+
response = @http.request(request)
|
40
|
+
end
|
41
|
+
|
42
|
+
def post(resource, data)
|
43
|
+
request = Net::HTTP::Post.new(resource_path(resource), initheader = {'Content-Type' =>'application/json'})
|
44
|
+
request.body = data.to_json
|
45
|
+
response = @http.request(request)
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def delete(resource, params)
|
50
|
+
request = Net::HTTP::Delete.new(resource_path(resource), initheader = {'Content-Type' =>'application/json'})
|
51
|
+
response = @http.request(request)
|
52
|
+
end
|
53
|
+
|
54
|
+
def resource_path(resource)
|
55
|
+
"/" + @namespace + resource
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
class HttPartyClient
|
2
61
|
require 'httparty'
|
3
62
|
include HTTParty
|
4
63
|
|
5
64
|
# sends http request with given method, uri and data and returns servers response
|
6
65
|
def send_request(method, uri, data=nil)
|
7
|
-
|
66
|
+
options = { :body => data.to_json, :format => :json }
|
67
|
+
build_response(self.class.send(method, uri, options))
|
8
68
|
end
|
9
69
|
|
10
70
|
protected
|
data/lib/tasks/api.rake
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
begin
|
2
|
+
config = YAML.load_file("#{Rails.root}/config/api_runner.yml")
|
3
|
+
rescue
|
4
|
+
end
|
3
5
|
namespace :api do
|
4
6
|
namespace :run do
|
5
7
|
config.each_key do |env|
|
@@ -10,7 +12,7 @@ namespace :api do
|
|
10
12
|
api_runner.run
|
11
13
|
puts "\nTestrun finished\n\n"
|
12
14
|
end
|
13
|
-
end
|
15
|
+
end unless config.nil?
|
14
16
|
end
|
15
17
|
desc "generates configuration and a skeleton for apirunner tests as well as excludes"
|
16
18
|
task :scaffold do
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 6
|
9
|
+
version: 0.1.6
|
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-09-
|
17
|
+
date: 2010-09-23 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -34,17 +34,17 @@ dependencies:
|
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: *id001
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
37
|
+
name: rest-client
|
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
|
-
-
|
44
|
+
- 1
|
45
45
|
- 6
|
46
46
|
- 1
|
47
|
-
version:
|
47
|
+
version: 1.6.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: *id002
|
@@ -263,7 +263,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
263
|
requirements:
|
264
264
|
- - ">="
|
265
265
|
- !ruby/object:Gem::Version
|
266
|
-
hash: -
|
266
|
+
hash: -4500206872193641389
|
267
267
|
segments:
|
268
268
|
- 0
|
269
269
|
version: "0"
|