hugs 2.5.2 → 2.6.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/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
- pkg/
2
- .bundle/
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Hugs
2
2
 
3
- Hugs net-http-persistent with convenient get, delete, post, and put methods.
3
+ Hugs net-http-persistent with convenient delete, get, head, post, and put methods.
4
4
 
5
5
  Opted to write this gem for four reasons:
6
6
 
@@ -15,13 +15,12 @@ Opted to write this gem for four reasons:
15
15
  There looks to be [work on this front](https://github.com/geemus/excon/issues#issue/29).
16
16
  * Wanted to learn how to handle this pattern.
17
17
 
18
- The XML usage of this gem will probably change. In the next couple of weeks Hugs
19
- will be implemented against an XML OCCI API.
20
-
21
- ## Assumptions
18
+ ## Opinionated
22
19
 
23
20
  * The webservice returns JSON or XML.
24
- * To objectify the returned JSON or hand back a Nokogiri::XML::Document.
21
+ * It is okay to parse the response.
22
+ * A Hash is returned when response is JSON.
23
+ * A Nokogiri::XML::Document is returned when XML.
25
24
 
26
25
  ## Usage
27
26
 
@@ -33,8 +32,18 @@ will be implemented against an XML OCCI API.
33
32
 
34
33
  See the 'Examples' section in the [wiki](http://github.com/retr0h/hugs/wiki/).
35
34
 
35
+ ## Compatability
36
+
37
+ ruby 1.9.2
38
+
36
39
  ## Testing
37
40
 
38
41
  Tests can run offline thanks to [webmock](https://github.com/bblimke/webmock).
39
42
 
40
43
  $ bundle exec rake
44
+
45
+ ## TODOs
46
+
47
+ * avoid the parse (eg client.raw)
48
+ * override type per request
49
+ * support hased query params
data/lib/hugs/client.rb CHANGED
@@ -1,60 +1,68 @@
1
1
  require "hugs/errors"
2
2
 
3
3
  require "net/http/persistent"
4
- require "net/http/post/multipart"
4
+ require "uri"
5
5
  require "nokogiri"
6
6
  require "yajl"
7
7
 
8
8
  module Hugs
9
9
  class Client
10
- attr_accessor :headers
11
- attr_accessor :raise_4xx, :raise_5xx, :debug, :http
12
- attr_writer :raise_4xx, :raise_5xx, :debug, :http
13
-
14
- Headers = {
15
- :json => "application/json",
16
- :xml => "application/xml",
10
+ MIME_TYPES = {
11
+ :binary => "application/octet-stream",
12
+ :json => "application/json",
13
+ :xml => "application/xml",
17
14
  }.freeze
18
15
 
19
- Classes = [
20
- Net::HTTP::Get,
16
+ CLASSES = [
21
17
  Net::HTTP::Delete,
18
+ Net::HTTP::Get,
19
+ Net::HTTP::Head,
22
20
  Net::HTTP::Post,
23
- Net::HTTP::Put,
24
- Net::HTTP::Head
21
+ Net::HTTP::Put
25
22
  ].freeze
26
23
 
27
24
  ##
28
25
  # Required options:
29
26
  # +host+: A String with the host to connect.
30
27
  # Optional:
31
- # +user+: A String containing the username for use in HTTP Basic auth.
32
- # +password+: A String containing the password for use in HTTP Basic auth.
33
- # +port+: An Integer containing the port to connect.
34
- # +scheme+: A String containing the HTTP scheme.
28
+ # +user+: A String containing the username for use in HTTP Basic Authentication.
29
+ # +pass+: A String containing the password for use in HTTP Basic Authentication.
30
+ # +port+: An Integer containing the port to connect.
31
+ # +scheme+: A String containing the HTTP scheme.
32
+ # +type+: A Symbol containing (:json or :xml) for automatic content-type parsing
33
+ # and encoding.
34
+ # +headers+: A Hash containing HTTP headers.
35
+ # +raises+: A boolean if HTTP 4xx and 5xx status codes should be raised.
35
36
 
36
37
  def initialize options
37
- @user = options[:user]
38
- @password = options[:password]
39
- @host = options[:host]
40
- @port = options[:port] || 80
41
- @scheme = options[:scheme] || "http"
42
- @type = options[:type] || :json
43
- @headers = options[:headers] || {}
44
-
45
- @http = Net::HTTP::Persistent.new
46
- @url = URI.parse "#{@scheme}://#{@host}:#{@port}"
38
+ @user = options[:user]
39
+ @pass = options[:password]
40
+ host = options[:host]
41
+ raises = options[:raise_errors]
42
+ port = options[:port] || 80
43
+ scheme = options[:scheme] || "http"
44
+ @type = options[:type] || :json
45
+ @headers = options[:headers] || {}
46
+
47
+ @http = Net::HTTP::Persistent.new
48
+ @errors = Errors::HTTP.new :raise_errors => raises
49
+ @uri = URI.parse "#{scheme}://#{options[:host]}:#{port}"
50
+
51
+ @http.debug_output = $stdout if options[:debug]
47
52
  end
48
53
 
49
54
  ##
50
- # Perform an HTTP get, delete, post, or put.
55
+ # Perform an HTTP Delete, Head, Get, Post, or Put.
56
+ #
51
57
  # +path+: A String with the path to the HTTP resource.
52
58
  # +params+: A Hash with the following keys:
53
- # - +:query+: Query String in the format "foo=bar"
54
- # - +:body+: A sub Hash to be JSON encoded, and posted in
55
- # the message body.
59
+ # +:query+: A String with the format "foo=bar".
60
+ # +:body+: A String containing the message body for Put and Post requests.
61
+ # +:upload+: A Hash with the following keys:
62
+ # +file+: The file to be HTTP chunked uploaded.
63
+ # +headers+: A Hash containing additional HTTP headers.
56
64
 
57
- Classes.each do |clazz|
65
+ CLASSES.each do |clazz|
58
66
  verb = clazz.to_s.split("::").last.downcase
59
67
 
60
68
  define_method verb do |*args|
@@ -66,10 +74,13 @@ module Hugs
66
74
  end
67
75
 
68
76
  ##
69
- # :method: get
77
+ # :method: delete
70
78
 
71
79
  ##
72
- # :method: delete
80
+ # :method: head
81
+
82
+ ##
83
+ # :method: get
73
84
 
74
85
  ##
75
86
  # :method: post
@@ -79,57 +90,88 @@ module Hugs
79
90
 
80
91
  private
81
92
  ##
82
- # Worker method to be called by #get, #delete, #post, #put.
93
+ # Worker method to be called by #delete, #get, #head, #post, or #put.
94
+ #
83
95
  # Method arguments have been documented in the callers.
84
96
 
85
- def response_for request, path, params
86
- @http.debug_output = $stdout if debug
97
+ def response_for clazz, path, params
98
+ request = clazz.new path_with_query path, params[:query]
99
+ request.body = encode params[:body]
87
100
 
88
- query = params[:query] && params.delete(:query)
89
- body = params[:body] && params.delete(:body)
90
- upload = params[:upload] && params.delete(:upload)
91
- full_path = path_with_query path, query
101
+ handle_request request, params[:upload]
102
+ end
92
103
 
93
- if upload && request.class === Net::HTTP::Post
94
- parts = upload[:parts] || {}
95
- parts[:file] = UploadIO.new(parts[:file], upload[:content_type]) if parts[:file]
104
+ ##
105
+ # Handles setting headers, performing the HTTP connection, parsing the
106
+ # response, and checking for any response errors (if configured).
107
+ #
108
+ # +request+: A net/http request Object.
96
109
 
97
- request = Net::HTTP::Post::Multipart.new full_path, parts
98
- else
99
- request = request.new full_path
100
- request.body = encode body
110
+ def handle_request request, upload
111
+ uploads request, upload
112
+ headers request, upload
101
113
 
102
- add_headers request
103
- end
114
+ response = @http.request @uri, request
115
+ response.body = parse response.body
116
+
117
+ request.body_stream.close if request.body_stream
104
118
 
105
- request.basic_auth(@user, @password) if requires_authentication?
106
- handle_response request
119
+ @errors.errors response
107
120
  end
108
121
 
109
- def handle_response request
110
- resp = @http.request @url, request
111
- Hugs::Errors::status_error resp, @raise_4xx, @raise_5xx
112
- resp.body = parse resp.body if resp.body
122
+ ##
123
+ # Handle chunked uploads.
124
+ #
125
+ # +request+: A net/http request Object.
126
+ # +upload+: A Hash containing :file and :headers for uploading.
113
127
 
114
- resp
115
- end
128
+ def uploads request, upload
129
+ return unless upload
116
130
 
117
- def path_with_query path, query
118
- [path, query].compact.join "?"
131
+ @headers.merge! chunked_headers upload if upload
132
+
133
+ request.body_stream = File.open upload[:file]
119
134
  end
120
135
 
121
- def add_headers request
122
- request.add_field "Accept", Headers[@type]
123
- if [Net::HTTP::Post, Net::HTTP::Put].include? request.class
124
- request.add_field "Content-Type", Headers[@type]
125
- end
136
+ ##
137
+ # Handles the setting of various default and custom headers.
138
+ # Headers set in initialize can override all others.
139
+ #
140
+ # +request+: A net/http request Object.
141
+
142
+ def headers request, upload
143
+ request.basic_auth(@user, @pass) if requires_authentication?
144
+
145
+ request.add_field "Accept", MIME_TYPES[@type]
146
+ request.add_field "Content-Type", MIME_TYPES[@type] if requires_content_type? request
126
147
 
127
148
  @headers.each do |header, value|
128
- request.add_field header, value
129
- end
149
+ request[header] = value
150
+ end if @headers
151
+ end
152
+
153
+ ##
154
+ # Setting of chunked upload headers.
155
+ #
156
+ # +:upload+: A Hash with the following keys:
157
+ # +file+: The file to be HTTP chunked uploaded.
158
+ # +headers+: A Hash containing additional HTTP headers.
159
+
160
+ def chunked_headers upload
161
+ chunked_headers = {
162
+ "Content-Type" => MIME_TYPES[:binary],
163
+ "Content-Length" => File.size(upload[:file]),
164
+ "Transfer-Encoding" => "chunked"
165
+ }.merge upload[:headers] || {}
166
+ end
167
+
168
+ def path_with_query path, query
169
+ [path, query].compact.join "?"
130
170
  end
131
171
 
132
172
  def parse data
173
+ return unless data
174
+
133
175
  if is_json?
134
176
  Yajl::Parser.parse data
135
177
  elsif is_xml?
@@ -141,11 +183,16 @@ module Hugs
141
183
 
142
184
  def encode body
143
185
  return unless body
186
+
144
187
  is_json? ? (Yajl::Encoder.encode body) : body
145
188
  end
146
189
 
147
190
  def requires_authentication?
148
- @user && @password
191
+ @user && @pass
192
+ end
193
+
194
+ def requires_content_type? request
195
+ [Net::HTTP::Post, Net::HTTP::Put].include? request.class
149
196
  end
150
197
 
151
198
  def is_xml?
data/lib/hugs/errors.rb CHANGED
@@ -39,46 +39,56 @@ module Hugs
39
39
  class ServiceUnavailable < HTTPStatusError; end # 503
40
40
  class GatewayTimeout < HTTPStatusError; end # 504
41
41
 
42
+ class HTTP
43
+ def initialize options
44
+ @raise_errors = options[:raise_errors]
45
+
46
+ initialize_errors
47
+ end
42
48
 
43
- def self.status_error response, raise_4xx, raise_5xx
44
- @errors ||= {
45
- 400 => [Hugs::Errors::BadRequest, 'Bad Request'],
46
- 401 => [Hugs::Errors::Unauthorized, 'Unauthorized'],
47
- 402 => [Hugs::Errors::PaymentRequired, 'Payment Required'],
48
- 403 => [Hugs::Errors::Forbidden, 'Forbidden'],
49
- 404 => [Hugs::Errors::NotFound, 'Not Found'],
50
- 405 => [Hugs::Errors::MethodNotAllowed, 'Method Not Allowed'],
51
- 406 => [Hugs::Errors::NotAcceptable, 'Not Acceptable'],
52
- 407 => [Hugs::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
53
- 408 => [Hugs::Errors::RequestTimeout, 'Request Timeout'],
54
- 409 => [Hugs::Errors::Conflict, 'Conflict'],
55
- 410 => [Hugs::Errors::Gone, 'Gone'],
56
- 411 => [Hugs::Errors::LengthRequired, 'Length Required'],
57
- 412 => [Hugs::Errors::PreconditionFailed, 'Precondition Failed'],
58
- 413 => [Hugs::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
59
- 414 => [Hugs::Errors::RequestURITooLong, 'Request-URI Too Long'],
60
- 415 => [Hugs::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
61
- 416 => [Hugs::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
62
- 417 => [Hugs::Errors::ExpectationFailed, 'Expectation Failed'],
63
- 422 => [Hugs::Errors::UnprocessableEntity, 'Unprocessable Entity'],
64
- 500 => [Hugs::Errors::InternalServerError, 'InternalServerError'],
65
- 501 => [Hugs::Errors::NotImplemented, 'Not Implemented'],
66
- 502 => [Hugs::Errors::BadGateway, 'Bad Gateway'],
67
- 503 => [Hugs::Errors::ServiceUnavailable, 'Service Unavailable'],
68
- 504 => [Hugs::Errors::GatewayTimeout, 'Gateway Timeout']
69
- }
49
+ def errors response
50
+ case response.code
51
+ when @raise_errors && %r{^[45]{1}[0-9]{2}$} ; raise_for(response)
52
+ end
70
53
 
71
- case response.code
72
- when raise_4xx && %r{^4[0-9]{2}$} ; raise_for(response)
73
- when raise_5xx && %r{^5[0-9]{2}$} ; raise_for(response)
54
+ response
74
55
  end
75
- end
76
56
 
77
- private
78
- def self.raise_for response
79
- error, message = @errors[response.code.to_i]
57
+ private
58
+ def raise_for response
59
+ error, message = @errors[response.code.to_i]
80
60
 
81
- raise error.new message, response
61
+ raise error.new message, response
62
+ end
63
+
64
+ def initialize_errors
65
+ @errors ||= {
66
+ 400 => [Hugs::Errors::BadRequest, 'Bad Request'],
67
+ 401 => [Hugs::Errors::Unauthorized, 'Unauthorized'],
68
+ 402 => [Hugs::Errors::PaymentRequired, 'Payment Required'],
69
+ 403 => [Hugs::Errors::Forbidden, 'Forbidden'],
70
+ 404 => [Hugs::Errors::NotFound, 'Not Found'],
71
+ 405 => [Hugs::Errors::MethodNotAllowed, 'Method Not Allowed'],
72
+ 406 => [Hugs::Errors::NotAcceptable, 'Not Acceptable'],
73
+ 407 => [Hugs::Errors::ProxyAuthenticationRequired, 'Proxy Authentication Required'],
74
+ 408 => [Hugs::Errors::RequestTimeout, 'Request Timeout'],
75
+ 409 => [Hugs::Errors::Conflict, 'Conflict'],
76
+ 410 => [Hugs::Errors::Gone, 'Gone'],
77
+ 411 => [Hugs::Errors::LengthRequired, 'Length Required'],
78
+ 412 => [Hugs::Errors::PreconditionFailed, 'Precondition Failed'],
79
+ 413 => [Hugs::Errors::RequestEntityTooLarge, 'Request Entity Too Large'],
80
+ 414 => [Hugs::Errors::RequestURITooLong, 'Request-URI Too Long'],
81
+ 415 => [Hugs::Errors::UnsupportedMediaType, 'Unsupported Media Type'],
82
+ 416 => [Hugs::Errors::RequestedRangeNotSatisfiable, 'Request Range Not Satisfiable'],
83
+ 417 => [Hugs::Errors::ExpectationFailed, 'Expectation Failed'],
84
+ 422 => [Hugs::Errors::UnprocessableEntity, 'Unprocessable Entity'],
85
+ 500 => [Hugs::Errors::InternalServerError, 'InternalServerError'],
86
+ 501 => [Hugs::Errors::NotImplemented, 'Not Implemented'],
87
+ 502 => [Hugs::Errors::BadGateway, 'Bad Gateway'],
88
+ 503 => [Hugs::Errors::ServiceUnavailable, 'Service Unavailable'],
89
+ 504 => [Hugs::Errors::GatewayTimeout, 'Gateway Timeout']
90
+ }
91
+ end
82
92
  end
83
93
  end
84
94
  end
data/lib/hugs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hugs
2
- VERSION = "2.5.2"
2
+ VERSION = "2.6.0"
3
3
  end
@@ -1,270 +1,279 @@
1
1
  require "test_helper"
2
2
 
3
- require "base64"
3
+ require "uri"
4
4
 
5
5
  describe Hugs::Client do
6
6
  before do
7
- @instance = Hugs::Client.new valid_options
8
- @request = Net::HTTP::Get
7
+ @uri = URI::HTTP.build :host => "example.com", :path => "/"
8
+ @client = Hugs::Client.new :host => @uri.host
9
9
 
10
10
  WebMock.reset!
11
11
  end
12
12
 
13
- describe "path" do
14
- it "is valid" do
15
- stub_request :get, url
16
-
17
- @instance.send :response_for, @request, "/", {}
18
-
19
- assert_requested :get, url
13
+ describe "verbs" do
14
+ it "supports HTTP Get" do
15
+ must_support_http_get
20
16
  end
21
17
 
22
- it "is valid when an invalid :query is supplied" do
23
- stub_request :get, url
24
-
25
- @instance.send :response_for, @request, "/", :query => nil
26
-
27
- assert_requested :get, url
18
+ it "supports HTTP Delete" do
19
+ must_support_http_delete
28
20
  end
29
21
 
30
- it "also has a query string" do
31
- stub_request(:get, url).with:query => {"foo" => "bar"}
32
-
33
- @instance.send :response_for, @request, "/", :query => "foo=bar"
34
-
35
- assert_requested :get, url, :query => {"foo" => "bar"}
22
+ it "supports HTTP Head" do
23
+ must_support_http_head
36
24
  end
37
- end
38
25
 
39
- describe "multi-part" do
40
- Content_Type_Matcher = %r{multipart/form-data}
41
-
42
- before do
43
- @request = Net::HTTP::Post
26
+ it "supports HTTP Post" do
27
+ must_support_http_post
44
28
  end
45
29
 
46
- it "uploads a file" do
47
- stub_request :post, url
48
- upload = {
49
- :upload => {
50
- :parts => { :file => "/dev/null" },
51
- :content_type => "type/subtype"
52
- }
53
- }
54
-
55
- @instance.send :response_for, @request, "/", upload
56
-
57
- assert_requested :post, url, :body => %r{Content-Type: type/subtype}, :headers => {
58
- "Content-Type" => Content_Type_Matcher
59
- }
30
+ it "supports HTTP Put" do
31
+ must_support_http_put
60
32
  end
33
+ end
61
34
 
62
- it "has parts" do
63
- stub_request :post, url
64
- upload = {
65
- :upload => {
66
- :parts => { :foo => :bar, :baz => :xyzzy },
67
- :content_type => "foo/bar"
68
- }
69
- }
35
+ describe "path" do
36
+ it "supports a query string" do
37
+ stub_request(:get, @uri.to_s).with:query => { "foo" => "bar" }
70
38
 
71
- @instance.send :response_for, @request, "/", upload
39
+ @client.get @uri.path, :query => "foo=bar"
72
40
 
73
- ### wtf can't use mx together.
74
- content_disposition_matcher = %r{^Content-Disposition: form-data; name="foo".*^bar.*^Content-Disposition: form-data; name="baz".*^xyzzy.*}m
75
- assert_requested :post, url, :body => content_disposition_matcher, :headers => {
76
- "Content-Type" => Content_Type_Matcher
77
- }
41
+ assert_requested :get, @uri.to_s, :query => { "foo" => "bar" }
78
42
  end
79
43
  end
80
44
 
81
- describe "body" do
82
- describe "parses response" do
83
- describe "json" do
84
- it "objectifies and returns a hash" do
85
- stub_request(:get, url).to_return :body => '{"foo":"bar"}'
86
- instance = Hugs::Client.new valid_options(:type => :json)
87
-
88
- response = instance.get "/"
89
-
90
- response.body.must_be_kind_of Hash
91
- end
45
+ describe "headers" do
46
+ describe "json" do
47
+ it "adds Accept header" do
48
+ must_have_headers_for_get :json
92
49
  end
93
50
 
94
- describe "xml" do
95
- it "parses and returns a Nokogiri object" do
96
- stub_request(:get, url).to_return :body => "<STORAGE></STORAGE>"
97
- instance = Hugs::Client.new valid_options(:type => :xml)
98
-
99
- response = instance.get "/"
51
+ it "adds Content-Type header" do
52
+ must_have_headers_for_put :json
53
+ must_have_headers_for_post :json
54
+ end
55
+ end
100
56
 
101
- response.body.must_be_kind_of Nokogiri::XML::Document
102
- end
57
+ describe "xml" do
58
+ before do
59
+ @client = Hugs::Client.new :host => @uri.host, :type => :xml
103
60
  end
104
61
 
105
- ### TODO: webmock seems to turn nil into "" body. Cannot test a failing case.
106
- ### TODO: fix when I can.
107
- it "doesn't parse a nil body" do
108
- #stub_request(:head, url).to_return :body => nil
62
+ it "adds Accept header" do
63
+ must_have_headers_for_get :xml
64
+ end
109
65
 
110
- #@instance.send :response_for, Net::HTTP::Head, "/", {}
66
+ it "adds Content-Type header" do
67
+ must_have_headers_for_put :xml
68
+ must_have_headers_for_post :xml
111
69
  end
112
70
  end
113
71
 
114
- describe "encodes request" do
72
+ describe "custom" do
115
73
  before do
116
- stub_request :get, url
74
+ @client = Hugs::Client.new(
75
+ :host => @uri.host,
76
+ :headers => { "foo" => "bar", "baz" => "xyzzy" }
77
+ )
117
78
  end
118
79
 
119
- it "is not set when :body invalid" do
120
- @instance.send :response_for, @request, "/", :body => nil
121
-
122
- assert_requested :get, url, :body => nil
80
+ it "adds headers" do
81
+ must_have_headers_for :get, {}, "foo" => "bar", "baz" => "xyzzy"
123
82
  end
83
+ end
124
84
 
125
- it "is not set when :body is missing" do
126
- @instance.send :response_for, @request, "/", {}
127
-
128
- assert_requested :get, url, {}
85
+ describe "custom override defaults" do
86
+ before do
87
+ @client = Hugs::Client.new(
88
+ :host => @uri.host,
89
+ :headers => { "Accept" => "foo bar" }
90
+ )
129
91
  end
130
92
 
131
- describe "json" do
132
- it "is valid" do
133
- instance = Hugs::Client.new valid_options(:type => :json)
134
-
135
- instance.send :response_for, @request, "/", :body => {:foo => :bar}
136
-
137
- assert_requested :get, url, :body => '{"foo":"bar"}'
138
- end
93
+ it "replaces headers" do
94
+ must_have_headers_for :get, {}, "Accept" => "foo bar"
139
95
  end
96
+ end
97
+ end
140
98
 
141
- describe "xml" do
142
- it "is valid" do
143
- instance = Hugs::Client.new valid_options(:type => :xml)
99
+ describe "authentication" do
100
+ before do
101
+ @uri.userinfo = "user:credentials"
102
+ @client = Hugs::Client.new(
103
+ :host => @uri.host,
104
+ :user => "user",
105
+ :password => "credentials"
106
+ )
107
+ end
144
108
 
145
- instance.send :response_for, @request, "/", :body => "foo bar"
109
+ it "uses Basic Authentication when initialized with :user and :password" do
110
+ stub_request :get, @uri.to_s
146
111
 
147
- assert_requested :get, url, :body => "foo bar"
148
- end
149
- end
112
+ @client.get @uri.path
113
+
114
+ assert_requested :get, @uri.to_s
150
115
  end
151
116
  end
152
117
 
153
- describe "headers" do
154
- describe "authentication" do
155
- it "uses basic auth when providing user and password" do
156
- stub_request :get, url("/", "user:credentials")
157
- instance = Hugs::Client.new valid_options(:user => "user", :password => "credentials")
118
+ describe "parse body" do
119
+ describe "json" do
120
+ it "parses the body" do
121
+ stub_request(:get, @uri.to_s).to_return :body => '{"foo":"bar"}'
158
122
 
159
- instance.send :response_for, @request, "/", {}
160
- assert_requested :get, url("/", "user:credentials")
123
+ response = @client.get @uri.path
124
+
125
+ response.body['foo'].must_equal "bar"
161
126
  end
127
+ end
162
128
 
163
- it "doesn't use basic auth without a user" do
164
- assert_doesnt_use_basic_auth_without :user
129
+ describe "xml" do
130
+ before do
131
+ @client = Hugs::Client.new :host => @uri.host, :type => :xml
165
132
  end
166
133
 
167
- it "doesn't use basic auth without a password" do
168
- assert_doesnt_use_basic_auth_without :password
134
+ it "parses the body" do
135
+ stub_request(:get, @uri.to_s).to_return :body => "<foo>bar</foo>"
136
+
137
+ response = @client.get @uri.path
138
+
139
+ response.body.xpath('foo').text.must_equal "bar"
169
140
  end
170
141
  end
142
+ end
171
143
 
172
- describe "JSON" do
173
- it "supports json GET" do
174
- assert_supports_http_verb :json, :get
175
- end
144
+ describe "encode body" do
145
+ describe "json" do
146
+ it "encodes the body" do
147
+ stub_request :post, @uri.to_s
176
148
 
177
- it "supports json DELETE" do
178
- assert_supports_http_verb :json, :delete
179
- end
149
+ @client.post @uri.path, :body => { :foo => :bar }
180
150
 
181
- it "supports json POST" do
182
- assert_supports_http_verb :json, :post
151
+ assert_requested :post, @uri.to_s, :body => '{"foo":"bar"}'
183
152
  end
153
+ end
184
154
 
185
- it "supports json PUT" do
186
- assert_supports_http_verb :json, :put
155
+ describe "xml" do
156
+ before do
157
+ @client = Hugs::Client.new :host => @uri.host, :type => :xml
187
158
  end
188
159
 
189
- it "supports json HEAD" do
190
- assert_supports_http_verb :json, :head
160
+ it "does not encode the body" do
161
+ stub_request :post, @uri.to_s
162
+
163
+ @client.post @uri.path, :body => "foo bar"
164
+
165
+ assert_requested :post, @uri.to_s, :body => "foo bar"
191
166
  end
192
167
  end
168
+ end
193
169
 
194
- describe "XML" do
195
- it "supports xml GET" do
196
- assert_supports_http_verb :xml, :get
197
- end
170
+ describe "upload" do
171
+ describe "chunked" do
172
+ before do
173
+ @upload = {
174
+ :upload => { :file => "/dev/null" }
175
+ }
198
176
 
199
- it "supports xml DELETE" do
200
- assert_supports_http_verb :xml, :delete
177
+ @upload_with_headers = {
178
+ :upload => {
179
+ :file => "/dev/null",
180
+ :headers => { "Accept" => "foo bar" }
181
+ }
182
+ }
201
183
  end
202
184
 
203
- it "supports xml POST" do
204
- assert_supports_http_verb :xml, :post
185
+ it "has chunked headers" do
186
+ must_have_headers_for :post, @upload, {
187
+ "Content-Type" => "application/octet-stream",
188
+ ###"Content-Length" => "0",
189
+ "Transfer-Encoding" => "chunked"
190
+ }
205
191
  end
206
192
 
207
- it "supports xml PUT" do
208
- assert_supports_http_verb :xml, :put
193
+ it "upload headers override default headers" do
194
+ must_have_headers_for :post, @upload_with_headers, "Accept" => "foo bar"
209
195
  end
210
196
 
211
- it "supports xml HEAD" do
212
- assert_supports_http_verb :xml, :head
197
+ it "has chunked body" do
198
+ #stub_request :post, @uri.to_s
199
+
200
+ #@client.post @uri.path, @upload
201
+
202
+ #assert_requested verb, @uri.to_s, :headers => headers
213
203
  end
214
204
  end
215
205
  end
216
206
 
217
- describe "debug_output" do
207
+ describe "debug" do
218
208
  before do
219
- @instance = Hugs::Client.new valid_options(
220
- :host => "gist.github.com",
221
- :port => "80"
222
- )
223
-
224
209
  WebMock.allow_net_connect!
225
210
  end
226
211
 
227
212
  it "outputs net/http debug data" do
228
- @instance.debug = true
213
+ out, _ = capture_io {
214
+ client = Hugs::Client.new :host => "gist.github.com", :debug => true
229
215
 
230
- out, err = capture_io { @instance.send :response_for, Net::HTTP::Get, "/api/v1/json/374130", {} }
216
+ client.get "/api/v1/json/374130"
217
+ }
231
218
 
232
219
  out.wont_be_empty
233
220
  end
234
221
 
235
222
  it "does not output net/http debug data" do
236
- out, err = capture_io { @instance.send :response_for, Net::HTTP::Get, "/api/v1/json/374130", {} }
223
+ out, _ = capture_io {
224
+ client = Hugs::Client.new :host => "gist.github.com"
225
+
226
+ client.get "/api/v1/json/374130"
227
+ }
237
228
 
238
229
  out.must_be_empty
239
230
  end
240
231
  end
241
232
 
233
+ def must_support_http_delete
234
+ must_support_http :delete
235
+ end
242
236
 
243
- def assert_supports_http_verb type, verb
244
- mimetype = {:xml => 'application/xml',
245
- :json => 'application/json'}[type]
237
+ def must_support_http_get
238
+ must_support_http :get
239
+ end
246
240
 
247
- assert mimetype, "Unsupported Mimetype '#{type}'"
241
+ def must_support_http_head
242
+ must_support_http :head
243
+ end
248
244
 
249
- clazz = Net::HTTP.const_get verb.capitalize
245
+ def must_support_http_post
246
+ must_support_http :post
247
+ end
250
248
 
251
- stub_request verb, url
252
- instance = Hugs::Client.new valid_options(:type => type)
249
+ def must_support_http_put
250
+ must_support_http :put
251
+ end
253
252
 
254
- instance.send :response_for, clazz, "/", {}
253
+ def must_support_http verb
254
+ stub_request verb, @uri.to_s
255
+ @client.send verb, @uri.path
255
256
 
256
- headers = { "Accept" => ["*/*", mimetype] }
257
- headers["Content-Type"] = mimetype if [:put, :put].include? type
257
+ assert_requested verb, @uri.to_s
258
+ end
259
+
260
+ def must_have_headers_for_get type
261
+ must_have_headers_for :get, {}, "Accept" => ["*/*", "application/#{type}"]
262
+ end
263
+
264
+ def must_have_headers_for_put type
265
+ must_have_headers_for :put, {}, "Content-Type" => "application/#{type}"
266
+ end
258
267
 
259
- assert_requested verb, url, :headers => headers
268
+ def must_have_headers_for_post type
269
+ must_have_headers_for :post, {}, "Content-Type" => "application/#{type}"
260
270
  end
261
271
 
262
- def assert_doesnt_use_basic_auth_without option
263
- stub_request :get, url
264
- instance = Hugs::Client.new valid_options(option => "value")
272
+ def must_have_headers_for verb, options, headers
273
+ stub_request verb, @uri.to_s
265
274
 
266
- instance.send :response_for, @request, "/", {}
275
+ @client.send verb, @uri.path, options
267
276
 
268
- assert_requested :get, url
277
+ assert_requested verb, @uri.to_s, :headers => headers
269
278
  end
270
279
  end
@@ -1,46 +1,59 @@
1
1
  require "test_helper"
2
2
 
3
- describe Hugs::Errors do
3
+ describe Hugs::Errors::HTTP do
4
4
  before do
5
- @instance = Hugs::Client.new valid_options
5
+ @errors_4xx = (400..417).to_a << 422
6
+ @errors_5xx = (500..504).to_a
7
+ @errors_all = @errors_4xx + @errors_5xx
8
+ @uri = URI::HTTP.build :host => "example.com"
6
9
 
7
10
  WebMock.reset!
8
11
  end
9
12
 
10
- describe "response codes" do
11
- Errors_4xx = (400..417).to_a << 422
12
- Errors_5xx = (500..504).to_a
13
-
14
- (Errors_4xx + Errors_5xx).each do |code|
15
- it "raises on #{code}" do
16
- assert_raises_on code
13
+ describe "errors" do
14
+ describe "does not raise" do
15
+ before do
16
+ @client = Hugs::Client.new :host => @uri.host
17
17
  end
18
18
 
19
- it "does not raise on #{code}" do
20
- assert_does_not_raise_on code
19
+ it "returns response" do
20
+ @errors_all.each do |code|
21
+ must_not_raise_on code
22
+ end
21
23
  end
22
24
  end
23
25
 
24
- def assert_raises_on code
25
- (code.to_s =~ %r{^4[0-9]{2}$}) ? (@instance.raise_4xx = true) : (@instance.raise_5xx = true)
26
- stub_request(:get, url).to_return :status => [code, "error #{code}"]
26
+ describe "raises" do
27
+ before do
28
+ @client = Hugs::Client.new :host => @uri.host, :raise_errors => true
29
+ end
27
30
 
28
- begin
29
- @instance.send :response_for, Net::HTTP::Get, "/", {}
30
- rescue => e
31
- e.class.superclass.must_be_same_as Hugs::Errors::HTTPStatusError
32
- e.must_respond_to :response
33
- return
31
+ it "returns exception" do
32
+ @errors_all.each do |code|
33
+ must_raise_on code
34
+ end
34
35
  end
35
- raise StandardError.new "did not raise expected exception"
36
36
  end
37
+ end
37
38
 
38
- def assert_does_not_raise_on code
39
- stub_request(:get, url).to_return :status => [code, "error #{code}"]
40
-
41
- response = @instance.send :response_for, Net::HTTP::Get, "/", {}
39
+ def must_raise_on code
40
+ stub_request(:get, @uri.to_s).to_return :status => [code, "error #{code}"]
42
41
 
43
- response.code.must_equal code.to_s
42
+ begin
43
+ @client.get "/"
44
+ rescue => e
45
+ e.class.superclass.must_be_same_as Hugs::Errors::HTTPStatusError
46
+ e.must_respond_to :response
47
+ return
44
48
  end
49
+ raise StandardError.new "did not raise expected exception"
50
+ end
51
+
52
+ def must_not_raise_on code
53
+ stub_request(:get, @uri.to_s).to_return :status => [code, "error #{code}"]
54
+
55
+ response = @client.get "/"
56
+
57
+ response.code.must_equal code.to_s
45
58
  end
46
59
  end
data/test/test_helper.rb CHANGED
@@ -3,28 +3,10 @@ Bundler.setup :default, :test
3
3
  require "hugs"
4
4
 
5
5
  require "minitest/spec"
6
- require "uri"
7
6
  require "webmock"
8
7
 
9
8
  class MiniTest::Unit::TestCase
10
9
  include WebMock::API
11
-
12
- def valid_options opts = {}
13
- {
14
- :host => "example.com",
15
- :port => 80,
16
- :scheme => "http"
17
- }.merge opts
18
- end
19
-
20
- def url path = "/", credentials = nil
21
- URI::HTTP.build(
22
- valid_options(
23
- :path => path,
24
- :userinfo => credentials
25
- )
26
- ).to_s
27
- end
28
10
  end
29
11
 
30
12
  MiniTest::Unit.autorun
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: hugs
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.5.2
5
+ version: 2.6.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - John Dewey
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-07 00:00:00 -07:00
13
+ date: 2011-05-17 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -103,7 +103,6 @@ files:
103
103
  - .gitignore
104
104
  - .rvmrc
105
105
  - Gemfile
106
- - Gemfile.lock
107
106
  - LICENSE
108
107
  - README.md
109
108
  - Rakefile
data/Gemfile.lock DELETED
@@ -1,32 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- hugs (2.5.1)
5
- multipart-post (~> 1.0.1)
6
- net-http-persistent (~> 1.4.1)
7
- nokogiri (~> 1.4.4)
8
- yajl-ruby (~> 0.7.9)
9
-
10
- GEM
11
- remote: http://rubygems.org/
12
- specs:
13
- addressable (2.2.2)
14
- crack (0.1.8)
15
- minitest (2.0.2)
16
- multipart-post (1.0.1)
17
- net-http-persistent (1.4.1)
18
- nokogiri (1.4.4)
19
- rake (0.8.7)
20
- webmock (1.6.1)
21
- addressable (>= 2.2.2)
22
- crack (>= 0.1.7)
23
- yajl-ruby (0.7.9)
24
-
25
- PLATFORMS
26
- ruby
27
-
28
- DEPENDENCIES
29
- hugs!
30
- minitest
31
- rake
32
- webmock