hugs 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/README.md +9 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/hugs.gemspec +10 -6
- data/lib/hugs.rb +1 -133
- data/lib/hugs/client.rb +152 -0
- data/lib/hugs/errors.rb +80 -0
- data/test/lib/hugs/client_test.rb +241 -0
- data/test/lib/hugs/errors_test.rb +74 -0
- data/test/{support.rb → test_helper.rb} +1 -0
- metadata +11 -7
- data/test/test_hugs.rb +0 -228
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -4,11 +4,11 @@ GEM
|
|
4
4
|
addressable (2.2.2)
|
5
5
|
crack (0.1.8)
|
6
6
|
git (1.2.5)
|
7
|
-
jeweler (1.5.
|
7
|
+
jeweler (1.5.2)
|
8
8
|
bundler (~> 1.0.0)
|
9
9
|
git (>= 1.2.5)
|
10
10
|
rake
|
11
|
-
minitest (2.0.
|
11
|
+
minitest (2.0.2)
|
12
12
|
multipart-post (1.0.1)
|
13
13
|
net-http-persistent (1.4.1)
|
14
14
|
nokogiri (1.4.4)
|
data/README.md
CHANGED
@@ -10,7 +10,8 @@ Opted to write this gem for four reasons:
|
|
10
10
|
* Wanted a [fast](http://blog.segment7.net/articles/2010/05/07/net-http-is-not-slow),
|
11
11
|
thread-safe, and persistent client.
|
12
12
|
* [Excon](https://github.com/geemus/excon) does most everything right, but is not
|
13
|
-
compatible with [VCR](https://github.com/myronmarston/vcr)
|
13
|
+
compatible with [VCR](https://github.com/myronmarston/vcr) (more specifically
|
14
|
+
[webmock](https://github.com/bblimke/webmock) and [fakeweb](https://github.com/chrisk/fakeweb)).
|
14
15
|
* Wanted to learn how to handle this pattern.
|
15
16
|
|
16
17
|
The XML usage of this gem will probably change. In the next couple of weeks Hugs
|
@@ -19,7 +20,7 @@ will be implemented against an XML OCCI API.
|
|
19
20
|
## Assumptions
|
20
21
|
|
21
22
|
* The webservice returns JSON or XML.
|
22
|
-
*
|
23
|
+
* To objectify the returned JSON or hand back a Nokogiri::XML::Document.
|
23
24
|
|
24
25
|
## Usage
|
25
26
|
|
@@ -33,4 +34,10 @@ See the 'Examples' section in the [wiki](http://github.com/retr0h/hugs/wiki/).
|
|
33
34
|
|
34
35
|
## Testing
|
35
36
|
|
37
|
+
Tests can run offline thanks to [webmock](https://github.com/bblimke/webmock).
|
38
|
+
|
36
39
|
$ bundle exec rake
|
40
|
+
|
41
|
+
or
|
42
|
+
|
43
|
+
$ rake
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.2.0
|
data/hugs.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{hugs}
|
8
|
-
s.version = "2.
|
8
|
+
s.version = "2.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["retr0h"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-04}
|
13
13
|
s.email = %q{john@dewey.ws}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
@@ -25,16 +25,20 @@ Gem::Specification.new do |s|
|
|
25
25
|
"VERSION",
|
26
26
|
"hugs.gemspec",
|
27
27
|
"lib/hugs.rb",
|
28
|
-
"
|
29
|
-
"
|
28
|
+
"lib/hugs/client.rb",
|
29
|
+
"lib/hugs/errors.rb",
|
30
|
+
"test/lib/hugs/client_test.rb",
|
31
|
+
"test/lib/hugs/errors_test.rb",
|
32
|
+
"test/test_helper.rb"
|
30
33
|
]
|
31
34
|
s.homepage = %q{http://github.com/retr0h/hugs}
|
32
35
|
s.require_paths = ["lib"]
|
33
36
|
s.rubygems_version = %q{1.3.7}
|
34
37
|
s.summary = %q{Hugs net-http-persistent with convenient get, delete, post, and put methods.}
|
35
38
|
s.test_files = [
|
36
|
-
"test/
|
37
|
-
"test/
|
39
|
+
"test/lib/hugs/client_test.rb",
|
40
|
+
"test/lib/hugs/errors_test.rb",
|
41
|
+
"test/test_helper.rb"
|
38
42
|
]
|
39
43
|
|
40
44
|
if s.respond_to? :specification_version then
|
data/lib/hugs.rb
CHANGED
@@ -1,133 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Hugs
|
4
|
-
Headers = {
|
5
|
-
:json => "application/json",
|
6
|
-
:xml => "application/xml",
|
7
|
-
}.freeze
|
8
|
-
|
9
|
-
Classes = [
|
10
|
-
Net::HTTP::Get,
|
11
|
-
Net::HTTP::Delete,
|
12
|
-
Net::HTTP::Post,
|
13
|
-
Net::HTTP::Put,
|
14
|
-
].freeze
|
15
|
-
|
16
|
-
##
|
17
|
-
# Required options:
|
18
|
-
# +host+: A String with the host to connect.
|
19
|
-
# Optional:
|
20
|
-
# +user+: A String containing the username for use in HTTP Basic auth.
|
21
|
-
# +password+: A String containing the password for use in HTTP Basic auth.
|
22
|
-
# +port+: An Integer containing the port to connect.
|
23
|
-
# +scheme+: A String containing the HTTP scheme.
|
24
|
-
|
25
|
-
def initialize options
|
26
|
-
@user = options[:user]
|
27
|
-
@password = options[:password]
|
28
|
-
@host = options[:host]
|
29
|
-
@port = options[:port] || 80
|
30
|
-
@scheme = options[:scheme] || "https"
|
31
|
-
@type = options[:type] || :json
|
32
|
-
end
|
33
|
-
|
34
|
-
##
|
35
|
-
# Perform an HTTP get, delete, post, or put.
|
36
|
-
# +path+: A String with the path to the HTTP resource.
|
37
|
-
# +params+: A Hash with the following keys:
|
38
|
-
# - +:query+: Query String in the format "foo=bar"
|
39
|
-
# - +:body+: A sub Hash to be JSON encoded, and posted in
|
40
|
-
# the message body.
|
41
|
-
|
42
|
-
Classes.each do |clazz|
|
43
|
-
verb = clazz.to_s.split("::")[-1].tr 'A-Z', 'a-z'
|
44
|
-
|
45
|
-
define_method verb do |*args|
|
46
|
-
path = args[0]
|
47
|
-
params = args[1] || {}
|
48
|
-
|
49
|
-
response = response_for(clazz, path, params)
|
50
|
-
response.body = parse response.body
|
51
|
-
response
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
##
|
56
|
-
# :method: get
|
57
|
-
|
58
|
-
##
|
59
|
-
# :method: delete
|
60
|
-
|
61
|
-
##
|
62
|
-
# :method: post
|
63
|
-
|
64
|
-
##
|
65
|
-
# :method: put
|
66
|
-
|
67
|
-
private
|
68
|
-
##
|
69
|
-
# Worker method to be called by #get, #delete, #post, #put.
|
70
|
-
# Method arguments have been documented in the callers.
|
71
|
-
|
72
|
-
def response_for request, path, params
|
73
|
-
query = params[:query] && params.delete(:query)
|
74
|
-
body = params[:body] && params.delete(:body)
|
75
|
-
upload = params[:upload] && params.delete(:upload)
|
76
|
-
|
77
|
-
@http ||= Net::HTTP::Persistent.new
|
78
|
-
@url ||= URI.parse "#{@scheme}://#{@host}:#{@port}"
|
79
|
-
|
80
|
-
if upload && request.class === Net::HTTP::Post
|
81
|
-
parts = upload[:parts] || {}
|
82
|
-
parts[:file] = UploadIO.new(parts[:file], upload[:content_type]) if parts[:file]
|
83
|
-
|
84
|
-
request = Net::HTTP::Post::Multipart.new path_with_query(path, query), parts
|
85
|
-
else
|
86
|
-
request = request.new path_with_query path, query
|
87
|
-
request.body = encode(body) if body
|
88
|
-
|
89
|
-
common_headers request
|
90
|
-
end
|
91
|
-
|
92
|
-
request.basic_auth(@user, @password) if requires_authentication?
|
93
|
-
@http.request(@url, request)
|
94
|
-
end
|
95
|
-
|
96
|
-
def path_with_query path, query
|
97
|
-
[path, query].compact.join "?"
|
98
|
-
end
|
99
|
-
|
100
|
-
def common_headers request
|
101
|
-
case request
|
102
|
-
when Net::HTTP::Get, Net::HTTP::Delete
|
103
|
-
request.add_field "Accept", Headers[@type]
|
104
|
-
when Net::HTTP::Post, Net::HTTP::Put
|
105
|
-
request.add_field "Accept", Headers[@type]
|
106
|
-
request.add_field "Content-Type", Headers[@type]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def parse data
|
111
|
-
if is_json?
|
112
|
-
Yajl::Parser.parse data
|
113
|
-
elsif is_xml?
|
114
|
-
Nokogiri::XML.parse data
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def encode body
|
119
|
-
is_json? ? (Yajl::Encoder.encode body) : body
|
120
|
-
end
|
121
|
-
|
122
|
-
def requires_authentication?
|
123
|
-
@user && @password
|
124
|
-
end
|
125
|
-
|
126
|
-
def is_xml?
|
127
|
-
@type == :xml
|
128
|
-
end
|
129
|
-
|
130
|
-
def is_json?
|
131
|
-
@type == :json
|
132
|
-
end
|
133
|
-
end
|
1
|
+
require "hugs/client"
|
data/lib/hugs/client.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
%w(hugs/errors net/http/persistent net/http/post/multipart yajl nokogiri).each { |r| require r }
|
2
|
+
|
3
|
+
module Hugs
|
4
|
+
class Client
|
5
|
+
attr_accessor :headers
|
6
|
+
attr_accessor :raise_4xx, :raise_5xx
|
7
|
+
attr_writer :raise_4xx, :raise_5xx
|
8
|
+
|
9
|
+
Headers = {
|
10
|
+
:json => "application/json",
|
11
|
+
:xml => "application/xml",
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
Classes = [
|
15
|
+
Net::HTTP::Get,
|
16
|
+
Net::HTTP::Delete,
|
17
|
+
Net::HTTP::Post,
|
18
|
+
Net::HTTP::Put,
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
##
|
22
|
+
# Required options:
|
23
|
+
# +host+: A String with the host to connect.
|
24
|
+
# Optional:
|
25
|
+
# +user+: A String containing the username for use in HTTP Basic auth.
|
26
|
+
# +password+: A String containing the password for use in HTTP Basic auth.
|
27
|
+
# +port+: An Integer containing the port to connect.
|
28
|
+
# +scheme+: A String containing the HTTP scheme.
|
29
|
+
|
30
|
+
def initialize options
|
31
|
+
@user = options[:user]
|
32
|
+
@password = options[:password]
|
33
|
+
@host = options[:host]
|
34
|
+
@port = options[:port] || 80
|
35
|
+
@scheme = options[:scheme] || "http"
|
36
|
+
@type = options[:type] || :json
|
37
|
+
@headers = options[:headers] || {}
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Perform an HTTP get, delete, post, or put.
|
42
|
+
# +path+: A String with the path to the HTTP resource.
|
43
|
+
# +params+: A Hash with the following keys:
|
44
|
+
# - +:query+: Query String in the format "foo=bar"
|
45
|
+
# - +:body+: A sub Hash to be JSON encoded, and posted in
|
46
|
+
# the message body.
|
47
|
+
|
48
|
+
Classes.each do |clazz|
|
49
|
+
verb = clazz.to_s.split("::").last.downcase
|
50
|
+
|
51
|
+
define_method verb do |*args|
|
52
|
+
path = args[0]
|
53
|
+
params = args[1] || {}
|
54
|
+
|
55
|
+
response_for clazz, path, params
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# :method: get
|
61
|
+
|
62
|
+
##
|
63
|
+
# :method: delete
|
64
|
+
|
65
|
+
##
|
66
|
+
# :method: post
|
67
|
+
|
68
|
+
##
|
69
|
+
# :method: put
|
70
|
+
|
71
|
+
private
|
72
|
+
##
|
73
|
+
# Worker method to be called by #get, #delete, #post, #put.
|
74
|
+
# Method arguments have been documented in the callers.
|
75
|
+
|
76
|
+
def response_for request, path, params
|
77
|
+
query = params[:query] && params.delete(:query)
|
78
|
+
body = params[:body] && params.delete(:body)
|
79
|
+
upload = params[:upload] && params.delete(:upload)
|
80
|
+
|
81
|
+
@http ||= Net::HTTP::Persistent.new
|
82
|
+
@url ||= URI.parse "#{@scheme}://#{@host}:#{@port}"
|
83
|
+
|
84
|
+
full_path = path_with_query path, query
|
85
|
+
|
86
|
+
if upload && request.class === Net::HTTP::Post
|
87
|
+
parts = upload[:parts] || {}
|
88
|
+
parts[:file] = UploadIO.new(parts[:file], upload[:content_type]) if parts[:file]
|
89
|
+
|
90
|
+
request = Net::HTTP::Post::Multipart.new full_path, parts
|
91
|
+
else
|
92
|
+
request = request.new full_path
|
93
|
+
request.body = encode body
|
94
|
+
|
95
|
+
add_headers request
|
96
|
+
end
|
97
|
+
|
98
|
+
request.basic_auth(@user, @password) if requires_authentication?
|
99
|
+
handle_response request
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_response request
|
103
|
+
resp = @http.request @url, request
|
104
|
+
Hugs::Errors::status_error resp, @raise_4xx, @raise_5xx
|
105
|
+
resp.body = parse resp.body
|
106
|
+
|
107
|
+
resp
|
108
|
+
end
|
109
|
+
|
110
|
+
def path_with_query path, query
|
111
|
+
[path, query].compact.join "?"
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_headers request
|
115
|
+
request.add_field "Accept", Headers[@type]
|
116
|
+
if [Net::HTTP::Post, Net::HTTP::Put].include? request.class
|
117
|
+
request.add_field "Content-Type", Headers[@type]
|
118
|
+
end
|
119
|
+
|
120
|
+
@headers.each do |header, value|
|
121
|
+
request.add_field header, value
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse data
|
126
|
+
if is_json?
|
127
|
+
Yajl::Parser.parse data
|
128
|
+
elsif is_xml?
|
129
|
+
Nokogiri::XML.parse data
|
130
|
+
else
|
131
|
+
data
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def encode body
|
136
|
+
return unless body
|
137
|
+
is_json? ? (Yajl::Encoder.encode body) : body
|
138
|
+
end
|
139
|
+
|
140
|
+
def requires_authentication?
|
141
|
+
@user && @password
|
142
|
+
end
|
143
|
+
|
144
|
+
def is_xml?
|
145
|
+
@type == :xml
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_json?
|
149
|
+
@type == :json
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/hugs/errors.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Hugs
|
2
|
+
module Errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
class HTTPStatusError < Error
|
6
|
+
def initialize msg
|
7
|
+
super msg
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Taken from geemus:excon/lib/excon/errors.rb
|
13
|
+
|
14
|
+
class BadRequest < HTTPStatusError; end # 400
|
15
|
+
class Unauthorized < HTTPStatusError; end # 401
|
16
|
+
class PaymentRequired < HTTPStatusError; end # 402
|
17
|
+
class Forbidden < HTTPStatusError; end # 403
|
18
|
+
class NotFound < HTTPStatusError; end # 404
|
19
|
+
class MethodNotAllowed < HTTPStatusError; end # 405
|
20
|
+
class NotAcceptable < HTTPStatusError; end # 406
|
21
|
+
class ProxyAuthenticationRequired < HTTPStatusError; end # 407
|
22
|
+
class RequestTimeout < HTTPStatusError; end # 408
|
23
|
+
class Conflict < HTTPStatusError; end # 409
|
24
|
+
class Gone < HTTPStatusError; end # 410
|
25
|
+
class LengthRequired < HTTPStatusError; end # 411
|
26
|
+
class PreconditionFailed < HTTPStatusError; end # 412
|
27
|
+
class RequestEntityTooLarge < HTTPStatusError; end # 413
|
28
|
+
class RequestURITooLong < HTTPStatusError; end # 414
|
29
|
+
class UnsupportedMediaType < HTTPStatusError; end # 415
|
30
|
+
class RequestedRangeNotSatisfiable < HTTPStatusError; end # 416
|
31
|
+
class ExpectationFailed < HTTPStatusError; end # 417
|
32
|
+
class UnprocessableEntity < HTTPStatusError; end # 422
|
33
|
+
class InternalServerError < HTTPStatusError; end # 500
|
34
|
+
class NotImplemented < HTTPStatusError; end # 501
|
35
|
+
class BadGateway < HTTPStatusError; end # 502
|
36
|
+
class ServiceUnavailable < HTTPStatusError; end # 503
|
37
|
+
class GatewayTimeout < HTTPStatusError; end # 504
|
38
|
+
|
39
|
+
|
40
|
+
def self.status_error response, raise_4xx, raise_5xx
|
41
|
+
@errors ||= {
|
42
|
+
400 => [Hugs::Errors::BadRequest, 'Bad Request'],
|
43
|
+
401 => [Hugs::Errors::Unauthorized, 'Unauthorized'],
|
44
|
+
402 => [Hugs::Errors::PaymentRequired, 'Payment Required'],
|
45
|
+
403 => [Hugs::Errors::Forbidden, 'Forbidden'],
|
46
|
+
404 => [Hugs::Errors::NotFound, 'Not Found'],
|
47
|
+
405 => [Hugs::Errors::MethodNotAllowed, 'Method Not Allowed'],
|
48
|
+
406 => [Hugs::Errors::NotAcceptable, 'Not Acceptable'],
|
49
|
+
407 => [Hugs::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
|
50
|
+
408 => [Hugs::Errors::RequestTimeout, 'Request Timeout'],
|
51
|
+
409 => [Hugs::Errors::Conflict, 'Conflict'],
|
52
|
+
410 => [Hugs::Errors::Gone, 'Gone'],
|
53
|
+
411 => [Hugs::Errors::LengthRequired, 'Length Required'],
|
54
|
+
412 => [Hugs::Errors::PreconditionFailed, 'Precondition Failed'],
|
55
|
+
413 => [Hugs::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
|
56
|
+
414 => [Hugs::Errors::RequestURITooLong, 'Request-URI Too Long'],
|
57
|
+
415 => [Hugs::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
|
58
|
+
416 => [Hugs::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
|
59
|
+
417 => [Hugs::Errors::ExpectationFailed, 'Expectation Failed'],
|
60
|
+
422 => [Hugs::Errors::UnprocessableEntity, 'Unprocessable Entity'],
|
61
|
+
500 => [Hugs::Errors::InternalServerError, 'InternalServerError'],
|
62
|
+
501 => [Hugs::Errors::NotImplemented, 'Not Implemented'],
|
63
|
+
502 => [Hugs::Errors::BadGateway, 'Bad Gateway'],
|
64
|
+
503 => [Hugs::Errors::ServiceUnavailable, 'Service Unavailable'],
|
65
|
+
504 => [Hugs::Errors::GatewayTimeout, 'Gateway Timeout']
|
66
|
+
}
|
67
|
+
|
68
|
+
case response.code
|
69
|
+
when raise_4xx && %r{^4[0-9]{2}$} ; raise_for(response.code)
|
70
|
+
when raise_5xx && %r{^5[0-9]{2}$} ; raise_for(response.code)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def self.raise_for code
|
76
|
+
error, message = @errors[code.to_i]
|
77
|
+
raise error.new message
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
%w(test_helper base64).each { |r| require r }
|
2
|
+
|
3
|
+
describe Hugs::Client do
|
4
|
+
before do
|
5
|
+
@scheme = "https"
|
6
|
+
@host = "example.com"
|
7
|
+
@port = 80
|
8
|
+
@base = "#{@host}:#{@port}"
|
9
|
+
@valid_options = {
|
10
|
+
:host => @host,
|
11
|
+
:port => @port,
|
12
|
+
:scheme => @scheme,
|
13
|
+
}
|
14
|
+
|
15
|
+
WebMock.reset!
|
16
|
+
@instance = Hugs::Client.new @valid_options
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#response_for" do
|
20
|
+
before do
|
21
|
+
@request = Net::HTTP::Get
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "path" do
|
25
|
+
it "is valid" do
|
26
|
+
stub_request :get, "#{@scheme}://#{@base}/"
|
27
|
+
|
28
|
+
@instance.send :response_for, @request, "/", {}
|
29
|
+
|
30
|
+
assert_requested :get, "#{@scheme}://#{@base}/"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is valid when an invalid :query is supplied" do
|
34
|
+
stub_request :get, "#{@scheme}://#{@base}/"
|
35
|
+
|
36
|
+
@instance.send :response_for, @request, "/", :query => nil
|
37
|
+
|
38
|
+
assert_requested :get, "#{@scheme}://#{@base}/"
|
39
|
+
end
|
40
|
+
|
41
|
+
it "also has a query string" do
|
42
|
+
stub_request(:get, "#{@scheme}://#{@base}/").with:query => {"foo" => "bar"}
|
43
|
+
|
44
|
+
@instance.send :response_for, @request, "/", :query => "foo=bar"
|
45
|
+
|
46
|
+
assert_requested :get, "#{@scheme}://#{@base}/", :query => {"foo" => "bar"}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "multi-part" do
|
51
|
+
Content_Type_Matcher = %r{multipart/form-data}
|
52
|
+
|
53
|
+
before do
|
54
|
+
@request = Net::HTTP::Post
|
55
|
+
end
|
56
|
+
|
57
|
+
it "uploads a file" do
|
58
|
+
stub_request :post, "#{@scheme}://#{@base}/"
|
59
|
+
upload = {
|
60
|
+
:upload => {
|
61
|
+
:parts => { :file => "/dev/null" },
|
62
|
+
:content_type => "type/subtype"
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
@instance.send :response_for, @request, "/", upload
|
67
|
+
|
68
|
+
assert_requested :post, "#{@scheme}://#{@base}/", :body => %r{Content-Type: type/subtype}, :headers => {
|
69
|
+
"Content-Type" => Content_Type_Matcher
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "has parts" do
|
74
|
+
stub_request :post, "#{@scheme}://#{@base}/"
|
75
|
+
upload = {
|
76
|
+
:upload => {
|
77
|
+
:parts => { :foo => :bar, :baz => :xyzzy },
|
78
|
+
:content_type => "foo/bar"
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
@instance.send :response_for, @request, "/", upload
|
83
|
+
|
84
|
+
### wtf can't use mx together.
|
85
|
+
content_disposition_matcher = %r{^Content-Disposition: form-data; name="foo".*^bar.*^Content-Disposition: form-data; name="baz".*^xyzzy.*}m
|
86
|
+
assert_requested :post, "#{@scheme}://#{@base}/", :body => content_disposition_matcher, :headers => {
|
87
|
+
"Content-Type" => Content_Type_Matcher
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "body" do
|
93
|
+
describe "parses response" do
|
94
|
+
describe "json" do
|
95
|
+
it "objectifies and returns a hash" do
|
96
|
+
stub_request(:get, "#{@scheme}://#{@base}/").to_return :body => '{"foo":"bar"}'
|
97
|
+
instance = Hugs::Client.new @valid_options.merge(:type => :json)
|
98
|
+
|
99
|
+
response = instance.get "/"
|
100
|
+
|
101
|
+
response.body.must_be_kind_of Hash
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "xml" do
|
106
|
+
it "parses and returns a Nokogiri object" do
|
107
|
+
stub_request(:get, "#{@scheme}://#{@base}/").to_return :body => "<STORAGE></STORAGE>"
|
108
|
+
instance = Hugs::Client.new @valid_options.merge(:type => :xml)
|
109
|
+
|
110
|
+
response = instance.get "/"
|
111
|
+
|
112
|
+
response.body.must_be_kind_of Nokogiri::XML::Document
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "encodes request" do
|
118
|
+
before do
|
119
|
+
stub_request :get, "#{@scheme}://#{@base}/"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "is not set when :body invalid" do
|
123
|
+
@instance.send :response_for, @request, "/", :body => nil
|
124
|
+
|
125
|
+
assert_requested :get, "#{@scheme}://#{@base}/", :body => nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it "is not set when :body is missing" do
|
129
|
+
@instance.send :response_for, @request, "/", {}
|
130
|
+
|
131
|
+
assert_requested :get, "#{@scheme}://#{@base}/", {}
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "json" do
|
135
|
+
it "is valid" do
|
136
|
+
instance = Hugs::Client.new @valid_options.merge(:type => :json)
|
137
|
+
|
138
|
+
instance.send :response_for, @request, "/", :body => {:foo => :bar}
|
139
|
+
|
140
|
+
assert_requested :get, "#{@scheme}://#{@base}/", :body => '{"foo":"bar"}'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "xml" do
|
145
|
+
it "is valid" do
|
146
|
+
instance = Hugs::Client.new @valid_options.merge(:type => :xml)
|
147
|
+
|
148
|
+
instance.send :response_for, @request, "/", :body => "foo bar"
|
149
|
+
|
150
|
+
assert_requested :get, "#{@scheme}://#{@base}/", :body => "foo bar"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
describe "headers" do
|
158
|
+
describe "authentication" do
|
159
|
+
it "uses basic auth when providing user and password" do
|
160
|
+
stub_request :get, "#{@scheme}://user:credentials@#{@base}/"
|
161
|
+
instance = Hugs::Client.new @valid_options.merge(:user => "user", :password => "credentials")
|
162
|
+
|
163
|
+
instance.send :response_for, @request, "/", {}
|
164
|
+
assert_requested :get, "#{@scheme}://user:credentials@#{@base}/"
|
165
|
+
end
|
166
|
+
|
167
|
+
it "doesn't use basic auth without a user" do
|
168
|
+
assert_doesnt_use_basic_auth_without :user
|
169
|
+
end
|
170
|
+
|
171
|
+
it "doesn't use basic auth without a password" do
|
172
|
+
assert_doesnt_use_basic_auth_without :password
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe "JSON" do
|
177
|
+
it "supports json GET" do
|
178
|
+
assert_supports_http_verb :json, :get
|
179
|
+
end
|
180
|
+
|
181
|
+
it "supports json DELETE" do
|
182
|
+
assert_supports_http_verb :json, :delete
|
183
|
+
end
|
184
|
+
|
185
|
+
it "supports json POST" do
|
186
|
+
assert_supports_http_verb :json, :post
|
187
|
+
end
|
188
|
+
|
189
|
+
it "supports json PUT" do
|
190
|
+
assert_supports_http_verb :json, :put
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "XML" do
|
195
|
+
it "supports xml GET" do
|
196
|
+
assert_supports_http_verb :xml, :get
|
197
|
+
end
|
198
|
+
|
199
|
+
it "supports xml DELETE" do
|
200
|
+
assert_supports_http_verb :xml, :delete
|
201
|
+
end
|
202
|
+
|
203
|
+
it "supports xml POST" do
|
204
|
+
assert_supports_http_verb :xml, :post
|
205
|
+
end
|
206
|
+
|
207
|
+
it "supports xml PUT" do
|
208
|
+
assert_supports_http_verb :xml, :put
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def assert_supports_http_verb type, verb
|
214
|
+
mimetype = {:xml => 'application/xml',
|
215
|
+
:json => 'application/json'}[type]
|
216
|
+
|
217
|
+
assert mimetype, "Unsupported Mimetype '#{type}'"
|
218
|
+
|
219
|
+
clazz = eval "Net::HTTP::#{verb.capitalize}"
|
220
|
+
|
221
|
+
stub_request verb, "#{@scheme}://#{@base}/"
|
222
|
+
instance = Hugs::Client.new @valid_options.merge(:type => type)
|
223
|
+
|
224
|
+
instance.send :response_for, clazz, "/", {}
|
225
|
+
|
226
|
+
headers = { "Accept" => ["*/*", mimetype] }
|
227
|
+
headers["Content-Type"] = mimetype if [:put, :put].include? type
|
228
|
+
|
229
|
+
assert_requested verb, "#{@scheme}://#{@base}/", :headers => headers
|
230
|
+
end
|
231
|
+
|
232
|
+
def assert_doesnt_use_basic_auth_without option
|
233
|
+
stub_request :get, "#{@scheme}://#{@base}/"
|
234
|
+
instance = Hugs::Client.new @valid_options.merge(option => "value")
|
235
|
+
|
236
|
+
instance.send :response_for, @request, "/", {}
|
237
|
+
|
238
|
+
assert_requested :get, "#{@scheme}://#{@base}/"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe Hugs::Errors do
|
4
|
+
before do
|
5
|
+
@scheme = "https"
|
6
|
+
@host = "example.com"
|
7
|
+
@port = 80
|
8
|
+
@base = "#{@host}:#{@port}"
|
9
|
+
@valid_options = {
|
10
|
+
:host => @host,
|
11
|
+
:port => @port,
|
12
|
+
:scheme => @scheme,
|
13
|
+
}
|
14
|
+
|
15
|
+
@instance = Hugs::Client.new @valid_options
|
16
|
+
WebMock.reset!
|
17
|
+
end
|
18
|
+
|
19
|
+
Error_4xx = {
|
20
|
+
400 => [Hugs::Errors::BadRequest, 'Bad Request'],
|
21
|
+
401 => [Hugs::Errors::Unauthorized, 'Unauthorized'],
|
22
|
+
402 => [Hugs::Errors::PaymentRequired, 'Payment Required'],
|
23
|
+
403 => [Hugs::Errors::Forbidden, 'Forbidden'],
|
24
|
+
404 => [Hugs::Errors::NotFound, 'Not Found'],
|
25
|
+
405 => [Hugs::Errors::MethodNotAllowed, 'Method Not Allowed'],
|
26
|
+
406 => [Hugs::Errors::NotAcceptable, 'Not Acceptable'],
|
27
|
+
407 => [Hugs::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
|
28
|
+
408 => [Hugs::Errors::RequestTimeout, 'Request Timeout'],
|
29
|
+
409 => [Hugs::Errors::Conflict, 'Conflict'],
|
30
|
+
410 => [Hugs::Errors::Gone, 'Gone'],
|
31
|
+
411 => [Hugs::Errors::LengthRequired, 'Length Required'],
|
32
|
+
412 => [Hugs::Errors::PreconditionFailed, 'Precondition Failed'],
|
33
|
+
413 => [Hugs::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
|
34
|
+
414 => [Hugs::Errors::RequestURITooLong, 'Request-URI Too Long'],
|
35
|
+
415 => [Hugs::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
|
36
|
+
416 => [Hugs::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
|
37
|
+
417 => [Hugs::Errors::ExpectationFailed, 'Expectation Failed'],
|
38
|
+
422 => [Hugs::Errors::UnprocessableEntity, 'Unprocessable Entity'],
|
39
|
+
}
|
40
|
+
|
41
|
+
Error_5xx = {
|
42
|
+
500 => [Hugs::Errors::InternalServerError, 'InternalServerError'],
|
43
|
+
501 => [Hugs::Errors::NotImplemented, 'Not Implemented'],
|
44
|
+
502 => [Hugs::Errors::BadGateway, 'Bad Gateway'],
|
45
|
+
503 => [Hugs::Errors::ServiceUnavailable, 'Service Unavailable'],
|
46
|
+
504 => [Hugs::Errors::GatewayTimeout, 'Gateway Timeout']
|
47
|
+
}
|
48
|
+
|
49
|
+
describe "error codes" do
|
50
|
+
before do
|
51
|
+
@instance.raise_4xx = false
|
52
|
+
@instance.raise_5xx = false
|
53
|
+
end
|
54
|
+
|
55
|
+
(Error_4xx.merge(Error_5xx)).each_pair do |code, errors|
|
56
|
+
error, message = errors
|
57
|
+
|
58
|
+
it "raises" do
|
59
|
+
(code.to_s =~ %r{^4[0-9]{2}$}) ? (@instance.raise_4xx = true) : (@instance.raise_5xx = true)
|
60
|
+
stub_request(:get, "#{@scheme}://#{@base}/").to_return :status => [code, message]
|
61
|
+
|
62
|
+
lambda { @instance.send :response_for, Net::HTTP::Get, "/", {} }.must_raise error
|
63
|
+
end
|
64
|
+
|
65
|
+
it "doesn't raise" do
|
66
|
+
stub_request(:get, "#{@scheme}://#{@base}/").to_return :status => [code, message]
|
67
|
+
|
68
|
+
response = @instance.send :response_for, Net::HTTP::Get, "/", {}
|
69
|
+
|
70
|
+
response.code.must_equal code.to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 2
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 2.
|
9
|
+
version: 2.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- retr0h
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2011-01-04 00:00:00 -08:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -154,8 +154,11 @@ files:
|
|
154
154
|
- VERSION
|
155
155
|
- hugs.gemspec
|
156
156
|
- lib/hugs.rb
|
157
|
-
-
|
158
|
-
-
|
157
|
+
- lib/hugs/client.rb
|
158
|
+
- lib/hugs/errors.rb
|
159
|
+
- test/lib/hugs/client_test.rb
|
160
|
+
- test/lib/hugs/errors_test.rb
|
161
|
+
- test/test_helper.rb
|
159
162
|
has_rdoc: true
|
160
163
|
homepage: http://github.com/retr0h/hugs
|
161
164
|
licenses: []
|
@@ -189,5 +192,6 @@ signing_key:
|
|
189
192
|
specification_version: 3
|
190
193
|
summary: Hugs net-http-persistent with convenient get, delete, post, and put methods.
|
191
194
|
test_files:
|
192
|
-
- test/
|
193
|
-
- test/
|
195
|
+
- test/lib/hugs/client_test.rb
|
196
|
+
- test/lib/hugs/errors_test.rb
|
197
|
+
- test/test_helper.rb
|
data/test/test_hugs.rb
DELETED
@@ -1,228 +0,0 @@
|
|
1
|
-
%w(support base64 hugs).each { |r| require r }
|
2
|
-
|
3
|
-
describe Hugs do
|
4
|
-
before do
|
5
|
-
@scheme = "https"
|
6
|
-
@host = "example.com"
|
7
|
-
@port = 80
|
8
|
-
@base = "#{@host}:#{@port}"
|
9
|
-
@valid_options = {
|
10
|
-
:host => @host,
|
11
|
-
:port => @port,
|
12
|
-
:scheme => @scheme,
|
13
|
-
}
|
14
|
-
|
15
|
-
WebMock.reset!
|
16
|
-
@instance = Hugs.new @valid_options
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "#response_for" do
|
20
|
-
before do
|
21
|
-
@request = Net::HTTP::Get
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "path" do
|
25
|
-
it "is valid" do
|
26
|
-
stub_request :get, "#{@scheme}://#{@base}/"
|
27
|
-
|
28
|
-
@instance.send :response_for, @request, "/", {}
|
29
|
-
|
30
|
-
assert_requested :get, "#{@scheme}://#{@base}/"
|
31
|
-
end
|
32
|
-
|
33
|
-
it "is valid when an invalid :query is supplied" do
|
34
|
-
stub_request :get, "#{@scheme}://#{@base}/"
|
35
|
-
|
36
|
-
@instance.send :response_for, @request, "/", :query => nil
|
37
|
-
|
38
|
-
assert_requested :get, "#{@scheme}://#{@base}/"
|
39
|
-
end
|
40
|
-
|
41
|
-
it "also has a query string" do
|
42
|
-
stub_request(:get, "#{@scheme}://#{@base}/").with:query => {"foo" => "bar"}
|
43
|
-
|
44
|
-
@instance.send :response_for, @request, "/", :query => "foo=bar"
|
45
|
-
|
46
|
-
assert_requested :get, "#{@scheme}://#{@base}/", :query => {"foo" => "bar"}
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "multi-part" do
|
51
|
-
Content_Type_Matcher = %r{multipart/form-data}
|
52
|
-
|
53
|
-
before do
|
54
|
-
@request = Net::HTTP::Post
|
55
|
-
end
|
56
|
-
|
57
|
-
it "uploads a file" do
|
58
|
-
stub_request :post, "#{@scheme}://#{@base}/"
|
59
|
-
|
60
|
-
upload = {
|
61
|
-
:upload => {
|
62
|
-
:parts => { :file => "/dev/null" },
|
63
|
-
:content_type => "type/subtype"
|
64
|
-
}
|
65
|
-
}
|
66
|
-
|
67
|
-
@instance.send :response_for, @request, "/", upload
|
68
|
-
|
69
|
-
assert_requested :post, "#{@scheme}://#{@base}/", :body => %r{Content-Type: type/subtype}, :headers => {
|
70
|
-
"Content-Type" => Content_Type_Matcher
|
71
|
-
}
|
72
|
-
end
|
73
|
-
|
74
|
-
it "has parts" do
|
75
|
-
stub_request :post, "#{@scheme}://#{@base}/"
|
76
|
-
|
77
|
-
upload = {
|
78
|
-
:upload => {
|
79
|
-
:parts => { :foo => :bar, :baz => :xyzzy },
|
80
|
-
:content_type => "foo/bar"
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
@instance.send :response_for, @request, "/", upload
|
85
|
-
|
86
|
-
### wtf can't use mx together.
|
87
|
-
content_disposition_matcher = %r{^Content-Disposition: form-data; name="foo".*^bar.*^Content-Disposition: form-data; name="baz".*^xyzzy.*}m
|
88
|
-
assert_requested :post, "#{@scheme}://#{@base}/", :body => content_disposition_matcher, :headers => {
|
89
|
-
"Content-Type" => Content_Type_Matcher
|
90
|
-
}
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe "body" do
|
95
|
-
before do
|
96
|
-
stub_request :get, "#{@scheme}://#{@base}/"
|
97
|
-
end
|
98
|
-
|
99
|
-
it "is not set when :body invalid" do
|
100
|
-
@instance.send :response_for, @request, "/", :body => nil
|
101
|
-
|
102
|
-
assert_requested :get, "#{@scheme}://#{@base}/", :body => nil
|
103
|
-
end
|
104
|
-
|
105
|
-
it "is not set when :body is missing" do
|
106
|
-
@instance.send :response_for, @request, "/", {}
|
107
|
-
|
108
|
-
assert_requested :get, "#{@scheme}://#{@base}/", {}
|
109
|
-
end
|
110
|
-
|
111
|
-
describe "json" do
|
112
|
-
before do
|
113
|
-
@instance = Hugs.new @valid_options.merge(:type => :json)
|
114
|
-
end
|
115
|
-
|
116
|
-
it "is valid" do
|
117
|
-
@instance.send :response_for, @request, "/", :body => {:foo => :bar}
|
118
|
-
|
119
|
-
assert_requested :get, "#{@scheme}://#{@base}/", :body => '{"foo":"bar"}'
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
describe "xml" do
|
124
|
-
before do
|
125
|
-
@instance = Hugs.new @valid_options.merge(:type => :xml)
|
126
|
-
end
|
127
|
-
|
128
|
-
it "is valid" do
|
129
|
-
@instance.send :response_for, @request, "/", :body => "foo bar"
|
130
|
-
|
131
|
-
assert_requested :get, "#{@scheme}://#{@base}/", :body => "foo bar"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
describe "headers" do
|
137
|
-
it "authenticates" do
|
138
|
-
stub_request :get, "#{@scheme}://user:credentials@#{@base}/"
|
139
|
-
|
140
|
-
@instance = Hugs.new @valid_options.merge(:user => "user", :password => "credentials")
|
141
|
-
|
142
|
-
@instance.send :response_for, @request, "/", {}
|
143
|
-
|
144
|
-
assert_requested :get, "#{@scheme}://user:credentials@#{@base}/"
|
145
|
-
end
|
146
|
-
|
147
|
-
[:user, :password].each do |option|
|
148
|
-
it "doesn't authenticate when '#{option}' missing" do
|
149
|
-
stub_request :get, "#{@scheme}://#{@base}/"
|
150
|
-
|
151
|
-
invalid_options = @valid_options.reject { |k,v| k == option }
|
152
|
-
@instance = Hugs.new invalid_options
|
153
|
-
|
154
|
-
@instance.send :response_for, @request, "/", {}
|
155
|
-
|
156
|
-
assert_requested :get, "#{@scheme}://#{@base}/"
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
[
|
161
|
-
{ :json => "application/json" },
|
162
|
-
{ :xml => "application/xml" },
|
163
|
-
].each do |pair|
|
164
|
-
pair.each_pair do |type, subtype|
|
165
|
-
[:post, :put].each do |verb|
|
166
|
-
clazz = eval "Net::HTTP::#{verb.capitalize}"
|
167
|
-
|
168
|
-
it "has '#{subtype}' Content-Type and Accept for '#{clazz}'" do
|
169
|
-
stub_request verb, "#{@scheme}://#{@base}/"
|
170
|
-
|
171
|
-
@instance = Hugs.new @valid_options.merge(:type => type)
|
172
|
-
|
173
|
-
@instance.send :response_for, clazz, "/", {}
|
174
|
-
|
175
|
-
assert_requested verb, "#{@scheme}://#{@base}/", :headers => {
|
176
|
-
"Accept" => ["*/*", subtype], "Content-Type" => subtype }
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
[:get, :delete].each do |verb|
|
181
|
-
clazz = eval "Net::HTTP::#{verb.capitalize}"
|
182
|
-
|
183
|
-
it "has '#{subtype}' Accept for '#{clazz}'" do
|
184
|
-
stub_request verb, "#{@scheme}://#{@base}/"
|
185
|
-
|
186
|
-
@instance = Hugs.new @valid_options.merge(:type => type)
|
187
|
-
|
188
|
-
@instance.send :response_for, clazz, "/", {}
|
189
|
-
|
190
|
-
assert_requested verb, "#{@scheme}://#{@base}/", :headers => {
|
191
|
-
"Accept" => ["*/*", subtype] }
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
describe "HTTP methods" do
|
200
|
-
describe "json" do
|
201
|
-
before do
|
202
|
-
@instance = Hugs.new @valid_options.merge(:type => :json)
|
203
|
-
end
|
204
|
-
|
205
|
-
it "objectifies and returns a hash" do
|
206
|
-
stub_request(:get, "#{@scheme}://#{@base}/").to_return :body => '{"foo":"bar"}'
|
207
|
-
|
208
|
-
response = @instance.get "/", :body => { :foo => :bar }
|
209
|
-
|
210
|
-
response.body.must_be_kind_of Hash
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
describe "xml" do
|
215
|
-
before do
|
216
|
-
@instance = Hugs.new @valid_options.merge(:type => :xml)
|
217
|
-
end
|
218
|
-
|
219
|
-
it "parses and returns a Nokogiri object" do
|
220
|
-
stub_request(:get, "#{@scheme}://#{@base}/").to_return :body => "<STORAGE></STORAGE>"
|
221
|
-
|
222
|
-
response = @instance.get "/", :body => { :foo => :bar }
|
223
|
-
|
224
|
-
response.body.must_be_kind_of Nokogiri::XML::Document
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
end
|