openlogic-resourceful 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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