yoyle439587298 0.13.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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +14 -0
  5. data/Guardfile +16 -0
  6. data/History +303 -0
  7. data/LICENSE +21 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +80 -0
  10. data/Rakefile +12 -0
  11. data/bin/httparty +117 -0
  12. data/cucumber.yml +1 -0
  13. data/examples/aaws.rb +32 -0
  14. data/examples/basic.rb +32 -0
  15. data/examples/crack.rb +19 -0
  16. data/examples/custom_parsers.rb +67 -0
  17. data/examples/delicious.rb +37 -0
  18. data/examples/google.rb +16 -0
  19. data/examples/headers_and_user_agents.rb +6 -0
  20. data/examples/nokogiri_html_parser.rb +22 -0
  21. data/examples/rubyurl.rb +14 -0
  22. data/examples/tripit_sign_in.rb +33 -0
  23. data/examples/twitter.rb +31 -0
  24. data/examples/whoismyrep.rb +10 -0
  25. data/features/basic_authentication.feature +20 -0
  26. data/features/command_line.feature +7 -0
  27. data/features/deals_with_http_error_codes.feature +26 -0
  28. data/features/digest_authentication.feature +20 -0
  29. data/features/handles_compressed_responses.feature +27 -0
  30. data/features/handles_multiple_formats.feature +57 -0
  31. data/features/steps/env.rb +22 -0
  32. data/features/steps/httparty_response_steps.rb +52 -0
  33. data/features/steps/httparty_steps.rb +35 -0
  34. data/features/steps/mongrel_helper.rb +94 -0
  35. data/features/steps/remote_service_steps.rb +74 -0
  36. data/features/supports_redirection.feature +22 -0
  37. data/features/supports_timeout_option.feature +13 -0
  38. data/httparty.gemspec +26 -0
  39. data/lib/httparty.rb +578 -0
  40. data/lib/httparty/connection_adapter.rb +176 -0
  41. data/lib/httparty/cookie_hash.rb +22 -0
  42. data/lib/httparty/core_extensions.rb +32 -0
  43. data/lib/httparty/exceptions.rb +29 -0
  44. data/lib/httparty/hash_conversions.rb +51 -0
  45. data/lib/httparty/logger/apache_logger.rb +22 -0
  46. data/lib/httparty/logger/curl_logger.rb +48 -0
  47. data/lib/httparty/logger/logger.rb +18 -0
  48. data/lib/httparty/module_inheritable_attributes.rb +56 -0
  49. data/lib/httparty/net_digest_auth.rb +84 -0
  50. data/lib/httparty/parser.rb +141 -0
  51. data/lib/httparty/request.rb +330 -0
  52. data/lib/httparty/response.rb +72 -0
  53. data/lib/httparty/response/headers.rb +31 -0
  54. data/lib/httparty/version.rb +3 -0
  55. data/script/release +42 -0
  56. data/spec/fixtures/delicious.xml +23 -0
  57. data/spec/fixtures/empty.xml +0 -0
  58. data/spec/fixtures/google.html +3 -0
  59. data/spec/fixtures/ssl/generate.sh +29 -0
  60. data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
  61. data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
  62. data/spec/fixtures/ssl/generated/ca.crt +16 -0
  63. data/spec/fixtures/ssl/generated/ca.key +15 -0
  64. data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
  65. data/spec/fixtures/ssl/generated/server.crt +13 -0
  66. data/spec/fixtures/ssl/generated/server.key +15 -0
  67. data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
  68. data/spec/fixtures/twitter.csv +2 -0
  69. data/spec/fixtures/twitter.json +1 -0
  70. data/spec/fixtures/twitter.xml +403 -0
  71. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  72. data/spec/httparty/connection_adapter_spec.rb +298 -0
  73. data/spec/httparty/cookie_hash_spec.rb +83 -0
  74. data/spec/httparty/exception_spec.rb +23 -0
  75. data/spec/httparty/logger/apache_logger_spec.rb +26 -0
  76. data/spec/httparty/logger/curl_logger_spec.rb +18 -0
  77. data/spec/httparty/logger/logger_spec.rb +22 -0
  78. data/spec/httparty/net_digest_auth_spec.rb +152 -0
  79. data/spec/httparty/parser_spec.rb +165 -0
  80. data/spec/httparty/request_spec.rb +631 -0
  81. data/spec/httparty/response_spec.rb +221 -0
  82. data/spec/httparty/ssl_spec.rb +74 -0
  83. data/spec/httparty_spec.rb +764 -0
  84. data/spec/spec.opts +2 -0
  85. data/spec/spec_helper.rb +37 -0
  86. data/spec/support/ssl_test_helper.rb +47 -0
  87. data/spec/support/ssl_test_server.rb +80 -0
  88. data/spec/support/stub_response.rb +43 -0
  89. data/website/css/common.css +47 -0
  90. data/website/index.html +73 -0
  91. metadata +208 -0
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ describe HTTParty::Logger do
4
+ describe ".build" do
5
+ subject { HTTParty::Logger }
6
+
7
+ it "defaults level to :info" do
8
+ logger_double = double()
9
+ subject.build(logger_double, nil, nil).level.should == :info
10
+ end
11
+
12
+ it "defaults format to :apache" do
13
+ logger_double = double()
14
+ subject.build(logger_double, nil, nil).should be_an_instance_of(HTTParty::Logger::ApacheLogger)
15
+ end
16
+
17
+ it "builds :curl style logger" do
18
+ logger_double = double()
19
+ subject.build(logger_double, nil, :curl).should be_an_instance_of(HTTParty::Logger::CurlLogger)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,152 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe Net::HTTPHeader::DigestAuthenticator do
4
+ def setup_digest(response)
5
+ digest = Net::HTTPHeader::DigestAuthenticator.new("Mufasa",
6
+ "Circle Of Life", "GET", "/dir/index.html", response)
7
+ digest.stub(:random).and_return("deadbeef")
8
+ Digest::MD5.stub(:hexdigest) { |str| "md5(#{str})" }
9
+ digest
10
+ end
11
+
12
+ def authorization_header
13
+ @digest.authorization_header.join(", ")
14
+ end
15
+
16
+
17
+ context "with an opaque value in the response header" do
18
+ before do
19
+ @digest = setup_digest({
20
+ 'www-authenticate' => 'Digest realm="myhost@testrealm.com", opaque="solid"'
21
+ })
22
+ end
23
+
24
+ it "should set opaque" do
25
+ authorization_header.should include(%Q(opaque="solid"))
26
+ end
27
+ end
28
+
29
+ context "without an opaque valid in the response header" do
30
+ before do
31
+ @digest = setup_digest({
32
+ 'www-authenticate' => 'Digest realm="myhost@testrealm.com"'
33
+ })
34
+ end
35
+
36
+ it "should not set opaque" do
37
+ authorization_header.should_not include(%Q(opaque=))
38
+ end
39
+ end
40
+
41
+ context "with specified quality of protection (qop)" do
42
+ before do
43
+ @digest = setup_digest({
44
+ 'www-authenticate' => 'Digest realm="myhost@testrealm.com", nonce="NONCE", qop="auth"',
45
+ })
46
+ end
47
+
48
+ it "should set prefix" do
49
+ authorization_header.should =~ /^Digest /
50
+ end
51
+
52
+ it "should set username" do
53
+ authorization_header.should include(%Q(username="Mufasa"))
54
+ end
55
+
56
+ it "should set digest-uri" do
57
+ authorization_header.should include(%Q(uri="/dir/index.html"))
58
+ end
59
+
60
+ it "should set qop" do
61
+ authorization_header.should include(%Q(qop="auth"))
62
+ end
63
+
64
+ it "should set cnonce" do
65
+ authorization_header.should include(%Q(cnonce="md5(deadbeef)"))
66
+ end
67
+
68
+ it "should set nonce-count" do
69
+ authorization_header.should include(%Q(nc=00000001))
70
+ end
71
+
72
+ it "should set response" do
73
+ request_digest = "md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life):NONCE:00000001:md5(deadbeef):auth:md5(GET:/dir/index.html))"
74
+ authorization_header.should include(%Q(response="#{request_digest}"))
75
+ end
76
+ end
77
+
78
+
79
+ context "with unspecified quality of protection (qop)" do
80
+ before do
81
+ @digest = setup_digest({
82
+ 'www-authenticate' => 'Digest realm="myhost@testrealm.com", nonce="NONCE"',
83
+ })
84
+ end
85
+
86
+ it "should set prefix" do
87
+ authorization_header.should =~ /^Digest /
88
+ end
89
+
90
+ it "should set username" do
91
+ authorization_header.should include(%Q(username="Mufasa"))
92
+ end
93
+
94
+ it "should set digest-uri" do
95
+ authorization_header.should include(%Q(uri="/dir/index.html"))
96
+ end
97
+
98
+ it "should not set qop" do
99
+ authorization_header.should_not include(%Q(qop=))
100
+ end
101
+
102
+ it "should not set cnonce" do
103
+ authorization_header.should_not include(%Q(cnonce=))
104
+ end
105
+
106
+ it "should not set nonce-count" do
107
+ authorization_header.should_not include(%Q(nc=))
108
+ end
109
+
110
+ it "should set response" do
111
+ request_digest = "md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life):NONCE:md5(GET:/dir/index.html))"
112
+ authorization_header.should include(%Q(response="#{request_digest}"))
113
+ end
114
+ end
115
+
116
+ context "with multiple authenticate headers" do
117
+ before do
118
+ @digest = setup_digest({
119
+ 'www-authenticate' => 'NTLM, Digest realm="myhost@testrealm.com", nonce="NONCE", qop="auth"',
120
+ })
121
+ end
122
+
123
+ it "should set prefix" do
124
+ authorization_header.should =~ /^Digest /
125
+ end
126
+
127
+ it "should set username" do
128
+ authorization_header.should include(%Q(username="Mufasa"))
129
+ end
130
+
131
+ it "should set digest-uri" do
132
+ authorization_header.should include(%Q(uri="/dir/index.html"))
133
+ end
134
+
135
+ it "should set qop" do
136
+ authorization_header.should include(%Q(qop="auth"))
137
+ end
138
+
139
+ it "should set cnonce" do
140
+ authorization_header.should include(%Q(cnonce="md5(deadbeef)"))
141
+ end
142
+
143
+ it "should set nonce-count" do
144
+ authorization_header.should include(%Q(nc=00000001))
145
+ end
146
+
147
+ it "should set response" do
148
+ request_digest = "md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life):NONCE:00000001:md5(deadbeef):auth:md5(GET:/dir/index.html))"
149
+ authorization_header.should include(%Q(response="#{request_digest}"))
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,165 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe HTTParty::Parser do
4
+ describe ".SupportedFormats" do
5
+ it "returns a hash" do
6
+ HTTParty::Parser::SupportedFormats.should be_instance_of(Hash)
7
+ end
8
+ end
9
+
10
+ describe ".call" do
11
+ it "generates an HTTParty::Parser instance with the given body and format" do
12
+ HTTParty::Parser.should_receive(:new).with('body', :plain).and_return(stub(:parse => nil))
13
+ HTTParty::Parser.call('body', :plain)
14
+ end
15
+
16
+ it "calls #parse on the parser" do
17
+ parser = mock('Parser')
18
+ parser.should_receive(:parse)
19
+ HTTParty::Parser.stub(:new => parser)
20
+ parser = HTTParty::Parser.call('body', :plain)
21
+ end
22
+ end
23
+
24
+ describe ".formats" do
25
+ it "returns the SupportedFormats constant" do
26
+ HTTParty::Parser.formats.should == HTTParty::Parser::SupportedFormats
27
+ end
28
+
29
+ it "returns the SupportedFormats constant for subclasses" do
30
+ class MyParser < HTTParty::Parser
31
+ SupportedFormats = {"application/atom+xml" => :atom}
32
+ end
33
+ MyParser.formats.should == {"application/atom+xml" => :atom}
34
+ end
35
+ end
36
+
37
+ describe ".format_from_mimetype" do
38
+ it "returns a symbol representing the format mimetype" do
39
+ HTTParty::Parser.format_from_mimetype("text/plain").should == :plain
40
+ end
41
+
42
+ it "returns nil when the mimetype is not supported" do
43
+ HTTParty::Parser.format_from_mimetype("application/atom+xml").should be_nil
44
+ end
45
+ end
46
+
47
+ describe ".supported_formats" do
48
+ it "returns a unique set of supported formats represented by symbols" do
49
+ HTTParty::Parser.supported_formats.should == HTTParty::Parser::SupportedFormats.values.uniq
50
+ end
51
+ end
52
+
53
+ describe ".supports_format?" do
54
+ it "returns true for a supported format" do
55
+ HTTParty::Parser.stub(:supported_formats => [:json])
56
+ HTTParty::Parser.supports_format?(:json).should be_true
57
+ end
58
+
59
+ it "returns false for an unsupported format" do
60
+ HTTParty::Parser.stub(:supported_formats => [])
61
+ HTTParty::Parser.supports_format?(:json).should be_false
62
+ end
63
+ end
64
+
65
+ describe "#parse" do
66
+ before do
67
+ @parser = HTTParty::Parser.new('body', :json)
68
+ end
69
+
70
+ it "attempts to parse supported formats" do
71
+ @parser.stub(:supports_format? => true)
72
+ @parser.should_receive(:parse_supported_format)
73
+ @parser.parse
74
+ end
75
+
76
+ it "returns the unparsed body when the format is unsupported" do
77
+ @parser.stub(:supports_format? => false)
78
+ @parser.parse.should == @parser.body
79
+ end
80
+
81
+ it "returns nil for an empty body" do
82
+ @parser.stub(:body => '')
83
+ @parser.parse.should be_nil
84
+ end
85
+
86
+ it "returns nil for a nil body" do
87
+ @parser.stub(:body => nil)
88
+ @parser.parse.should be_nil
89
+ end
90
+
91
+ it "returns nil for a 'null' body" do
92
+ @parser.stub(:body => "null")
93
+ @parser.parse.should be_nil
94
+ end
95
+
96
+ it "returns nil for a body with spaces only" do
97
+ @parser.stub(:body => " ")
98
+ @parser.parse.should be_nil
99
+ end
100
+ end
101
+
102
+ describe "#supports_format?" do
103
+ it "utilizes the class method to determine if the format is supported" do
104
+ HTTParty::Parser.should_receive(:supports_format?).with(:json)
105
+ parser = HTTParty::Parser.new('body', :json)
106
+ parser.send(:supports_format?)
107
+ end
108
+ end
109
+
110
+ describe "#parse_supported_format" do
111
+ it "calls the parser for the given format" do
112
+ parser = HTTParty::Parser.new('body', :json)
113
+ parser.should_receive(:json)
114
+ parser.send(:parse_supported_format)
115
+ end
116
+
117
+ context "when a parsing method does not exist for the given format" do
118
+ it "raises an exception" do
119
+ parser = HTTParty::Parser.new('body', :atom)
120
+ expect do
121
+ parser.send(:parse_supported_format)
122
+ end.to raise_error(NotImplementedError, "HTTParty::Parser has not implemented a parsing method for the :atom format.")
123
+ end
124
+
125
+ it "raises a useful exception message for subclasses" do
126
+ atom_parser = Class.new(HTTParty::Parser) do
127
+ def self.name; 'AtomParser'; end
128
+ end
129
+ parser = atom_parser.new 'body', :atom
130
+ expect do
131
+ parser.send(:parse_supported_format)
132
+ end.to raise_error(NotImplementedError, "AtomParser has not implemented a parsing method for the :atom format.")
133
+ end
134
+ end
135
+ end
136
+
137
+ context "parsers" do
138
+ subject do
139
+ HTTParty::Parser.new('body', nil)
140
+ end
141
+
142
+ it "parses xml with MultiXml" do
143
+ MultiXml.should_receive(:parse).with('body')
144
+ subject.send(:xml)
145
+ end
146
+
147
+ it "parses json with JSON" do
148
+ JSON.should_receive(:load).with('body', nil)
149
+ subject.send(:json)
150
+ end
151
+
152
+ it "parses html by simply returning the body" do
153
+ subject.send(:html).should == 'body'
154
+ end
155
+
156
+ it "parses plain text by simply returning the body" do
157
+ subject.send(:plain).should == 'body'
158
+ end
159
+
160
+ it "parses csv with CSV" do
161
+ CSV.should_receive(:parse).with('body')
162
+ subject.send(:csv)
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,631 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe HTTParty::Request do
4
+ before do
5
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml)
6
+ end
7
+
8
+ describe "::NON_RAILS_QUERY_STRING_NORMALIZER" do
9
+ let(:normalizer) { HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER }
10
+
11
+ it "doesn't modify strings" do
12
+ query_string = normalizer["foo=bar&foo=baz"]
13
+ URI.unescape(query_string).should == "foo=bar&foo=baz"
14
+ end
15
+
16
+ context "when the query is an array" do
17
+
18
+ it "doesn't include brackets" do
19
+ query_string = normalizer[{:page => 1, :foo => %w(bar baz)}]
20
+ URI.unescape(query_string).should == "foo=bar&foo=baz&page=1"
21
+ end
22
+
23
+ it "URI encodes array values" do
24
+ query_string = normalizer[{:people => ["Otis Redding", "Bob Marley", "Tim & Jon"], :page => 1, :xyzzy => 3}]
25
+ query_string.should == "page=1&people=Otis%20Redding&people=Bob%20Marley&people=Tim%20%26%20Jon&xyzzy=3"
26
+ end
27
+ end
28
+
29
+ context "when the query is a hash" do
30
+ it "correctly handles nil values" do
31
+ query_string = normalizer[{:page => 1, :per_page => nil}]
32
+ query_string.should == "page=1&per_page"
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "initialization" do
38
+ it "sets parser to HTTParty::Parser" do
39
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
40
+ request.parser.should == HTTParty::Parser
41
+ end
42
+
43
+ it "sets parser to the optional parser" do
44
+ my_parser = lambda {}
45
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', :parser => my_parser)
46
+ request.parser.should == my_parser
47
+ end
48
+
49
+ it "sets connection_adapter to HTTPParty::ConnectionAdapter" do
50
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
51
+ request.connection_adapter.should == HTTParty::ConnectionAdapter
52
+ end
53
+
54
+ it "sets connection_adapter to the optional connection_adapter" do
55
+ my_adapter = lambda {}
56
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', :connection_adapter => my_adapter)
57
+ request.connection_adapter.should == my_adapter
58
+ end
59
+ end
60
+
61
+ describe "#format" do
62
+ context "request yet to be made" do
63
+ it "returns format option" do
64
+ request = HTTParty::Request.new 'get', '/', :format => :xml
65
+ request.format.should == :xml
66
+ end
67
+
68
+ it "returns nil format" do
69
+ request = HTTParty::Request.new 'get', '/'
70
+ request.format.should be_nil
71
+ end
72
+ end
73
+
74
+ context "request has been made" do
75
+ it "returns format option" do
76
+ request = HTTParty::Request.new 'get', '/', :format => :xml
77
+ request.last_response = stub
78
+ request.format.should == :xml
79
+ end
80
+
81
+ it "returns the content-type from the last response when the option is not set" do
82
+ request = HTTParty::Request.new 'get', '/'
83
+ response = stub
84
+ response.should_receive(:[]).with('content-type').and_return('text/json')
85
+ request.last_response = response
86
+ request.format.should == :json
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ context "options" do
93
+ it "should use basic auth when configured" do
94
+ @request.options[:basic_auth] = {:username => 'foobar', :password => 'secret'}
95
+ @request.send(:setup_raw_request)
96
+ @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
97
+ end
98
+
99
+ it "should use digest auth when configured" do
100
+ FakeWeb.register_uri(:get, "http://api.foo.com/v1",
101
+ :www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
102
+
103
+ @request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
104
+ @request.send(:setup_raw_request)
105
+
106
+ raw_request = @request.instance_variable_get(:@raw_request)
107
+ raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
108
+ end
109
+
110
+ it "should use the right http method for digest authentication" do
111
+ @post_request = HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :format => :xml)
112
+ FakeWeb.register_uri(:post, "http://api.foo.com/v1", {})
113
+
114
+ http = @post_request.send(:http)
115
+ @post_request.should_receive(:http).and_return(http)
116
+ http.should_not_receive(:head).and_return({'www-authenticate' => nil})
117
+ @post_request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
118
+ @post_request.send(:setup_raw_request)
119
+ end
120
+ end
121
+
122
+ describe "#uri" do
123
+ context "query strings" do
124
+ it "does not add an empty query string when default_params are blank" do
125
+ @request.options[:default_params] = {}
126
+ @request.uri.query.should be_nil
127
+ end
128
+
129
+ it "respects the query string normalization proc" do
130
+ empty_proc = lambda {|qs| ""}
131
+ @request.options[:query_string_normalizer] = empty_proc
132
+ @request.options[:query] = {:foo => :bar}
133
+ URI.unescape(@request.uri.query).should == ""
134
+ end
135
+
136
+ it "does not append an ampersand when queries are embedded in paths" do
137
+ @request.path = "/path?a=1"
138
+ @request.options[:query] = {}
139
+ @request.uri.query.should == "a=1"
140
+ end
141
+
142
+ it "does not duplicate query string parameters when uri is called twice" do
143
+ @request.options[:query] = {:foo => :bar}
144
+ @request.uri
145
+ @request.uri.query.should == "foo=bar"
146
+ end
147
+
148
+ context "when representing an array" do
149
+ it "returns a Rails style query string" do
150
+ @request.options[:query] = {:foo => %w(bar baz)}
151
+ URI.unescape(@request.uri.query).should == "foo[]=bar&foo[]=baz"
152
+ end
153
+ end
154
+
155
+ end
156
+ end
157
+
158
+ describe "#setup_raw_request" do
159
+ context "when query_string_normalizer is set" do
160
+ it "sets the body to the return value of the proc" do
161
+ @request.options[:query_string_normalizer] = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER
162
+ @request.options[:body] = {:page => 1, :foo => %w(bar baz)}
163
+ @request.send(:setup_raw_request)
164
+ body = @request.instance_variable_get(:@raw_request).body
165
+ URI.unescape(body).should == "foo=bar&foo=baz&page=1"
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'http' do
171
+ it "should get a connection from the connection_adapter" do
172
+ http = Net::HTTP.new('google.com')
173
+ adapter = mock('adapter')
174
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443', :connection_adapter => adapter)
175
+ adapter.should_receive(:call).with(request.uri, request.options).and_return(http)
176
+ request.send(:http).should be http
177
+ end
178
+ end
179
+
180
+ describe '#format_from_mimetype' do
181
+ it 'should handle text/xml' do
182
+ ["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
183
+ @request.send(:format_from_mimetype, ct).should == :xml
184
+ end
185
+ end
186
+
187
+ it 'should handle application/xml' do
188
+ ["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
189
+ @request.send(:format_from_mimetype, ct).should == :xml
190
+ end
191
+ end
192
+
193
+ it 'should handle text/json' do
194
+ ["text/json", "text/json; charset=iso8859-1"].each do |ct|
195
+ @request.send(:format_from_mimetype, ct).should == :json
196
+ end
197
+ end
198
+
199
+ it 'should handle application/json' do
200
+ ["application/json", "application/json; charset=iso8859-1"].each do |ct|
201
+ @request.send(:format_from_mimetype, ct).should == :json
202
+ end
203
+ end
204
+
205
+ it 'should handle text/csv' do
206
+ ["text/csv", "text/csv; charset=iso8859-1"].each do |ct|
207
+ @request.send(:format_from_mimetype, ct).should == :csv
208
+ end
209
+ end
210
+
211
+ it 'should handle application/csv' do
212
+ ["application/csv", "application/csv; charset=iso8859-1"].each do |ct|
213
+ @request.send(:format_from_mimetype, ct).should == :csv
214
+ end
215
+ end
216
+
217
+ it 'should handle text/comma-separated-values' do
218
+ ["text/comma-separated-values", "text/comma-separated-values; charset=iso8859-1"].each do |ct|
219
+ @request.send(:format_from_mimetype, ct).should == :csv
220
+ end
221
+ end
222
+
223
+ it 'should handle text/javascript' do
224
+ ["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
225
+ @request.send(:format_from_mimetype, ct).should == :plain
226
+ end
227
+ end
228
+
229
+ it 'should handle application/javascript' do
230
+ ["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
231
+ @request.send(:format_from_mimetype, ct).should == :plain
232
+ end
233
+ end
234
+
235
+ it "returns nil for an unrecognized mimetype" do
236
+ @request.send(:format_from_mimetype, "application/atom+xml").should be_nil
237
+ end
238
+
239
+ it "returns nil when using a default parser" do
240
+ @request.options[:parser] = lambda {}
241
+ @request.send(:format_from_mimetype, "text/json").should be_nil
242
+ end
243
+ end
244
+
245
+ describe 'parsing responses' do
246
+ it 'should handle xml automatically' do
247
+ xml = %q[<books><book><id>1234</id><name>Foo Bar!</name></book></books>]
248
+ @request.options[:format] = :xml
249
+ @request.send(:parse_response, xml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
250
+ end
251
+
252
+ it 'should handle csv automatically' do
253
+ csv=[%q["id","Name"],%q["1234","Foo Bar!"]].join("\n")
254
+ @request.options[:format] = :csv
255
+ @request.send(:parse_response, csv).should == [["id","Name"],["1234","Foo Bar!"]]
256
+ end
257
+
258
+ it 'should handle json automatically' do
259
+ json = %q[{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}]
260
+ @request.options[:format] = :json
261
+ @request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
262
+ end
263
+
264
+ it "should include any HTTP headers in the returned response" do
265
+ @request.options[:format] = :html
266
+ response = stub_response "Content"
267
+ response.initialize_http_header("key" => "value")
268
+
269
+ @request.perform.headers.should == { "key" => ["value"] }
270
+ end
271
+
272
+ if "".respond_to?(:encoding)
273
+
274
+ it "should process charset in content type properly" do
275
+ response = stub_response "Content"
276
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-8")
277
+ resp = @request.perform
278
+ resp.body.encoding.should == Encoding.find("UTF-8")
279
+ end
280
+
281
+ it "should process charset in content type properly if it has a different case" do
282
+ response = stub_response "Content"
283
+ response.initialize_http_header("Content-Type" => "text/plain;CHARSET = utf-8")
284
+ resp = @request.perform
285
+ resp.body.encoding.should == Encoding.find("UTF-8")
286
+ end
287
+
288
+ it "should process quoted charset in content type properly" do
289
+ response = stub_response "Content"
290
+ response.initialize_http_header("Content-Type" => "text/plain;charset = \"utf-8\"")
291
+ resp = @request.perform
292
+ resp.body.encoding.should == Encoding.find("UTF-8")
293
+ end
294
+
295
+ it "should process utf-16 charset with little endian bom correctly" do
296
+ @request.options[:assume_utf16_is_big_endian] = true
297
+
298
+ response = stub_response "\xFF\xFEC\x00o\x00n\x00t\x00e\x00n\x00t\x00"
299
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
300
+ resp = @request.perform
301
+ resp.body.encoding.should == Encoding.find("UTF-16LE")
302
+ end
303
+
304
+ it "should process utf-16 charset with big endian bom correctly" do
305
+ @request.options[:assume_utf16_is_big_endian] = false
306
+
307
+ response = stub_response "\xFE\xFF\x00C\x00o\x00n\x00t\x00e\x00n\x00t"
308
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
309
+ resp = @request.perform
310
+ resp.body.encoding.should == Encoding.find("UTF-16BE")
311
+ end
312
+
313
+ it "should assume utf-16 little endian if options has been chosen" do
314
+ @request.options[:assume_utf16_is_big_endian] = false
315
+
316
+ response = stub_response "C\x00o\x00n\x00t\x00e\x00n\x00t\x00"
317
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
318
+ resp = @request.perform
319
+ resp.body.encoding.should == Encoding.find("UTF-16LE")
320
+ end
321
+
322
+
323
+ it "should perform no encoding if the charset is not available" do
324
+
325
+ response = stub_response "Content"
326
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-lols")
327
+ resp = @request.perform
328
+ resp.body.should == "Content"
329
+ resp.body.encoding.should == "Content".encoding
330
+ end
331
+
332
+ it "should perform no encoding if the content type is specified but no charset is specified" do
333
+
334
+ response = stub_response "Content"
335
+ response.initialize_http_header("Content-Type" => "text/plain")
336
+ resp = @request.perform
337
+ resp.body.should == "Content"
338
+ resp.body.encoding.should == "Content".encoding
339
+ end
340
+ end
341
+
342
+
343
+ describe 'with non-200 responses' do
344
+ context "3xx responses" do
345
+ it 'returns a valid object for 304 not modified' do
346
+ stub_response '', 304
347
+ resp = @request.perform
348
+ resp.code.should == 304
349
+ resp.body.should == ''
350
+ resp.should be_nil
351
+ end
352
+
353
+ it "redirects if a 300 contains a location header" do
354
+ redirect = stub_response '', 300
355
+ redirect['location'] = 'http://foo.com/foo'
356
+ ok = stub_response('<hash><foo>bar</foo></hash>', 200)
357
+ @http.stub!(:request).and_return(redirect, ok)
358
+ response = @request.perform
359
+ response.request.base_uri.to_s.should == "http://foo.com"
360
+ response.request.path.to_s.should == "http://foo.com/foo"
361
+ response.request.uri.request_uri.should == "/foo"
362
+ response.request.uri.to_s.should == "http://foo.com/foo"
363
+ response.should == {"hash" => {"foo" => "bar"}}
364
+ end
365
+
366
+ it "calls block given to perform with each redirect" do
367
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', :format => :xml)
368
+ FakeWeb.register_uri(:get, "http://test.com/redirect", :status => [300, "REDIRECT"], :location => "http://api.foo.com/v2")
369
+ FakeWeb.register_uri(:get, "http://api.foo.com/v2", :body => "<hash><foo>bar</foo></hash>")
370
+ body = ""
371
+ response = @request.perform { |chunk| body += chunk }
372
+ body.length.should == 27
373
+ end
374
+
375
+ it "redirects if a 300 contains a relative location header" do
376
+ redirect = stub_response '', 300
377
+ redirect['location'] = '/foo/bar'
378
+ ok = stub_response('<hash><foo>bar</foo></hash>', 200)
379
+ @http.stub!(:request).and_return(redirect, ok)
380
+ response = @request.perform
381
+ response.request.base_uri.to_s.should == "http://api.foo.com"
382
+ response.request.path.to_s.should == "/foo/bar"
383
+ response.request.uri.request_uri.should == "/foo/bar"
384
+ response.request.uri.to_s.should == "http://api.foo.com/foo/bar"
385
+ response.should == {"hash" => {"foo" => "bar"}}
386
+ end
387
+
388
+ it "handles multiple redirects and relative location headers on different hosts" do
389
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', :format => :xml)
390
+ FakeWeb.register_uri(:get, "http://test.com/redirect", :status => [300, "REDIRECT"], :location => "http://api.foo.com/v2")
391
+ FakeWeb.register_uri(:get, "http://api.foo.com/v2", :status => [300, "REDIRECT"], :location => "/v3")
392
+ FakeWeb.register_uri(:get, "http://api.foo.com/v3", :body => "<hash><foo>bar</foo></hash>")
393
+ response = @request.perform
394
+ response.request.base_uri.to_s.should == "http://api.foo.com"
395
+ response.request.path.to_s.should == "/v3"
396
+ response.request.uri.request_uri.should == "/v3"
397
+ response.request.uri.to_s.should == "http://api.foo.com/v3"
398
+ response.should == {"hash" => {"foo" => "bar"}}
399
+ end
400
+
401
+ it "returns the HTTParty::Response when the 300 does not contain a location header" do
402
+ stub_response '', 300
403
+ HTTParty::Response.should === @request.perform
404
+ end
405
+ end
406
+
407
+ it 'should return a valid object for 4xx response' do
408
+ stub_response '<foo><bar>yes</bar></foo>', 401
409
+ resp = @request.perform
410
+ resp.code.should == 401
411
+ resp.body.should == "<foo><bar>yes</bar></foo>"
412
+ resp['foo']['bar'].should == "yes"
413
+ end
414
+
415
+ it 'should return a valid object for 5xx response' do
416
+ stub_response '<foo><bar>error</bar></foo>', 500
417
+ resp = @request.perform
418
+ resp.code.should == 500
419
+ resp.body.should == "<foo><bar>error</bar></foo>"
420
+ resp['foo']['bar'].should == "error"
421
+ end
422
+
423
+ it "parses response lazily so codes can be checked prior" do
424
+ stub_response 'not xml', 500
425
+ @request.options[:format] = :xml
426
+ lambda {
427
+ response = @request.perform
428
+ response.code.should == 500
429
+ response.body.should == 'not xml'
430
+ }.should_not raise_error
431
+ end
432
+ end
433
+ end
434
+
435
+ it "should not attempt to parse empty responses" do
436
+ [204, 304].each do |code|
437
+ stub_response "", code
438
+
439
+ @request.options[:format] = :xml
440
+ @request.perform.should be_nil
441
+ end
442
+ end
443
+
444
+ it "should not fail for missing mime type" do
445
+ stub_response "Content for you"
446
+ @request.options[:format] = :html
447
+ @request.perform.should == 'Content for you'
448
+ end
449
+
450
+ describe "a request that redirects" do
451
+ before(:each) do
452
+ @redirect = stub_response("", 302)
453
+ @redirect['location'] = '/foo'
454
+
455
+ @ok = stub_response('<hash><foo>bar</foo></hash>', 200)
456
+ end
457
+
458
+ describe "once" do
459
+ before(:each) do
460
+ @http.stub!(:request).and_return(@redirect, @ok)
461
+ end
462
+
463
+ it "should be handled by GET transparently" do
464
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
465
+ end
466
+
467
+ it "should be handled by POST transparently" do
468
+ @request.http_method = Net::HTTP::Post
469
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
470
+ end
471
+
472
+ it "should be handled by DELETE transparently" do
473
+ @request.http_method = Net::HTTP::Delete
474
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
475
+ end
476
+
477
+ it "should be handled by MOVE transparently" do
478
+ @request.http_method = Net::HTTP::Move
479
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
480
+ end
481
+
482
+ it "should be handled by COPY transparently" do
483
+ @request.http_method = Net::HTTP::Copy
484
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
485
+ end
486
+
487
+ it "should be handled by PATCH transparently" do
488
+ @request.http_method = Net::HTTP::Patch
489
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
490
+ end
491
+
492
+ it "should be handled by PUT transparently" do
493
+ @request.http_method = Net::HTTP::Put
494
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
495
+ end
496
+
497
+ it "should be handled by HEAD transparently" do
498
+ @request.http_method = Net::HTTP::Head
499
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
500
+ end
501
+
502
+ it "should be handled by OPTIONS transparently" do
503
+ @request.http_method = Net::HTTP::Options
504
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
505
+ end
506
+
507
+ it "should keep track of cookies between redirects" do
508
+ @redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
509
+ @request.perform
510
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
511
+ @request.options[:headers]['Cookie'].should match(/name=value/)
512
+ end
513
+
514
+ it 'should update cookies with rediects' do
515
+ @request.options[:headers] = {'Cookie'=> 'foo=bar;'}
516
+ @redirect['Set-Cookie'] = 'foo=tar;'
517
+ @request.perform
518
+ @request.options[:headers]['Cookie'].should match(/foo=tar/)
519
+ end
520
+
521
+ it 'should keep cookies between rediects' do
522
+ @request.options[:headers] = {'Cookie'=> 'keep=me'}
523
+ @redirect['Set-Cookie'] = 'foo=tar;'
524
+ @request.perform
525
+ @request.options[:headers]['Cookie'].should match(/keep=me/)
526
+ end
527
+
528
+ it "should handle multiple Set-Cookie headers between redirects" do
529
+ @redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
530
+ @redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
531
+ @request.perform
532
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
533
+ @request.options[:headers]['Cookie'].should match(/name=value/)
534
+ @request.options[:headers]['Cookie'].should match(/one=1/)
535
+ @request.options[:headers]['Cookie'].should match(/two=2/)
536
+ end
537
+
538
+ it 'should make resulting request a get request if it not already' do
539
+ @request.http_method = Net::HTTP::Delete
540
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
541
+ @request.http_method.should == Net::HTTP::Get
542
+ end
543
+
544
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
545
+ @request.options[:maintain_method_across_redirects] = true
546
+ @request.http_method = Net::HTTP::Delete
547
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
548
+ @request.http_method.should == Net::HTTP::Delete
549
+ end
550
+
551
+ it 'should log the redirection' do
552
+ logger_double = double
553
+ logger_double.should_receive(:info).twice
554
+ @request.options[:logger] = logger_double
555
+ @request.perform
556
+ end
557
+ end
558
+
559
+ describe "infinitely" do
560
+ before(:each) do
561
+ @http.stub!(:request).and_return(@redirect)
562
+ end
563
+
564
+ it "should raise an exception" do
565
+ lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
566
+ end
567
+ end
568
+ end
569
+
570
+ describe "#handle_deflation" do
571
+ context "context-encoding" do
572
+ before do
573
+ @request.options[:format] = :html
574
+ @last_response = mock()
575
+ @last_response.stub!(:body).and_return('')
576
+ end
577
+
578
+ it "should inflate the gzipped body with content-encoding: gzip" do
579
+ @last_response.stub!(:[]).with("content-encoding").and_return("gzip")
580
+ @request.stub!(:last_response).and_return(@last_response)
581
+ Zlib::GzipReader.should_receive(:new).and_return(StringIO.new(''))
582
+ @request.last_response.should_receive(:delete).with('content-encoding')
583
+ @request.send(:handle_deflation)
584
+ end
585
+
586
+ it "should inflate the gzipped body with content-encoding: x-gzip" do
587
+ @last_response.stub!(:[]).with("content-encoding").and_return("x-gzip")
588
+ @request.stub!(:last_response).and_return(@last_response)
589
+ Zlib::GzipReader.should_receive(:new).and_return(StringIO.new(''))
590
+ @request.last_response.should_receive(:delete).with('content-encoding')
591
+ @request.send(:handle_deflation)
592
+ end
593
+
594
+ it "should inflate the deflated body" do
595
+ @last_response.stub!(:[]).with("content-encoding").and_return("deflate")
596
+ @request.stub!(:last_response).and_return(@last_response)
597
+ Zlib::Inflate.should_receive(:inflate).and_return('')
598
+ @request.last_response.should_receive(:delete).with('content-encoding')
599
+ @request.send(:handle_deflation)
600
+ end
601
+ end
602
+ end
603
+
604
+ context "with POST http method" do
605
+ it "should raise argument error if query is not a hash" do
606
+ lambda {
607
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :format => :xml, :query => 'astring').perform
608
+ }.should raise_error(ArgumentError)
609
+ end
610
+ end
611
+
612
+ describe "argument validation" do
613
+ it "should raise argument error if basic_auth and digest_auth are both present" do
614
+ lambda {
615
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
616
+ }.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
617
+ end
618
+
619
+ it "should raise argument error if basic_auth is not a hash" do
620
+ lambda {
621
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
622
+ }.should raise_error(ArgumentError, ":basic_auth must be a hash")
623
+ end
624
+
625
+ it "should raise argument error if digest_auth is not a hash" do
626
+ lambda {
627
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
628
+ }.should raise_error(ArgumentError, ":digest_auth must be a hash")
629
+ end
630
+ end
631
+ end