paul-resourceful 0.2.3

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 (35) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/Manifest.txt +34 -0
  3. data/README.markdown +86 -0
  4. data/Rakefile +14 -0
  5. data/lib/resourceful.rb +29 -0
  6. data/lib/resourceful/authentication_manager.rb +107 -0
  7. data/lib/resourceful/cache_manager.rb +174 -0
  8. data/lib/resourceful/header.rb +31 -0
  9. data/lib/resourceful/http_accessor.rb +85 -0
  10. data/lib/resourceful/net_http_adapter.rb +60 -0
  11. data/lib/resourceful/options_interpreter.rb +78 -0
  12. data/lib/resourceful/request.rb +63 -0
  13. data/lib/resourceful/resource.rb +266 -0
  14. data/lib/resourceful/response.rb +175 -0
  15. data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
  16. data/lib/resourceful/util.rb +6 -0
  17. data/lib/resourceful/version.rb +1 -0
  18. data/resourceful.gemspec +30 -0
  19. data/spec/acceptance_shared_specs.rb +49 -0
  20. data/spec/acceptance_spec.rb +408 -0
  21. data/spec/resourceful/authentication_manager_spec.rb +249 -0
  22. data/spec/resourceful/cache_manager_spec.rb +211 -0
  23. data/spec/resourceful/header_spec.rb +38 -0
  24. data/spec/resourceful/http_accessor_spec.rb +125 -0
  25. data/spec/resourceful/net_http_adapter_spec.rb +96 -0
  26. data/spec/resourceful/options_interpreter_spec.rb +94 -0
  27. data/spec/resourceful/request_spec.rb +186 -0
  28. data/spec/resourceful/resource_spec.rb +600 -0
  29. data/spec/resourceful/response_spec.rb +238 -0
  30. data/spec/resourceful/stubbed_resource_proxy_spec.rb +58 -0
  31. data/spec/simple_http_server_shared_spec.rb +160 -0
  32. data/spec/simple_http_server_shared_spec_spec.rb +212 -0
  33. data/spec/spec.opts +3 -0
  34. data/spec/spec_helper.rb +14 -0
  35. metadata +98 -0
