restfully 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +90 -0
- data/Rakefile +75 -0
- data/TODO.rdoc +3 -0
- data/VERSION +1 -0
- data/bin/restfully +80 -0
- data/examples/grid5000.rb +28 -0
- data/lib/restfully.rb +19 -0
- data/lib/restfully/collection.rb +63 -0
- data/lib/restfully/error.rb +4 -0
- data/lib/restfully/extensions.rb +41 -0
- data/lib/restfully/http.rb +9 -0
- data/lib/restfully/http/adapters/abstract_adapter.rb +30 -0
- data/lib/restfully/http/adapters/patron_adapter.rb +16 -0
- data/lib/restfully/http/adapters/rest_client_adapter.rb +31 -0
- data/lib/restfully/http/error.rb +20 -0
- data/lib/restfully/http/headers.rb +20 -0
- data/lib/restfully/http/request.rb +24 -0
- data/lib/restfully/http/response.rb +19 -0
- data/lib/restfully/link.rb +35 -0
- data/lib/restfully/parsing.rb +31 -0
- data/lib/restfully/resource.rb +117 -0
- data/lib/restfully/session.rb +61 -0
- data/lib/restfully/special_array.rb +5 -0
- data/lib/restfully/special_hash.rb +5 -0
- data/restfully.gemspec +99 -0
- data/spec/collection_spec.rb +93 -0
- data/spec/fixtures/grid5000-sites.json +489 -0
- data/spec/http/error_spec.rb +18 -0
- data/spec/http/headers_spec.rb +17 -0
- data/spec/http/request_spec.rb +45 -0
- data/spec/http/response_spec.rb +15 -0
- data/spec/http/rest_client_adapter_spec.rb +33 -0
- data/spec/link_spec.rb +58 -0
- data/spec/parsing_spec.rb +25 -0
- data/spec/resource_spec.rb +198 -0
- data/spec/restfully_spec.rb +13 -0
- data/spec/session_spec.rb +105 -0
- data/spec/spec_helper.rb +13 -0
- metadata +117 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../spec_helper'
|
2
|
+
describe Restfully::HTTP::Error do
|
3
|
+
it "should have a response reader" do
|
4
|
+
response = mock("restfully response", :status => 404, :body => {'title' => 'Not Found', 'message' => 'The requested resource cannot be found.', 'code' => 404})
|
5
|
+
error = Restfully::HTTP::Error.new(response)
|
6
|
+
error.response.should == response
|
7
|
+
end
|
8
|
+
it "should work properly" do
|
9
|
+
response = mock("restfully response", :status => 404, :body => {'title' => 'Not Found', 'message' => 'The requested resource cannot be found.', 'code' => 404})
|
10
|
+
begin
|
11
|
+
raise Restfully::HTTP::Error.new(response)
|
12
|
+
rescue Restfully::HTTP::Error => e
|
13
|
+
e.response.should == response
|
14
|
+
e.message.should == "404 Not Found. The requested resource cannot be found."
|
15
|
+
e.backtrace.should_not be_empty
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../spec_helper'
|
2
|
+
describe Restfully::HTTP::Headers do
|
3
|
+
class IncludeHeadersModule
|
4
|
+
include Restfully::HTTP::Headers
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should correctly parse headers" do
|
8
|
+
sanitized_headers = IncludeHeadersModule.new.sanitize_http_headers('accept' => 'application/json', :x_remote_ident => 'crohr', 'X_GVI' => 'sid', 'CACHE-CONTROL' => ['max-age=0', 'no-cache'], 'Content-Length' => 22)
|
9
|
+
sanitized_headers.should == {
|
10
|
+
'Accept' => 'application/json',
|
11
|
+
'X-Remote-Ident' => 'crohr',
|
12
|
+
'X-Gvi' => 'sid',
|
13
|
+
'Cache-Control' => 'max-age=0, no-cache',
|
14
|
+
'Content-Length' => 22
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../spec_helper'
|
2
|
+
describe Restfully::HTTP::Request do
|
3
|
+
|
4
|
+
it "should correctly initialize the attributes" do
|
5
|
+
request = Restfully::HTTP::Request.new(
|
6
|
+
'https://api.grid5000.fr/sid/grid5000?q1=v1&q2=v2',
|
7
|
+
:headers => {'accept' => 'application/json', :cache_control => 'max-age=0'},
|
8
|
+
:query => {'custom_param1' => [3, 4, 5, 6], 'custom_param2' => 'value_custom_param2'}
|
9
|
+
)
|
10
|
+
request.uri.should be_a URI
|
11
|
+
request.uri.to_s.should == 'https://api.grid5000.fr/sid/grid5000?q1=v1&q2=v2&custom_param1=3,4,5,6&custom_param2=value_custom_param2'
|
12
|
+
request.uri.query.should == "q1=v1&q2=v2&custom_param1=3,4,5,6&custom_param2=value_custom_param2"
|
13
|
+
request.headers.should == {
|
14
|
+
'Accept' => 'application/json',
|
15
|
+
'Cache-Control' => 'max-age=0'
|
16
|
+
}
|
17
|
+
request.retries.should == 0
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should accept a URI object as url" do
|
21
|
+
request = Restfully::HTTP::Request.new(uri=URI.parse('https://api.grid5000.fr/sid/grid5000'))
|
22
|
+
request.uri.to_s.should == 'https://api.grid5000.fr/sid/grid5000'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not fail if there are query parameters but no query string in the given URL" do
|
26
|
+
request = Restfully::HTTP::Request.new('https://api.grid5000.fr/grid5000', :query => {:q1 => 'v1'})
|
27
|
+
request.uri.query.should == "q1=v1"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not change the query string if none is given as an option" do
|
31
|
+
request = Restfully::HTTP::Request.new('https://api.grid5000.fr/grid5000?q1=v1&q2=v2')
|
32
|
+
request.uri.to_s.should == 'https://api.grid5000.fr/grid5000?q1=v1&q2=v2'
|
33
|
+
request.headers.should == {}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should offer a function to add headers" do
|
37
|
+
request = Restfully::HTTP::Request.new('https://api.grid5000.fr/grid5000?q1=v1&q2=v2', :headers => {:accept => 'application/json'})
|
38
|
+
expected_headers = {
|
39
|
+
'Accept' => 'application/json',
|
40
|
+
'Cache-Control' => 'max-age=0'
|
41
|
+
}
|
42
|
+
request.add_headers(:cache_control => 'max-age=0').should == expected_headers
|
43
|
+
request.headers.should == expected_headers
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../spec_helper'
|
2
|
+
describe Restfully::HTTP::Response do
|
3
|
+
|
4
|
+
it "should correctly initialize the attributes" do
|
5
|
+
response = Restfully::HTTP::Response.new(404, {:content_type => 'application/json;charset=utf-8'}, '{"property1": "value1", "property2": "value2"}')
|
6
|
+
response.status.should == 404
|
7
|
+
response.headers.should == {
|
8
|
+
'Content-Type' => 'application/json;charset=utf-8'
|
9
|
+
}
|
10
|
+
response.body.should == {
|
11
|
+
'property1' => 'value1',
|
12
|
+
'property2' => 'value2'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/../spec_helper'
|
2
|
+
describe Restfully::HTTP::Adapters::RestClientAdapter do
|
3
|
+
it "should correctly get the resource corresponding to the request" do
|
4
|
+
adapter = Restfully::HTTP::Adapters::RestClientAdapter.new("https://api.grid5000.fr", :username => 'crohr', :password => 'password')
|
5
|
+
request = Restfully::HTTP::Request.new('http://api.local/sid/grid5000', :headers => {:accept => 'application/json'}, :query => {:q1 => 'v1'})
|
6
|
+
RestClient::Resource.should_receive(:new).with('http://api.local/sid/grid5000?q1=v1', :password => 'password', :user => 'crohr').and_return(resource = mock("restclient resource"))
|
7
|
+
resource.should_receive(:get).with(request.headers).and_return(mock("restclient response", :headers => {:content_type => 'application/json;charset=utf-8'}, :to_s => "", :code => 200))
|
8
|
+
response = adapter.get(request)
|
9
|
+
response.status.should == 200
|
10
|
+
response.body.should be_nil
|
11
|
+
response.headers.should == {
|
12
|
+
'Content-Type' => 'application/json;charset=utf-8'
|
13
|
+
}
|
14
|
+
end
|
15
|
+
it "should transform the username option into a user option" do
|
16
|
+
adapter = Restfully::HTTP::Adapters::RestClientAdapter.new("https://api.grid5000.fr", :username => 'crohr', :password => 'password')
|
17
|
+
adapter.options[:user].should == 'crohr'
|
18
|
+
adapter.options[:username].should be_nil
|
19
|
+
end
|
20
|
+
it "should raise a not implemented error when trying to use functions not implemented yet" do
|
21
|
+
adapter = Restfully::HTTP::Adapters::RestClientAdapter.new("https://api.grid5000.fr")
|
22
|
+
lambda{adapter.post(mock("restfully request"))}.should raise_error NotImplementedError, "POST is not supported by your adapter."
|
23
|
+
end
|
24
|
+
it "should rescue any RestClient::Exception and correctly populate the response" do
|
25
|
+
res = mock(Net::HTTPResponse, :code => 404, :body => '{"message":"whatever"}', :to_hash => {'Content-Type' => 'application/json;charset=utf-8', 'Content-Length' => 22}, :[] => '')
|
26
|
+
RestClient::Resource.should_receive(:new).and_raise RestClient::ResourceNotFound.new(res)
|
27
|
+
adapter = Restfully::HTTP::Adapters::RestClientAdapter.new("https://api.grid5000.fr", :username => 'crohr', :password => 'password')
|
28
|
+
response = adapter.get(mock("request", :uri => "uri"))
|
29
|
+
response.status.should == 404
|
30
|
+
response.headers.should == {'Content-Type' => 'application/json;charset=utf-8', 'Content-Length' => 22}
|
31
|
+
response.body.should == {'message' => 'whatever'}
|
32
|
+
end
|
33
|
+
end
|
data/spec/link_spec.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/spec_helper'
|
2
|
+
|
3
|
+
include Restfully
|
4
|
+
describe Link do
|
5
|
+
|
6
|
+
it "should have a rel reader" do
|
7
|
+
link = Link.new
|
8
|
+
link.should_not respond_to(:rel=)
|
9
|
+
link.should respond_to(:rel)
|
10
|
+
end
|
11
|
+
it "should have a href reader" do
|
12
|
+
link = Link.new
|
13
|
+
link.should_not respond_to(:href=)
|
14
|
+
link.should respond_to(:href)
|
15
|
+
end
|
16
|
+
it "should have a title reader" do
|
17
|
+
link = Link.new
|
18
|
+
link.should_not respond_to(:title=)
|
19
|
+
link.should respond_to(:title)
|
20
|
+
end
|
21
|
+
it "should have a errors reader" do
|
22
|
+
link = Link.new
|
23
|
+
link.should_not respond_to(:errors=)
|
24
|
+
link.should respond_to(:errors)
|
25
|
+
end
|
26
|
+
it "should respond to valid?" do
|
27
|
+
link = Link.new
|
28
|
+
link.should respond_to(:valid?)
|
29
|
+
end
|
30
|
+
it "should respond to resolved?" do
|
31
|
+
link = Link.new
|
32
|
+
link.should respond_to(:resolved?)
|
33
|
+
end
|
34
|
+
it "should respond to resolvable?" do
|
35
|
+
link = Link.new
|
36
|
+
link.should respond_to(:resolvable?)
|
37
|
+
end
|
38
|
+
it "by default, should not be resolvable" do
|
39
|
+
link = Link.new
|
40
|
+
link.should_not be_resolvable
|
41
|
+
end
|
42
|
+
it "by default, should not be resolved" do
|
43
|
+
link = Link.new
|
44
|
+
link.should_not be_resolved
|
45
|
+
end
|
46
|
+
it "should not be valid if there is no href" do
|
47
|
+
link = Link.new 'rel' => 'collection', 'title' => 'my collection'
|
48
|
+
link.should_not be_valid
|
49
|
+
end
|
50
|
+
it "should not be valid if there is no rel" do
|
51
|
+
link = Link.new 'href' => '/', 'title' => 'my collection'
|
52
|
+
link.should_not be_valid
|
53
|
+
end
|
54
|
+
it "should not be valid if the rel is valid but requires a title that is not given" do
|
55
|
+
link = Link.new 'rel' => 'collection', 'href' => '/'
|
56
|
+
link.should_not be_valid
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/spec_helper'
|
2
|
+
|
3
|
+
include Restfully::Parsing
|
4
|
+
|
5
|
+
describe Restfully::Parsing do
|
6
|
+
class IncludesParsingModule
|
7
|
+
include Restfully::Parsing
|
8
|
+
end
|
9
|
+
it "should make available the serialize and unserialize methods" do
|
10
|
+
klass = IncludesParsingModule.new
|
11
|
+
klass.should respond_to(:unserialize)
|
12
|
+
klass.should respond_to(:serialize)
|
13
|
+
end
|
14
|
+
it "should raise a ParserNotFound error if the object cannot be parsed" do
|
15
|
+
lambda{unserialize("whatever", :content_type => 'unknown')}.should raise_error(Restfully::Parsing::ParserNotFound, "Content-Type 'unknown' is not supported. Cannot parse the given object.")
|
16
|
+
end
|
17
|
+
it "should correctly unserialize json content" do
|
18
|
+
object = {'p1' => 'v1'}
|
19
|
+
unserialize(object.to_json, :content_type => 'application/json;charset=utf-8').should == object
|
20
|
+
end
|
21
|
+
it "should correctly serialize an object into json" do
|
22
|
+
object = {'p1' => 'v1'}
|
23
|
+
serialize(object, :content_type => 'application/json;charset=utf-8').should == object.to_json
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/spec_helper'
|
2
|
+
|
3
|
+
include Restfully
|
4
|
+
|
5
|
+
describe Resource do
|
6
|
+
before do
|
7
|
+
@logger = Logger.new(STDOUT)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have all the methods of a hash" do
|
11
|
+
resource = Resource.new("uri", session=mock('session'))
|
12
|
+
resource.size.should == 0
|
13
|
+
resource['whatever'] = 'thing'
|
14
|
+
resource.size.should == 1
|
15
|
+
resource.should == {'whatever' => 'thing'}
|
16
|
+
resource.should respond_to(:each)
|
17
|
+
resource.should respond_to(:length)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "accessors" do
|
21
|
+
it "should have a reader on the session" do
|
22
|
+
resource = Resource.new("uri", session=mock("session"))
|
23
|
+
resource.should_not respond_to(:session=)
|
24
|
+
resource.session.should == session
|
25
|
+
end
|
26
|
+
it "should have a reader on the uri" do
|
27
|
+
resource = Resource.new("uri", session=mock("session"))
|
28
|
+
resource.should_not respond_to(:uri=)
|
29
|
+
resource.uri.should == "uri"
|
30
|
+
end
|
31
|
+
it "should have a reader on the raw property" do
|
32
|
+
resource = Resource.new("uri", session=mock("session"), 'raw' => {})
|
33
|
+
resource.should_not respond_to(:raw=)
|
34
|
+
resource.raw.should == {}
|
35
|
+
end
|
36
|
+
it "should have a reader on the state property" do
|
37
|
+
resource = Resource.new("uri", session=mock("session"))
|
38
|
+
resource.should_not respond_to(:state=)
|
39
|
+
resource.state.should == :unloaded
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
describe "loading" do
|
45
|
+
|
46
|
+
before do
|
47
|
+
@raw = {
|
48
|
+
'links' => [
|
49
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes'},
|
50
|
+
{'rel' => 'parent', 'href' => '/grid5000'},
|
51
|
+
{'rel' => 'invalid_rel', 'href' => '/whatever'},
|
52
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/status', 'title' => 'status'},
|
53
|
+
{'rel' => 'member', 'href' => '/grid5000/sites/rennes/versions/123', 'title' => 'version'},
|
54
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/versions', 'resolvable' => false, 'title' => 'versions'},
|
55
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters', 'resolvable' => true, 'resolved' => true, 'title' => 'clusters'},
|
56
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/environments/versions/123', 'resolvable' => true, 'resolved' => false, 'title' => 'environments'},
|
57
|
+
{'rel' => 'collection', 'href' => '/has/no/title'}
|
58
|
+
],
|
59
|
+
'uid' => 'rennes',
|
60
|
+
'whatever' => 'whatever',
|
61
|
+
'an_array' => [1, 2, 3],
|
62
|
+
'clusters' => {
|
63
|
+
'paradent' => {
|
64
|
+
'uid' => 'paradent',
|
65
|
+
'links' => [
|
66
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes/clusters/paradent'},
|
67
|
+
{'rel' => 'parent', 'href' => '/grid5000/sites/rennes'},
|
68
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paradent/nodes', 'title' => 'nodes', 'resolvable' => true, 'resolved' => false},
|
69
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paradent/versions', 'resolvable' => false, 'title' => 'versions'},
|
70
|
+
{'rel' => 'member', 'href' => '/grid5000/sites/rennes/clusters/paradent/versions/123', 'title' => 'version'},
|
71
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paradent/status', 'title' => 'status'}
|
72
|
+
],
|
73
|
+
'model' => 'XYZ'
|
74
|
+
},
|
75
|
+
'paramount' => {
|
76
|
+
'uid' => 'paramount',
|
77
|
+
'links' => [
|
78
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes/clusters/paramount'},
|
79
|
+
{'rel' => 'parent', 'href' => '/grid5000/sites/rennes'},
|
80
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paramount/nodes', 'title' => 'nodes', 'resolvable' => true, 'resolved' => false},
|
81
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paramount/versions', 'resolvable' => false, 'title' => 'versions'},
|
82
|
+
{'rel' => 'member', 'href' => '/grid5000/sites/rennes/clusters/paramount/versions/123', 'title' => 'version'},
|
83
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/clusters/paramount/status', 'title' => 'status'}
|
84
|
+
],
|
85
|
+
'model' => 'XYZ1b'
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}.to_json
|
89
|
+
@response_200 = Restfully::HTTP::Response.new(200, {'Content-Type' => 'application/json;utf-8', 'Content-Length' => @raw.length}, @raw)
|
90
|
+
end
|
91
|
+
it "should not be loaded in its initial state" do
|
92
|
+
resource = Resource.new(mock("uri"), mock('session'))
|
93
|
+
resource.should_not be_loaded
|
94
|
+
end
|
95
|
+
it "should get the raw representation of the resource via the session if it doesn't have it" do
|
96
|
+
resource = Resource.new(uri=mock("uri"), session = mock("session", :logger => Logger.new(STDOUT)))
|
97
|
+
resource.stub!(:define_link) # do not define links
|
98
|
+
resource.raw.should be_nil
|
99
|
+
session.should_receive(:get).with(uri, {}).and_return(@response_200)
|
100
|
+
resource.load
|
101
|
+
resource.should be_loaded
|
102
|
+
end
|
103
|
+
it "should get the raw representation of the resource via the session if there are query parameters" do
|
104
|
+
resource = Resource.new(uri=mock("uri"), session = mock("session", :logger => Logger.new(STDOUT)))
|
105
|
+
resource.stub!(:define_link) # do not define links
|
106
|
+
resource.stub!(:loaded?).and_return(true) # should force reload even if already loaded
|
107
|
+
resource.raw.should be_nil
|
108
|
+
session.should_receive(:get).with(uri, {:query => {:q1 => 'v1'}}).and_return(@response_200)
|
109
|
+
resource.load(:query => {:q1 => 'v1'})
|
110
|
+
end
|
111
|
+
it "should get the raw representation of the resource if forced to do so" do
|
112
|
+
resource = Resource.new(uri=mock("uri"), session = mock("session", :logger => Logger.new(STDOUT)))
|
113
|
+
resource.stub!(:define_link) # do not define links
|
114
|
+
resource.stub!(:loaded?).and_return(true) # should force reload even if already loaded
|
115
|
+
resource.raw.should be_nil
|
116
|
+
session.should_receive(:get).with(uri, {}).and_return(@response_200)
|
117
|
+
resource.load(:reload => true)
|
118
|
+
end
|
119
|
+
it "should correctly define the functions to access simple values" do
|
120
|
+
resource = Resource.new("uri", session = mock("session", :get => @response_200, :logger => @logger))
|
121
|
+
resource.stub!(:define_link) # do not define links
|
122
|
+
resource.load
|
123
|
+
resource['whatever'].should == 'whatever'
|
124
|
+
resource.uri.should == 'uri'
|
125
|
+
resource.uid.should == 'rennes'
|
126
|
+
resource['an_array'].should be_a(SpecialArray)
|
127
|
+
resource['an_array'].should == [1,2,3]
|
128
|
+
lambda{resource.clusters}.should raise_error(NoMethodError)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should correctly define a collection association" do
|
132
|
+
resource = Resource.new("uri", session = mock("session", :get => mock("restfully response", :body => {
|
133
|
+
'links' => [
|
134
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes'},
|
135
|
+
{'rel' => 'collection', 'href' => '/grid5000/sites/rennes/versions', 'resolvable' => false, 'title' => 'versions'}
|
136
|
+
],
|
137
|
+
'uid' => 'rennes'
|
138
|
+
}), :logger => @logger))
|
139
|
+
Collection.should_receive(:new).with('/grid5000/sites/rennes/versions', session, :title => 'versions', :raw => nil).and_return(collection=mock("restfully collection"))
|
140
|
+
resource.load
|
141
|
+
resource.associations['versions'].should == collection
|
142
|
+
end
|
143
|
+
it "should NOT update the URI with the self link" do
|
144
|
+
resource = Resource.new("uri", session = mock("session", :get => mock("restfully response", :body => {
|
145
|
+
'links' => [
|
146
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes'}
|
147
|
+
],
|
148
|
+
'uid' => 'rennes'
|
149
|
+
}), :logger => @logger))
|
150
|
+
resource.uri.should == "uri"
|
151
|
+
resource.load
|
152
|
+
resource.uri.should == "uri"
|
153
|
+
end
|
154
|
+
it "should correctly define a member association" do
|
155
|
+
resource = Resource.new("uri", session = mock("session", :get => mock("restfully response", :body => {
|
156
|
+
'links' => [
|
157
|
+
{'rel' => 'member', 'href' => '/grid5000/sites/rennes/versions/123', 'title' => 'version'}
|
158
|
+
],
|
159
|
+
'uid' => 'rennes'
|
160
|
+
}), :logger => @logger))
|
161
|
+
Resource.should_receive(:new).with('/grid5000/sites/rennes/versions/123', session, :title => 'version', :raw => nil).and_return(member=mock("restfully resource"))
|
162
|
+
resource.load
|
163
|
+
resource.associations['version'].should == member
|
164
|
+
end
|
165
|
+
it "should correctly define a parent association" do
|
166
|
+
resource = Resource.new("uri", session = mock("session", :get => mock("restfully response", :body => {
|
167
|
+
'links' => [
|
168
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes'},
|
169
|
+
{'rel' => 'parent', 'href' => '/grid5000'}
|
170
|
+
],
|
171
|
+
'uid' => 'rennes'
|
172
|
+
}), :logger => @logger))
|
173
|
+
Resource.should_receive(:new).with('/grid5000', session).and_return(parent=mock("restfully resource"))
|
174
|
+
resource.load
|
175
|
+
resource.associations['parent'].should == parent
|
176
|
+
end
|
177
|
+
it "should ignore bad links" do
|
178
|
+
resource = Resource.new("uri", session = mock("session", :get => mock("restfully response", :body => {
|
179
|
+
'links' => [
|
180
|
+
{'rel' => 'self', 'href' => '/grid5000/sites/rennes'},
|
181
|
+
{'rel' => 'invalid_rel', 'href' => '/whatever'},
|
182
|
+
{'rel' => 'collection', 'href' => '/has/no/title'}
|
183
|
+
],
|
184
|
+
'uid' => 'rennes'
|
185
|
+
}), :logger => @logger))
|
186
|
+
resource.load
|
187
|
+
resource.associations.should be_empty
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should correctly define the functions to access links [integration test]" do
|
191
|
+
resource = Resource.new("uri", session = mock("session", :get => @response_200, :logger => @logger))
|
192
|
+
@logger.should_receive(:warn).with(/collection \/has\/no\/title has no title/)
|
193
|
+
@logger.should_receive(:warn).with(/invalid_rel is not a valid link relationship/)
|
194
|
+
resource.load
|
195
|
+
resource.associations.keys.should =~ ['versions', 'clusters', 'environments', 'status', 'parent', 'version']
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Restfully do
|
4
|
+
it "should have a default adapter" do
|
5
|
+
Restfully.adapter.should == Restfully::HTTP::Adapters::RestClientAdapter
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should allow setting other adapters" do
|
9
|
+
require 'restfully/http/adapters/patron_adapter'
|
10
|
+
Restfully.adapter = Restfully::HTTP::Adapters::PatronAdapter
|
11
|
+
Restfully.adapter.should == Restfully::HTTP::Adapters::PatronAdapter
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/spec_helper'
|
2
|
+
|
3
|
+
include Restfully
|
4
|
+
describe Session do
|
5
|
+
describe "initialization" do
|
6
|
+
it "should have a logger reader" do
|
7
|
+
session = Session.new('https://api.grid5000.fr')
|
8
|
+
session.should_not respond_to(:logger=)
|
9
|
+
session.should respond_to(:logger)
|
10
|
+
end
|
11
|
+
it "should have a base_uri reader" do
|
12
|
+
session = Session.new('https://api.grid5000.fr')
|
13
|
+
session.should_not respond_to(:base_uri=)
|
14
|
+
session.base_uri.should == 'https://api.grid5000.fr'
|
15
|
+
end
|
16
|
+
it "should have a root_path reader that returns the path of the root resource, relative to the base URI" do
|
17
|
+
session = Session.new('https://api.grid5000.fr/sid', 'root_path' => '/grid5000')
|
18
|
+
session.should_not respond_to(:root_path=)
|
19
|
+
session.root_path.should == '/grid5000'
|
20
|
+
end
|
21
|
+
it "should set the default root_path to /" do
|
22
|
+
session = Session.new('https://api.grid5000.fr')
|
23
|
+
session.root_path.should == '/'
|
24
|
+
end
|
25
|
+
it "should log to NullLogger by default" do
|
26
|
+
NullLogger.should_receive(:new).and_return(logger = mock(NullLogger))
|
27
|
+
session = Session.new('https://api.grid5000.fr')
|
28
|
+
session.logger.should == logger
|
29
|
+
end
|
30
|
+
it "should use the given logger" do
|
31
|
+
logger = mock("custom logger")
|
32
|
+
session = Session.new('https://api.grid5000.fr', 'logger' => logger)
|
33
|
+
session.logger.should == logger
|
34
|
+
end
|
35
|
+
it "should set a default Accept HTTP header if no default headers are given" do
|
36
|
+
session = Session.new('https://api.grid5000.fr')
|
37
|
+
session.default_headers.should == {
|
38
|
+
'Accept' => 'application/json'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
it "should use the given default headers" do
|
42
|
+
session = Session.new('https://api.grid5000.fr', :default_headers => {:accept => 'application/xml'})
|
43
|
+
session.default_headers.should == {
|
44
|
+
:accept => 'application/xml'
|
45
|
+
}
|
46
|
+
end
|
47
|
+
it "should correctly initialize the connection" do
|
48
|
+
Restfully.adapter.should_receive(:new).with('https://api.grid5000.fr/sid', :user => 'crohr', :password => 'password').and_return(connection = mock("restfully connection"))
|
49
|
+
session = Session.new('https://api.grid5000.fr/sid', 'root_path' => '/grid5000', :user => 'crohr', :password => 'password')
|
50
|
+
session.connection.should == connection
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should initialize the root resource" do
|
54
|
+
session = Session.new('https://api.grid5000.fr', 'root_path' => '/grid5000', :user => 'crohr', :password => 'password')
|
55
|
+
session.root.should be_a Restfully::Resource
|
56
|
+
session.root.uri.to_s.should == '/grid5000'
|
57
|
+
session.root.should_not be_loaded
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should yield the loaded root resource and the session object" do
|
61
|
+
Restfully::Resource.stub!(:new).and_return(root_resource = mock(Restfully::Resource))
|
62
|
+
root_resource.should_receive(:load).and_return(root_resource)
|
63
|
+
Session.new('https://api.grid5000.fr', :root_path => '/grid5000', :user => 'crohr', :password => 'password') do |root, session|
|
64
|
+
session.root.should == root
|
65
|
+
root.should == root_resource
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "Getting resources" do
|
71
|
+
before(:each) do
|
72
|
+
@session = Session.new('https://api.grid5000.fr/sid', :root_path => '/grid5000', :user => 'crohr', :password => 'password', :default_headers => {})
|
73
|
+
@request = mock("restfully http request", :uri => mock("uri"), :headers => mock("headers"))
|
74
|
+
@response = mock("restfully http response", :status => 200, :headers => mock("headers"))
|
75
|
+
end
|
76
|
+
it "should create a new Request object and pass it to the connection" do
|
77
|
+
Restfully::HTTP::Request.should_receive(:new).with(URI.parse('https://api.grid5000.fr/sid/some/path'), :headers => {:cache_control => 'max-age=0'}, :query => {}).and_return(@request)
|
78
|
+
@session.connection.should_receive(:get).with(@request).and_return(@response)
|
79
|
+
@session.get('/some/path', :headers => {:cache_control => 'max-age=0'}).should == @response
|
80
|
+
end
|
81
|
+
it "should not use the base_url if the path is a complete url" do
|
82
|
+
Restfully::HTTP::Request.should_receive(:new).with(URI.parse('http://somehost.com/some/path'), :headers => {}, :query => {}).and_return(@request)
|
83
|
+
@session.connection.should_receive(:get).with(@request).and_return(@response)
|
84
|
+
@session.get('http://somehost.com/some/path').should == @response
|
85
|
+
end
|
86
|
+
it "should add the session's default headers to the request's headers" do
|
87
|
+
Restfully::HTTP::Request.should_receive(:new).with(URI.parse('http://somehost.com/some/path'), :headers => {}, :query => {}).and_return(@request)
|
88
|
+
@session.default_headers['cache-control'] = 'max-age=0'
|
89
|
+
@request.should_receive(:add_headers).with('cache-control' => 'max-age=0')
|
90
|
+
@session.connection.should_receive(:get).with(@request).and_return(@response)
|
91
|
+
@session.get('http://somehost.com/some/path').should == @response
|
92
|
+
end
|
93
|
+
it "should raise a Restfully::HTTP::ClientError error on 4xx errors" do
|
94
|
+
Restfully::HTTP::Request.should_receive(:new).with(URI.parse('http://somehost.com/some/path'), :headers => {}, :query => {}).and_return(@request)
|
95
|
+
@session.connection.should_receive(:get).with(@request).and_return(response = mock("404 response", :status => 404, :body => {'code' => 404, 'message' => 'The requested resource cannot be found.', 'title' => 'Not Found'}, :headers => mock("headers")))
|
96
|
+
lambda{ @session.get('http://somehost.com/some/path') }.should raise_error(Restfully::HTTP::ClientError, "404 Not Found. The requested resource cannot be found.")
|
97
|
+
end
|
98
|
+
it "should raise a Restfully::HTTP::ServerError error on 4xx errors" do
|
99
|
+
Restfully::HTTP::Request.should_receive(:new).with(URI.parse('http://somehost.com/some/path'), :headers => {}, :query => {}).and_return(@request)
|
100
|
+
@session.connection.should_receive(:get).with(@request).and_return(response = mock("404 response", :status => 500, :body => {'code' => 500, 'message' => 'Something went wrong.', 'title' => 'Internal Server Error'}, :headers => mock("headers")))
|
101
|
+
lambda{ @session.get('http://somehost.com/some/path') }.should raise_error(Restfully::HTTP::ServerError, "500 Internal Server Error. Something went wrong.")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|