openlogic-resourceful 1.2.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 (47) hide show
  1. data/History.txt +45 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest +46 -0
  4. data/README.markdown +92 -0
  5. data/Rakefile +91 -0
  6. data/lib/resourceful.rb +27 -0
  7. data/lib/resourceful/abstract_form_data.rb +30 -0
  8. data/lib/resourceful/authentication_manager.rb +107 -0
  9. data/lib/resourceful/cache_manager.rb +242 -0
  10. data/lib/resourceful/exceptions.rb +34 -0
  11. data/lib/resourceful/header.rb +355 -0
  12. data/lib/resourceful/http_accessor.rb +103 -0
  13. data/lib/resourceful/memcache_cache_manager.rb +75 -0
  14. data/lib/resourceful/multipart_form_data.rb +46 -0
  15. data/lib/resourceful/net_http_adapter.rb +84 -0
  16. data/lib/resourceful/promiscuous_basic_authenticator.rb +18 -0
  17. data/lib/resourceful/request.rb +235 -0
  18. data/lib/resourceful/resource.rb +179 -0
  19. data/lib/resourceful/response.rb +221 -0
  20. data/lib/resourceful/simple.rb +36 -0
  21. data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
  22. data/lib/resourceful/urlencoded_form_data.rb +19 -0
  23. data/lib/resourceful/util.rb +6 -0
  24. data/openlogic-resourceful.gemspec +51 -0
  25. data/resourceful.gemspec +51 -0
  26. data/spec/acceptance/authorization_spec.rb +16 -0
  27. data/spec/acceptance/caching_spec.rb +190 -0
  28. data/spec/acceptance/header_spec.rb +24 -0
  29. data/spec/acceptance/redirecting_spec.rb +12 -0
  30. data/spec/acceptance/resource_spec.rb +84 -0
  31. data/spec/acceptance/resourceful_spec.rb +56 -0
  32. data/spec/acceptance_shared_specs.rb +44 -0
  33. data/spec/caching_spec.rb +89 -0
  34. data/spec/old_acceptance_specs.rb +378 -0
  35. data/spec/resourceful/header_spec.rb +153 -0
  36. data/spec/resourceful/http_accessor_spec.rb +56 -0
  37. data/spec/resourceful/multipart_form_data_spec.rb +84 -0
  38. data/spec/resourceful/promiscuous_basic_authenticator_spec.rb +30 -0
  39. data/spec/resourceful/resource_spec.rb +20 -0
  40. data/spec/resourceful/response_spec.rb +51 -0
  41. data/spec/resourceful/urlencoded_form_data_spec.rb +64 -0
  42. data/spec/resourceful_spec.rb +79 -0
  43. data/spec/simple_sinatra_server.rb +74 -0
  44. data/spec/simple_sinatra_server_spec.rb +98 -0
  45. data/spec/spec.opts +3 -0
  46. data/spec/spec_helper.rb +31 -0
  47. metadata +192 -0