@@ -0,0 +1,38 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+
4
+ require 'resourceful/header'
5
+
6
+ describe Resourceful::Header do
7
+
8
+ it "should capitalize on all accesses" do
9
+ h = Resourceful::Header.new("foo" => "bar")
10
+ h["foo"].should == "bar"
11
+ h["Foo"].should == "bar"
12
+ h["FOO"].should == "bar"
13
+
14
+ h.to_hash.should == {"Foo" => "bar"}
15
+
16
+ h["bar-zzle"] = "quux"
17
+
18
+ h.to_hash.should == {"Foo" => "bar", "Bar-Zzle" => "quux"}
19
+ end
20
+
21
+ it "should capitalize correctly" do
22
+ h = Resourceful::Header.new
23
+
24
+ h.capitalize("foo").should == "Foo"
25
+ h.capitalize("foo-bar").should == "Foo-Bar"
26
+ h.capitalize("foo_bar").should == "Foo-Bar"
27
+ h.capitalize("foo bar").should == "Foo Bar"
28
+ h.capitalize("foo-bar-quux").should == "Foo-Bar-Quux"
29
+ h.capitalize("foo-bar-2quux").should == "Foo-Bar-2quux"
30
+ end
31
+
32
+ it "should be converted to real Hash" do
33
+ h = Resourceful::Header.new("foo" => "bar")
34
+ h.to_hash.should be_instance_of(Hash)
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,125 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+
4
+ require 'resourceful/http_accessor'
5
+
6
+ describe Resourceful::HttpAccessor, 'init' do
7
+
8
+ it 'should be instantiatable' do
9
+ Resourceful::HttpAccessor.new().should be_instance_of(Resourceful::HttpAccessor)
10
+ end
11
+
12
+ it 'should accept logger to new' do
13
+ ha = Resourceful::HttpAccessor.new(:logger => (l = stub('logger')))
14
+
15
+ ha.logger.should == l
16
+ end
17
+
18
+ it 'should provide logger object even when no logger is specified' do
19
+ ha = Resourceful::HttpAccessor.new()
20
+
21
+ ha.logger.should be_instance_of(Resourceful::BitBucketLogger)
22
+ end
23
+
24
+ it 'should raise arg error if unrecognized options are passed' do
25
+ lambda {
26
+ ha = Resourceful::HttpAccessor.new(:foo => 'foo', :bar => 'bar')
27
+ }.should raise_error(ArgumentError, /Unrecognized options: (foo, bar)|(bar, foo)/)
28
+ end
29
+
30
+ it 'should allow an additional user agent token to be passed at init' do
31
+ Resourceful::HttpAccessor.new(:user_agent => "Super/3000").tap do |ha|
32
+ ha.user_agent_string.should match(%r{^Super/3000})
33
+ end
34
+ end
35
+
36
+ it 'should allow multiple additional user agent tokens to be passed at init' do
37
+ Resourceful::HttpAccessor.new(:user_agent => ["Super/3000", "Duper/2.1"]).tap do |ha|
38
+ ha.user_agent_string.should match(%r{^Super/3000 Duper/2\.1 })
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ describe Resourceful::HttpAccessor do
45
+ before do
46
+ @logger = stub('logger')
47
+ @accessor = Resourceful::HttpAccessor.new(:logger => @logger)
48
+ @auth_manager = mock('authentication_manager')
49
+ Resourceful::AuthenticationManager.stub!(:new).and_return(@auth_manager)
50
+
51
+ @resource = mock('resource')
52
+ Resourceful::Resource.stub!(:new).and_return(@resource)
53
+ end
54
+
55
+ it 'should have user agent string w/ just resourceful token by default' do
56
+ @accessor.user_agent_string.should == "Resourceful/#{RESOURCEFUL_VERSION}(Ruby/#{RUBY_VERSION})"
57
+ end
58
+
59
+ it 'should add additional user agent tokens to beginning of user agent string' do
60
+ @accessor.user_agent_tokens << 'FooBar/3000(special-version)'
61
+
62
+ @accessor.user_agent_string.should match(%r{^FooBar\/3000\(special-version\) Resourceful/})
63
+ end
64
+
65
+ it 'should allow a logger to be specified' do
66
+ l = stub('logger')
67
+
68
+ @accessor.logger = l
69
+ @accessor.logger.should == l
70
+ end
71
+
72
+ it 'should allow a logger to be removed' do
73
+ l = stub('logger')
74
+
75
+ @accessor.logger = l
76
+ @accessor.logger = nil
77
+ @accessor.logger.should be_nil
78
+ end
79
+
80
+ it 'should be able to return a particular resource (#[])' do
81
+ @accessor['http://www.example/'].should == @resource
82
+ end
83
+
84
+ it 'should create resource if it does not already exist (#[])' do
85
+ Resourceful::Resource.should_receive(:new).and_return(stub('resource'))
86
+ @accessor['http://www.example/previously-unused-uri']
87
+ end
88
+
89
+ it 'should pass uri to resource upon creation (#[])' do
90
+ Resourceful::Resource.should_receive(:new).with(anything, 'http://www.example/previously-unused-uri', anything).
91
+ and_return(stub('resource'))
92
+ @accessor['http://www.example/previously-unused-uri']
93
+ end
94
+
95
+ it 'should pass owning accessor to resource upon creation (#[])' do
96
+ Resourceful::Resource.should_receive(:new).with(@accessor, anything, anything).and_return(stub('resource'))
97
+ @accessor['http://www.example/previously-unused-uri']
98
+ end
99
+
100
+ it 'should be able to return a particular resource (#resource)' do
101
+ @accessor.resource('http://www.example/').should == @resource
102
+ end
103
+
104
+ it 'should create resource if it does not already exist (#resource)' do
105
+ Resourceful::Resource.should_receive(:new).and_return(stub('resource'))
106
+ @accessor.resource('http://www.example/previously-unused-uri')
107
+ end
108
+
109
+ it 'should pass owning accessor to resource upon creation (#[])' do
110
+ Resourceful::Resource.should_receive(:new).with(@accessor, anything, anything).and_return(stub('resource'))
111
+ @accessor.resource('http://www.example/previously-unused-uri')
112
+ end
113
+
114
+ it 'should pass uri to resource upon creation (#resource)' do
115
+ Resourceful::Resource.should_receive(:new).with(anything, 'http://www.example/previously-unused-uri', anything).
116
+ and_return(stub('resource'))
117
+ @accessor.resource('http://www.example/previously-unused-uri')
118
+ end
119
+
120
+ it 'should pass additional options to resource upon creation' do
121
+ Resourceful::Resource.should_receive(:new).with(anything, anything, :foo => :bar).and_return(stub('resource'))
122
+ @accessor.resource('http://example.com/', :foo => :bar)
123
+ end
124
+
125
+ end
@@ -0,0 +1,96 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+
4
+ require 'resourceful/net_http_adapter'
5
+
6
+ describe Resourceful::NetHttpAdapter do
7
+ describe '#make_request (mocked)' do
8
+ it 'should enable ssl on the connection' do
9
+ resp = stub('http_response', :code => 200, :header => {}, :body => "hello")
10
+ conn = stub('http_conn', :request => resp, :finish => nil)
11
+ Net::HTTP.should_receive(:new).and_return(conn)
12
+ conn.should_receive(:use_ssl=).with(true).ordered
13
+ conn.should_receive(:start).ordered
14
+
15
+ Resourceful::NetHttpAdapter.make_request(:get, 'https://localhost:3000/get')
16
+ end
17
+ end
18
+ end
19
+
20
+ describe Resourceful::NetHttpAdapter do
21
+ it_should_behave_like 'simple http server'
22
+
23
+
24
+ describe '#make_request' do
25
+ before do
26
+ @response = Resourceful::NetHttpAdapter.make_request(:get, 'http://localhost:3000/get')
27
+ end
28
+
29
+ describe 'response' do
30
+ it 'should be an array' do
31
+ @response.should be_instance_of(Array)
32
+ end
33
+
34
+ it 'should have the numeric response code as the first element' do
35
+ code = @response[0]
36
+ code.should be_instance_of(Fixnum)
37
+ code.should == 200
38
+ end
39
+
40
+ it 'should have the Header as the second element' do
41
+ header = @response[1]
42
+ header.should be_instance_of(Resourceful::Header)
43
+ header['content-type'].should == ['text/plain']
44
+ end
45
+
46
+ it 'should have the body as the third and last element' do
47
+ body = @response[2]
48
+ body.should == "Hello, world!"
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ describe '#net_http_request_class' do
56
+
57
+ it 'should provide Net::HTTP::Get for a get method' do
58
+ Resourceful::NetHttpAdapter.send(:net_http_request_class, :get).should == Net::HTTP::Get
59
+ end
60
+
61
+ it 'should provide Net::HTTP::Post for a post method' do
62
+ Resourceful::NetHttpAdapter.send(:net_http_request_class, :post).should == Net::HTTP::Post
63
+ end
64
+
65
+ it 'should provide Net::HTTP::Put for a put method' do
66
+ Resourceful::NetHttpAdapter.send(:net_http_request_class, :put).should == Net::HTTP::Put
67
+ end
68
+
69
+ it 'should provide Net::HTTP::Delete for a delete method' do
70
+ Resourceful::NetHttpAdapter.send(:net_http_request_class, :delete).should == Net::HTTP::Delete
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ describe Addressable::URI, '#absolute_path monkey patch' do
78
+
79
+ it 'should have the path and any query parameters' do
80
+ uri = Addressable::URI.parse('http://localhost/foo?bar=baz')
81
+ uri.absolute_path.should == '/foo?bar=baz'
82
+ end
83
+
84
+ it 'should not have a ? if there are no query params' do
85
+ uri = Addressable::URI.parse('http://localhost/foo')
86
+ uri.absolute_path.should_not =~ /\?/
87
+ uri.absolute_path.should == '/foo'
88
+ end
89
+
90
+ it 'should not add the query parameter twice' do
91
+ uri = Addressable::URI.parse('http://localhost/foo?bar=baz')
92
+ uri.absolute_path.should == '/foo?bar=baz'
93
+ uri.absolute_path.should == '/foo?bar=baz'
94
+ end
95
+
96
+ end
@@ -0,0 +1,94 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+ require 'resourceful/options_interpreter'
3
+
4
+ describe Resourceful::OptionsInterpreter, '#initialize' do
5
+ it 'should be creatable block' do
6
+ Resourceful::OptionsInterpreter.new() {}
7
+ end
8
+ end
9
+
10
+
11
+ describe Resourceful::OptionsInterpreter, "#option()" do
12
+ before do
13
+ @interpreter = Resourceful::OptionsInterpreter.new()
14
+ end
15
+
16
+ it 'should take option name' do
17
+ @interpreter.option(:test)
18
+ @interpreter.supported_options.should include(:test)
19
+ end
20
+
21
+ it 'should take interpretation block' do
22
+ @interpreter.option(:test) {"this"}
23
+ @interpreter.supported_options.should include(:test)
24
+ end
25
+ end
26
+
27
+ describe Resourceful::OptionsInterpreter, '#interpret(options)' do
28
+ before do
29
+ @interpreter = Resourceful::OptionsInterpreter.new()
30
+ @interpreter.option(:foo)
31
+ end
32
+
33
+ it 'should return hash like structure of interpreted options' do
34
+ opts = @interpreter.interpret(:foo => 'bar')
35
+
36
+ opts.should have_key(:foo)
37
+ opts[:foo].should == 'bar'
38
+ end
39
+
40
+ it 'should raise argument error if there is an unsupported option in src hash' do
41
+ lambda {
42
+ @interpreter.interpret(:bar => 'baz')
43
+ }.should raise_error(ArgumentError, "Unrecognized options: bar")
44
+ end
45
+
46
+ it 'should list all unsupported options in the exception' do
47
+ lambda {
48
+ @interpreter.interpret(:bar => 'baz', :baz => 'bar')
49
+ }.should raise_error(ArgumentError, /Unrecognized options: (bar, baz)|(baz, bar)/)
50
+ end
51
+
52
+ it 'should execute pass the options though the appropriate handling block' do
53
+ @interpreter.option(:foo) {|foo| foo + " hello"}
54
+
55
+ @interpreter.interpret(:foo => 'bar')[:foo].should == 'bar hello'
56
+ end
57
+
58
+ it 'should not include options that were not passed in resulting hash' do
59
+ @interpreter = Resourceful::OptionsInterpreter.new()
60
+ @interpreter.option(:foo)
61
+
62
+ @interpreter.interpret({}).keys.should_not include(:foo)
63
+ end
64
+
65
+ it 'should not invoked option value munging block if option is not specified'
66
+
67
+ it 'should use default if option is not specified' do
68
+ @interpreter = Resourceful::OptionsInterpreter.new()
69
+ @interpreter.option(:foo, :default => 'hello')
70
+
71
+ opts = @interpreter.interpret({})
72
+ opts.should have_key(:foo)
73
+ opts[:foo].should == 'hello'
74
+ end
75
+
76
+ it 'should use default value if option is specified as nil' do
77
+ @interpreter = Resourceful::OptionsInterpreter.new()
78
+ @interpreter.option(:foo, :default => 'hello')
79
+
80
+ opts = @interpreter.interpret({:foo => nil})
81
+ opts.should have_key(:foo)
82
+ opts[:foo].should == 'hello'
83
+ end
84
+
85
+ it 'should not use default if option is specified ' do
86
+ @interpreter = Resourceful::OptionsInterpreter.new()
87
+ @interpreter.option(:foo, :default => 'hello')
88
+
89
+ opts = @interpreter.interpret({:foo => 'bye'})
90
+ opts.should have_key(:foo)
91
+ opts[:foo].should == 'bye'
92
+ end
93
+
94
+ end
@@ -0,0 +1,186 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+ require 'rubygems'
4
+ require 'addressable/uri'
5
+
6
+ describe Resourceful::Request do
7
+ before do
8
+ @uri = Addressable::URI.parse('http://www.example.com')
9
+ @resource = mock('resource', :logger => Resourceful::BitBucketLogger.new)
10
+ @resource.stub!(:uri).and_return(@uri)
11
+
12
+ @request = Resourceful::Request.new(:get, @resource)
13
+
14
+ @cachemgr = mock('cache_mgr')
15
+ @cachemgr.stub!(:lookup).and_return(nil)
16
+ @cachemgr.stub!(:store)
17
+ @resource.stub!(:accessor).and_return(mock('accessor', :cache_manager => @cachemgr, :logger => Resourceful::BitBucketLogger.new))
18
+ end
19
+
20
+ describe 'init' do
21
+
22
+ it 'should be instantiatable' do
23
+ @request.should be_instance_of(Resourceful::Request)
24
+ end
25
+
26
+ it 'should take an http method' do
27
+ @request.method.should == :get
28
+ end
29
+
30
+ it 'should take a resource' do
31
+ @request.resource.should == @resource
32
+ end
33
+
34
+ it 'should take an optional body' do
35
+ req = Resourceful::Request.new(:get, @resource)
36
+ req.body.should be_nil
37
+
38
+ req = Resourceful::Request.new(:post, @resource, 'Hello from post!')
39
+ req.body.should == 'Hello from post!'
40
+ end
41
+
42
+ it 'should have a request_time' do
43
+ @request.should respond_to(:request_time)
44
+ end
45
+
46
+ end
47
+
48
+ describe '#response' do
49
+ before do
50
+ @net_http_adapter_response = mock('net_http_adapter_response')
51
+ Resourceful::NetHttpAdapter.stub!(:make_request).and_return(@net_http_adapter_response)
52
+
53
+ @response = mock('response', :code => 200, :authoritative= => true, :was_unsuccessful? => false, :request_time= => nil)
54
+ Resourceful::Response.stub!(:new).and_return(@response)
55
+ end
56
+
57
+ it 'should be a method' do
58
+ @request.should respond_to(:response)
59
+ end
60
+
61
+ it 'should return the Response object' do
62
+ @request.response.should == @response
63
+ end
64
+
65
+ it 'should set the request_time to now' do
66
+ now = Time.now
67
+ Time.stub!(:now).and_return(now)
68
+
69
+ @request.response
70
+ @request.request_time.should == now
71
+ end
72
+
73
+ it 'should set the response\'s request time' do
74
+ now = Time.now
75
+ Time.stub!(:now).and_return(now)
76
+
77
+ @response.should_receive(:request_time=).with(now)
78
+ @request.response
79
+ end
80
+ end
81
+
82
+ describe '#should_be_redirected?' do
83
+ before do
84
+ @net_http_adapter_response = mock('net_http_adapter_response')
85
+ Resourceful::NetHttpAdapter.stub!(:make_request).and_return(@net_http_adapter_response)
86
+
87
+ @response = mock('response', :code => 200, :authoritative= => true, :was_unsuccessful? => false, :request_time= => nil)
88
+ Resourceful::Response.stub!(:new).and_return(@response)
89
+ end
90
+
91
+ describe 'with no callback set' do
92
+ before do
93
+ @callback = nil
94
+ @resource.stub!(:on_redirect).and_return(@callback)
95
+ end
96
+
97
+ it 'should be true for GET' do
98
+ request = Resourceful::Request.new(:get, @resource, @post_data)
99
+
100
+ request.should_be_redirected?.should be_true
101
+ end
102
+
103
+ it 'should be false for POST, etc' do
104
+ request = Resourceful::Request.new(:post, @resource, @post_data)
105
+
106
+ request.should_be_redirected?.should be_false
107
+ end
108
+
109
+ end
110
+
111
+ it 'should be true when callback returns true' do
112
+ @callback = lambda { true }
113
+ @resource.stub!(:on_redirect).and_return(@callback)
114
+ request = Resourceful::Request.new(:get, @resource, @post_data)
115
+
116
+ request.should_be_redirected?.should be_true
117
+ end
118
+
119
+ it 'should be false when callback returns false' do
120
+ @callback = lambda { false }
121
+ @resource.stub!(:on_redirect).and_return(@callback)
122
+ request = Resourceful::Request.new(:get, @resource, @post_data)
123
+
124
+ request.should_be_redirected?.should be_false
125
+ end
126
+
127
+ end
128
+
129
+ describe "content coding" do
130
+ it "should set Accept-Encoding automatically" do
131
+ @request.header['Accept-Encoding'].should == 'gzip, identity'
132
+ end
133
+ end
134
+
135
+ describe '#set_validation_headers' do
136
+ before do
137
+ @cached_response = mock('cached_response')
138
+
139
+ @cached_response_header = mock('header', :[] => nil, :has_key? => false)
140
+ @cached_response.stub!(:header).and_return(@cached_response_header)
141
+
142
+ @cachemgr.stub!(:lookup).and_return(@cached_response)
143
+ end
144
+
145
+ it 'should have an #set_validation_headers method' do
146
+ @request.should respond_to(:set_validation_headers)
147
+ end
148
+
149
+ it 'should set If-None-Match to the cached response\'s ETag' do
150
+ @cached_response_header.should_receive(:[]).with('ETag').and_return('some etag')
151
+ @cached_response_header.should_receive(:has_key?).with('ETag').and_return(true)
152
+ @request.set_validation_headers(@cached_response)
153
+
154
+ @request.header['If-None-Match'].should == 'some etag'
155
+ end
156
+
157
+ it 'should not set If-None-Match if the cached response does not have an ETag' do
158
+ @request.set_validation_headers(@cached_response)
159
+ @request.header.should_not have_key('If-None-Match')
160
+ end
161
+
162
+ it 'should set If-Modified-Since to the cached response\'s Last-Modified' do
163
+ @cached_response_header.should_receive(:[]).with('Last-Modified').and_return('some date')
164
+ @cached_response_header.should_receive(:has_key?).with('Last-Modified').and_return(true)
165
+ @request.set_validation_headers(@cached_response)
166
+
167
+ @request.header['If-Modified-Since'].should == 'some date'
168
+ end
169
+
170
+ it 'should not set If-Modified-Since if the cached response does not have Last-Modified' do
171
+ @request.set_validation_headers(@cached_response)
172
+ @request.header.should_not have_key('If-Modified-Since')
173
+ end
174
+
175
+ it 'should add "Cache-Control: max-age=0" to the request when revalidating a response that has "Cache-Control: must-revalidate" set' do
176
+ @cached_response_header.should_receive(:[]).with('Cache-Control').and_return(['must-revalidate'])
177
+ @cached_response_header.should_receive(:has_key?).with('Cache-Control').and_return(true)
178
+ @request.set_validation_headers(@cached_response)
179
+
180
+ @request.header['Cache-Control'].should include('max-age=0')
181
+ end
182
+
183
+ end
184
+
185
+ end
186
+