paul-resourceful 0.2.3

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