@@ -0,0 +1,153 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper.rb"
2
+
3
+ module Resourceful
4
+ describe Header do
5
+ def self.should_support_header(name)
6
+ const_name = name.upcase.gsub('-', '_')
7
+ meth_name = name.downcase.gsub('-', '_')
8
+
9
+ eval <<-RUBY
10
+ it "should have constant `#{const_name}` for header `#{name}`" do
11
+ Resourceful::Header::#{const_name}.should == '#{name}'
12
+ end
13
+
14
+ it "should have accessor method `#{meth_name}` for header `#{name}`" do
15
+ Resourceful::Header.new.should respond_to(:#{meth_name})
16
+ end
17
+
18
+ RUBY
19
+ end
20
+
21
+ should_support_header('Accept')
22
+ should_support_header('Accept-Charset')
23
+ should_support_header('Accept-Encoding')
24
+ should_support_header('Accept-Language')
25
+ should_support_header('Accept-Ranges')
26
+ should_support_header('Age')
27
+ should_support_header('Allow')
28
+ should_support_header('Authorization')
29
+ should_support_header('Cache-Control')
30
+ should_support_header('Connection')
31
+ should_support_header('Content-Encoding')
32
+ should_support_header('Content-Language')
33
+ should_support_header('Content-Length')
34
+ should_support_header('Content-Location')
35
+ should_support_header('Content-MD5')
36
+ should_support_header('Content-Range')
37
+ should_support_header('Content-Type')
38
+ should_support_header('Date')
39
+ should_support_header('ETag')
40
+ should_support_header('Expect')
41
+ should_support_header('Expires')
42
+ should_support_header('From')
43
+ should_support_header('Host')
44
+ should_support_header('If-Match')
45
+ should_support_header('If-Modified-Since')
46
+ should_support_header('If-None-Match')
47
+ should_support_header('If-Range')
48
+ should_support_header('If-Unmodified-Since')
49
+ should_support_header('Keep-Alive')
50
+ should_support_header('Last-Modified')
51
+ should_support_header('Location')
52
+ should_support_header('Max-Forwards')
53
+ should_support_header('Pragma')
54
+ should_support_header('Proxy-Authenticate')
55
+ should_support_header('Proxy-Authorization')
56
+ should_support_header('Range')
57
+ should_support_header('Referer')
58
+ should_support_header('Retry-After')
59
+ should_support_header('Server')
60
+ should_support_header('TE')
61
+ should_support_header('Trailer')
62
+ should_support_header('Transfer-Encoding')
63
+ should_support_header('Upgrade')
64
+ should_support_header('User-Agent')
65
+ should_support_header('Vary')
66
+ should_support_header('Via')
67
+ should_support_header('Warning')
68
+ should_support_header('WWW-Authenticate')
69
+
70
+
71
+ it "should be instantiatable w/ single valued header fields" do
72
+ Header.new('Host' => 'foo.example').
73
+ host.should eql('foo.example')
74
+ end
75
+
76
+ it "should gracefully handle repeated values for single valued header fields" do
77
+ lambda {
78
+ Header.new('Host' => ['foo.example', 'bar.example'])
79
+ }.should raise_error(ArgumentError, 'Host field may only have one value')
80
+ end
81
+
82
+ it "should provide #each_fields to iterate through all header fields and values as strings" do
83
+ field_names = []
84
+ Header.new('Accept' => "this", :content_type => "that", 'pragma' => 'test').each_field do |fname, _|
85
+ field_names << fname
86
+ end
87
+
88
+ field_names.should include('Accept')
89
+ field_names.should include('Content-Type')
90
+ field_names.should include('Pragma')
91
+ field_names.should have(3).items
92
+ end
93
+
94
+ it "should provide #to_hash as a way to dump the header fields" do
95
+ Header.new('Accept' => "this", :content_type => "that", 'date' => 'today').to_hash.tap do |h|
96
+ h.should have_pair('Accept', ['this'])
97
+ h.should have_pair('Content-Type', 'that')
98
+ h.should have_pair('Date', 'today')
99
+ end
100
+ end
101
+
102
+ it "should provide a list of hop-by-hop fields" do
103
+ Header.header_field('X-Hop-By-Hop-Header', :hop_by_hop => true)
104
+ Header.hop_by_hop_fields.should include('X-Hop-By-Hop-Header')
105
+ end
106
+
107
+ it "should provide a list of not modified fields" do
108
+ Header.header_field('X-Dont-Modify-Me', :modifiable => false)
109
+ Header.non_modifiable_fields.should include('X-Dont-Modify-Me')
110
+ end
111
+
112
+ describe "multi-valued fields" do
113
+ it "should be instantiatable w/ repeated multi-valued header fields" do
114
+ Header.new('Accept' => ['application/foo', 'application/bar']).
115
+ accept.should eql(['application/foo', 'application/bar'])
116
+ end
117
+
118
+ it "should be instantiatable w/ repeated multi-valued header fields w/ multiple values" do
119
+ Header.new('Accept' => ['application/foo, application/bar', 'text/plain']).
120
+ accept.should eql(['application/foo', 'application/bar', 'text/plain'])
121
+ end
122
+
123
+ it "should be instantiatable w/ multi-valued header fields w/ multiple values" do
124
+ Header.new('Accept' => 'application/foo, application/bar').
125
+ accept.should eql(['application/foo', 'application/bar'])
126
+ end
127
+
128
+ it "should be instantiatable w/ multi-valued header fields w/ one value" do
129
+ Header.new('Accept' => 'application/foo').
130
+ accept.should eql(['application/foo'])
131
+ end
132
+
133
+ it "should provide values to #each_field as a comma separated string" do
134
+ Header.new('Accept' => ['this', 'that']).each_field do |fname, fval|
135
+ fval.should == 'this, that'
136
+ end
137
+ end
138
+
139
+ it "should provide #each as a way to iterate through fields as w/ higher level values" do
140
+ Header.new('Accept' => ['this', 'that']).each do |fname, fval|
141
+ fval.should == ['this', 'that']
142
+ end
143
+ end
144
+ end
145
+
146
+ Spec::Matchers.define :have_pair do |name, value|
147
+ match do |header_hash|
148
+ header_hash.has_key?(name)
149
+ header_hash[name] == value
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ require "resourceful/http_accessor"
4
+
5
+ module Resourceful
6
+ describe HttpAccessor do
7
+ describe "instantiation" do
8
+ it "should accept logger option" do
9
+ test_logger = stub('logger', :debug => false)
10
+ ha = HttpAccessor.new(:logger => test_logger)
11
+ ha.logger.should equal(test_logger)
12
+ end
13
+
14
+ it "should accept array user_agent option" do
15
+ ha = HttpAccessor.new(:user_agent => ['foo/3.2', 'bar/1.0'])
16
+ ha.user_agent_string.should match(/^foo\/3.2 bar\/1.0 Resourceful/)
17
+ end
18
+
19
+ it "should accept string user_agent option" do
20
+ ha = HttpAccessor.new(:user_agent => 'foo')
21
+ ha.user_agent_string.should match(/^foo Resourceful/)
22
+ end
23
+
24
+ it "should accept cache_manager option" do
25
+ test_cache_manager = stub('cache_manager', :debug => false)
26
+ ha = HttpAccessor.new(:cache_manager => test_cache_manager)
27
+ ha.cache_manager.should equal(test_cache_manager)
28
+ end
29
+
30
+ it "should accept http_adapter option" do
31
+ test_http_adapter = stub('http_adapter', :debug => false)
32
+ ha = HttpAccessor.new(:http_adapter => test_http_adapter)
33
+ ha.http_adapter.should equal(test_http_adapter)
34
+ end
35
+
36
+ it "should accept authenticator option" do
37
+ test_authenticator = stub('authenticator', :debug => false)
38
+ ha = HttpAccessor.new(:authenticator => test_authenticator)
39
+ # cannot really be tested safely so we just rely on the fact that the option was accepted
40
+ end
41
+
42
+ it "should accept authenticators option" do
43
+ test_authenticator1 = stub('authenticator1', :debug => false)
44
+ test_authenticator2 = stub('authenticator2', :debug => false)
45
+ ha = HttpAccessor.new(:authenticator => [test_authenticator1, test_authenticator2])
46
+ # cannot really be tested safely so we just rely on the fact that the option was accepted
47
+ end
48
+
49
+ it "should reject unrecognized options" do
50
+ lambda {
51
+ HttpAccessor.new(:not_a_valid_option => "this")
52
+ }.should raise_error(ArgumentError)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+ require 'tempfile'
3
+ require "resourceful/multipart_form_data.rb"
4
+
5
+ describe Resourceful::MultipartFormData do
6
+
7
+ before do
8
+ @form_data = Resourceful::MultipartFormData.new
9
+ end
10
+
11
+ it "should know its content-type" do
12
+ @form_data.content_type.should match(/^multipart\/form-data/i)
13
+ end
14
+
15
+ it "should know its boundary string" do
16
+ @form_data.content_type.should match(/; boundary=[0-9A-Za-z]{10,}/i)
17
+ end
18
+
19
+ it "should allow simple parameters to be added" do
20
+ @form_data.add(:foo, "testing")
21
+ end
22
+
23
+ describe "with multiple simple parameters" do
24
+ before do
25
+ @form_data.add('foo', 'bar')
26
+ @form_data.add('baz', 'this')
27
+ end
28
+
29
+ it "should render a multipart form-data document when #read is called" do
30
+ boundary = /boundary=(\w+)/.match(@form_data.content_type)[1]
31
+ @form_data.read.should eql(<<MPFD[0..-2])
32
+ --#{boundary}\r
33
+ Content-Disposition: form-data; name="foo"\r
34
+ \r
35
+ bar\r
36
+ --#{boundary}\r
37
+ Content-Disposition: form-data; name="baz"\r
38
+ \r
39
+ this\r
40
+ --#{boundary}--
41
+ MPFD
42
+ end
43
+
44
+ it "should be rewindable" do
45
+ first_read = @form_data.read
46
+ @form_data.rewind
47
+ @form_data.read.should eql(first_read)
48
+ end
49
+
50
+ it "should add file parameters to be added" do
51
+ Tempfile.open('resourceful-post-file-tests') do |file_to_upload|
52
+ file_to_upload << "This is a test"
53
+ file_to_upload.flush
54
+
55
+ @form_data.add_file(:foo, file_to_upload.path)
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "with file parameter" do
61
+ before do
62
+ @file_to_upload = Tempfile.new('resourceful-post-file-tests')
63
+ @file_to_upload << "This is a test"
64
+ @file_to_upload.flush
65
+
66
+ @form_data.add_file(:foo, @file_to_upload.path)
67
+ end
68
+
69
+ it "should render a multipart form-data document when #read is called" do
70
+ boundary = /boundary=(\w+)/.match(@form_data.content_type)[1]
71
+ @form_data.read.should eql(<<MPFD[0..-2])
72
+ --#{boundary}\r
73
+ Content-Disposition: form-data; name="foo"; filename="#{File.basename(@file_to_upload.path)}"\r
74
+ Content-Type: application/octet-stream\r
75
+ \r
76
+ This is a test\r
77
+ --#{boundary}--
78
+ MPFD
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+
@@ -0,0 +1,30 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ describe Resourceful::PromiscuousBasicAuthenticator do
4
+ before do
5
+ @authenticator = Resourceful::PromiscuousBasicAuthenticator.new('jim', 'mypasswd')
6
+ end
7
+
8
+ it "should always claim to be valid for a challenge response" do
9
+ challenge_resp = mock("a challenge response")
10
+ @authenticator.valid_for?(challenge_resp).should eql(true)
11
+ end
12
+
13
+ it "should always claim to handle any request" do
14
+ a_req = mock("a request")
15
+ @authenticator.valid_for?(a_req).should eql(true)
16
+ end
17
+
18
+ it "be creatable with just a username and password" do
19
+ Resourceful::PromiscuousBasicAuthenticator.new('jim', 'mypasswd').should be_instance_of(Resourceful::PromiscuousBasicAuthenticator)
20
+ end
21
+
22
+ it "add credentials to request" do
23
+ header = mock('header')
24
+ header.should_receive(:[]=).with("Authorization", "Basic amltOm15cGFzc3dk")
25
+ a_req = mock("a request", :header => header)
26
+
27
+ @authenticator.add_credentials_to(a_req)
28
+ end
29
+
30
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ module Resourceful
4
+ describe Resource do
5
+ before do
6
+ @http_adapter = stub(:http_adapter)
7
+ http = Resourceful::HttpAccessor.new(:http_adapter => @http_adapter)
8
+ @resource = http.resource('http://foo.example')
9
+ end
10
+
11
+ describe "POSTing" do
12
+ it "should use bodies content type as the request content-type if it is known" do
13
+ @http_adapter.should_receive(:make_request).with(anything, anything, anything, hash_including('Content-Type' => 'application/x-special-type')).and_return([200, {}, ""])
14
+ body = stub(:body, :content_type => 'application/x-special-type', :read => "hello there")
15
+ @resource.post(body)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ require 'resourceful/response'
4
+ require 'resourceful/header'
5
+
6
+ module Resourceful
7
+ describe Response do
8
+
9
+ it "should know when it is expired" do
10
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=2', 'Date' => (Time.now - 2).httpdate), nil)
11
+ resp.request_time = Time.now
12
+
13
+ resp.expired?.should be_true
14
+ end
15
+
16
+ it "should know when it is not expired" do
17
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => Time.now.httpdate), nil)
18
+ resp.request_time = Time.now
19
+
20
+ resp.expired?.should be_false
21
+ end
22
+
23
+ it "know when it is stale due to expiration" do
24
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => (Time.now - 2).httpdate), nil)
25
+ resp.request_time = Time.now
26
+
27
+ resp.stale?.should be_true
28
+ end
29
+
30
+ it "know when it is stale due to no-cache" do
31
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'no-cache', 'Date' => Time.now.httpdate), nil)
32
+ resp.request_time = Time.now
33
+
34
+ resp.stale?.should be_true
35
+ end
36
+
37
+ it "know when it is stale due to must-revalidate" do
38
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'must-revalidate', 'Date' => Time.now.httpdate), nil)
39
+ resp.request_time = Time.now
40
+
41
+ resp.stale?.should be_true
42
+ end
43
+
44
+ it "know when it is not stale" do
45
+ resp = Response.new(nil, nil, Header.new('Cache-Control' => 'max-age=1', 'Date' => Time.now.httpdate), nil)
46
+ resp.request_time = Time.now
47
+
48
+ resp.stale?.should be_false
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+ require 'tempfile'
3
+ require "resourceful/urlencoded_form_data.rb"
4
+
5
+ describe Resourceful::UrlencodedFormData do
6
+
7
+ before do
8
+ @form_data = Resourceful::UrlencodedFormData.new
9
+ end
10
+
11
+ it "should know its content-type" do
12
+ @form_data.content_type.should match(/^application\/x-www-form-urlencoded$/i)
13
+ end
14
+
15
+ describe "instantiation" do
16
+ it "should be creatable with hash" do
17
+ Resourceful::UrlencodedFormData.new(:foo => 'testing').read.should eql("foo=testing")
18
+ end
19
+ end
20
+
21
+ it "should allow simple parameters to be added" do
22
+ @form_data.add(:foo, "testing")
23
+ end
24
+
25
+ describe "with multiple items" do
26
+ before do
27
+ @form_data.add('foo', 'bar')
28
+ @form_data.add('baz', 'this')
29
+ end
30
+
31
+ it "should render itself correctly" do
32
+ @form_data.read.should eql("foo=bar&baz=this")
33
+ end
34
+
35
+ it "should be rewindable" do
36
+ first = @form_data.read
37
+ @form_data.rewind
38
+
39
+ @form_data.read.should eql(first)
40
+ end
41
+ end
42
+
43
+ describe "with unsafe characters in name" do
44
+ before do
45
+ @form_data.add('foo=bar', 'this')
46
+ end
47
+
48
+ it "should render itself correctly" do
49
+ @form_data.read.should eql("foo%3Dbar=this")
50
+ end
51
+ end
52
+
53
+
54
+ describe "with unsafe characters in value" do
55
+ before do
56
+ @form_data.add('foo', 'this & that')
57
+ end
58
+
59
+ it "should render itself correctly" do
60
+ @form_data.read.should eql("foo=this+%26+that")
61
+ end
62
+ end
63
+
64
+ end