hyperclient 0.0.8 → 0.1.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.
- data/Gemfile +2 -1
- data/Readme.md +15 -21
- data/examples/cyberscore.rb +28 -16
- data/examples/hal_shop.rb +15 -22
- data/hyperclient.gemspec +2 -0
- data/lib/hyperclient.rb +2 -84
- data/lib/hyperclient/attributes.rb +20 -0
- data/lib/hyperclient/collection.rb +53 -0
- data/lib/hyperclient/entry_point.rb +49 -0
- data/lib/hyperclient/http.rb +38 -37
- data/lib/hyperclient/link.rb +93 -0
- data/lib/hyperclient/link_collection.rb +24 -0
- data/lib/hyperclient/resource.rb +26 -65
- data/lib/hyperclient/resource_collection.rb +33 -0
- data/lib/hyperclient/version.rb +1 -1
- data/test/fixtures/element.json +4 -15
- data/test/hyperclient/attributes_test.rb +26 -0
- data/test/hyperclient/collection_test.rb +39 -0
- data/test/hyperclient/entry_point_test.rb +51 -0
- data/test/hyperclient/http_test.rb +37 -23
- data/test/hyperclient/link_collection_test.rb +29 -0
- data/test/hyperclient/link_test.rb +93 -0
- data/test/hyperclient/resource_collection_test.rb +34 -0
- data/test/hyperclient/resource_test.rb +36 -42
- data/test/test_helper.rb +10 -1
- metadata +55 -16
- data/lib/hyperclient/discoverer.rb +0 -84
- data/lib/hyperclient/representation.rb +0 -43
- data/lib/hyperclient/resource_factory.rb +0 -53
- data/test/hyperclient/discoverer_test.rb +0 -101
- data/test/hyperclient/representation_test.rb +0 -52
- data/test/hyperclient/resource_factory_test.rb +0 -32
- data/test/hyperclient_test.rb +0 -68
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/resource'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe Collection do
|
6
|
+
let(:representation) do
|
7
|
+
JSON.parse( File.read('test/fixtures/element.json'))
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:collection) do
|
11
|
+
Collection.new(representation)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'exposes the collection as methods' do
|
15
|
+
collection.title.must_equal 'Real World ASP.NET MVC3'
|
16
|
+
collection.description.must_match(/production/)
|
17
|
+
collection.permitted.must_equal true
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'exposes collection as a hash' do
|
21
|
+
collection['title'].must_equal 'Real World ASP.NET MVC3'
|
22
|
+
collection['description'].must_match(/production/)
|
23
|
+
collection['permitted'].must_equal true
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'correctly responds to methods' do
|
27
|
+
collection.must_respond_to :title
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'acts as enumerable' do
|
31
|
+
names = collection.map do |name, value|
|
32
|
+
name
|
33
|
+
end
|
34
|
+
|
35
|
+
names.must_equal ['_links', 'title', 'description', 'permitted', '_embedded']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/entry_point'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe EntryPoint do
|
6
|
+
let(:api) do
|
7
|
+
EntryPoint.new 'http://my.api.org'
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
stub_request(:get, "http://my.api.org/").
|
12
|
+
to_return(body: '{"_links": {"self": {"href": "http://my.api.org"}}}', headers: {content_type: 'application/json'})
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'initialize' do
|
16
|
+
it 'initializes a Resource at the entry point' do
|
17
|
+
api.links['self'].url.must_equal 'http://my.api.org'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'setups the HTTP config' do
|
21
|
+
options = {:headers => {'accept-encoding' => 'deflate, gzip'}}
|
22
|
+
|
23
|
+
api = EntryPoint.new('http://my.api.org', options)
|
24
|
+
|
25
|
+
api.config[:headers].must_include 'accept-encoding'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the base_uri for HTTP' do
|
29
|
+
api = EntryPoint.new('http://my.api.org')
|
30
|
+
|
31
|
+
api.config[:base_uri].must_equal 'http://my.api.org'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'method missing' do
|
36
|
+
it 'delegates undefined methods to the API when they exist' do
|
37
|
+
Resource.any_instance.expects(:foo).returns 'foo'
|
38
|
+
api.foo.must_equal 'foo'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'responds to missing methods' do
|
42
|
+
Resource.any_instance.expects(:respond_to?).with('foo').returns(true)
|
43
|
+
api.respond_to?(:foo).must_equal true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises an error when the method does not exist in the API' do
|
47
|
+
lambda { api.this_method_does_not_exist }.must_raise(NoMethodError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -3,33 +3,47 @@ require 'hyperclient/http'
|
|
3
3
|
|
4
4
|
module Hyperclient
|
5
5
|
describe HTTP do
|
6
|
-
let
|
7
|
-
|
8
|
-
resource.expect(:url, 'http://api.example.org/productions/1')
|
6
|
+
let(:url) do
|
7
|
+
'/productions/1'
|
9
8
|
end
|
10
9
|
|
11
|
-
let
|
10
|
+
let(:config) { {base_uri: 'http://api.example.org'} }
|
11
|
+
|
12
|
+
let(:http) do
|
12
13
|
HTTP.instance_variable_set("@default_options", {})
|
13
|
-
HTTP.new(
|
14
|
+
HTTP.new(url, config)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'url' do
|
18
|
+
it 'merges the resource url with the base uri' do
|
19
|
+
http.url.to_s.must_equal 'http://api.example.org/productions/1'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns the given url if it cannot merge it' do
|
23
|
+
config = {base_uri: nil}
|
24
|
+
http = HTTP.new(url, config)
|
25
|
+
http.url.to_s.must_equal '/productions/1'
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
29
|
describe 'authentication' do
|
17
30
|
it 'sets the authentication options' do
|
18
|
-
stub_request(:get, 'user:pass@api.example.org/productions/1').
|
19
|
-
to_return(body: 'This is the resource')
|
31
|
+
stub_request(:get, 'http://user:pass@api.example.org/productions/1').
|
32
|
+
to_return(body: '{"resource": "This is the resource"}')
|
33
|
+
|
34
|
+
config.update({auth: {type: :basic, user: 'user', password: 'pass'}})
|
20
35
|
|
21
|
-
http
|
22
|
-
http.get.must_equal 'This is the resource'
|
36
|
+
http.get.must_equal({'resource' => 'This is the resource'})
|
23
37
|
end
|
24
38
|
end
|
25
39
|
|
26
40
|
describe 'headers' do
|
27
41
|
it 'sets headers from the given option' do
|
28
|
-
|
42
|
+
config.update({headers: {'accept-encoding' => 'deflate, gzip'}})
|
29
43
|
|
30
|
-
stub_request(:get, 'api.example.org/productions/1').
|
44
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
31
45
|
with(headers: {'Accept-Encoding' => 'deflate, gzip'}).
|
32
|
-
to_return(body: 'This is the resource')
|
46
|
+
to_return(body: '{"resource": "This is the resource"}')
|
33
47
|
|
34
48
|
http.get
|
35
49
|
end
|
@@ -37,14 +51,14 @@ module Hyperclient
|
|
37
51
|
|
38
52
|
describe 'debug' do
|
39
53
|
it 'enables debugging' do
|
40
|
-
|
54
|
+
config.update({debug: true})
|
41
55
|
|
42
56
|
http.class.instance_variable_get(:@default_options)[:debug_output].must_equal $stderr
|
43
57
|
end
|
44
58
|
|
45
59
|
it 'uses a custom stream' do
|
46
60
|
stream = StringIO.new
|
47
|
-
|
61
|
+
config.update({debug: stream})
|
48
62
|
|
49
63
|
http.class.instance_variable_get(:@default_options)[:debug_output].must_equal stream
|
50
64
|
end
|
@@ -52,14 +66,14 @@ module Hyperclient
|
|
52
66
|
|
53
67
|
describe 'get' do
|
54
68
|
it 'sends a GET request and returns the response body' do
|
55
|
-
stub_request(:get, 'api.example.org/productions/1').
|
56
|
-
to_return(body: 'This is the resource')
|
69
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
70
|
+
to_return(body: '{"resource": "This is the resource"}')
|
57
71
|
|
58
|
-
http.get.must_equal 'This is the resource'
|
72
|
+
http.get.must_equal({'resource' => 'This is the resource'})
|
59
73
|
end
|
60
74
|
|
61
75
|
it 'returns the parsed response' do
|
62
|
-
stub_request(:get, 'api.example.org/productions/1').
|
76
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
63
77
|
to_return(body: '{"some_json": 12345 }', headers: {content_type: 'application/json'})
|
64
78
|
|
65
79
|
http.get.must_equal({'some_json' => 12345})
|
@@ -68,7 +82,7 @@ module Hyperclient
|
|
68
82
|
|
69
83
|
describe 'post' do
|
70
84
|
it 'sends a POST request' do
|
71
|
-
stub_request(:post, 'api.example.org/productions/1').
|
85
|
+
stub_request(:post, 'http://api.example.org/productions/1').
|
72
86
|
to_return(body: 'Posting like a big boy huh?', status: 201)
|
73
87
|
|
74
88
|
response = http.post({data: 'foo'})
|
@@ -80,7 +94,7 @@ module Hyperclient
|
|
80
94
|
|
81
95
|
describe 'put' do
|
82
96
|
it 'sends a PUT request' do
|
83
|
-
stub_request(:put, 'api.example.org/productions/1').
|
97
|
+
stub_request(:put, 'http://api.example.org/productions/1').
|
84
98
|
to_return(body: 'No changes were made', status: 204)
|
85
99
|
|
86
100
|
response = http.put({attribute: 'changed'})
|
@@ -92,7 +106,7 @@ module Hyperclient
|
|
92
106
|
|
93
107
|
describe 'options' do
|
94
108
|
it 'sends a OPTIONS request' do
|
95
|
-
stub_request(:options, 'api.example.org/productions/1').
|
109
|
+
stub_request(:options, 'http://api.example.org/productions/1').
|
96
110
|
to_return(status: 200, headers: {allow: 'GET, POST'})
|
97
111
|
|
98
112
|
response = http.options
|
@@ -102,7 +116,7 @@ module Hyperclient
|
|
102
116
|
|
103
117
|
describe 'head' do
|
104
118
|
it 'sends a HEAD request' do
|
105
|
-
stub_request(:head, 'api.example.org/productions/1').
|
119
|
+
stub_request(:head, 'http://api.example.org/productions/1').
|
106
120
|
to_return(status: 200, headers: {content_type: 'application/json'})
|
107
121
|
|
108
122
|
response = http.head
|
@@ -112,7 +126,7 @@ module Hyperclient
|
|
112
126
|
|
113
127
|
describe 'delete' do
|
114
128
|
it 'sends a DELETE request' do
|
115
|
-
stub_request(:delete, 'api.example.org/productions/1').
|
129
|
+
stub_request(:delete, 'http://api.example.org/productions/1').
|
116
130
|
to_return(body: 'Resource deleted', status: 200)
|
117
131
|
|
118
132
|
response = http.delete
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/link_collection'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe LinkCollection do
|
6
|
+
let(:entry_point) { stub('Entry point', config: {base_uri: '/'}) }
|
7
|
+
|
8
|
+
let(:representation) do
|
9
|
+
JSON.parse( File.read('test/fixtures/element.json'))
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:links) do
|
13
|
+
LinkCollection.new(representation['_links'], entry_point)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'is a collection' do
|
17
|
+
LinkCollection.ancestors.must_include Collection
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'initializes the collection with links' do
|
21
|
+
links.must_respond_to :filter
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns link objects for each link' do
|
25
|
+
links.filter.must_be_kind_of Link
|
26
|
+
links['self'].must_be_kind_of Link
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/link'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe Link do
|
6
|
+
let(:entry_point) { stub('Entry point', config: {base_uri: '/'}) }
|
7
|
+
|
8
|
+
describe 'templated?' do
|
9
|
+
it 'returns true if the link is templated' do
|
10
|
+
link = Link.new({'templated' => true}, entry_point)
|
11
|
+
|
12
|
+
link.templated?.must_equal true
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns false if the link is not templated' do
|
16
|
+
link = Link.new({}, entry_point)
|
17
|
+
|
18
|
+
link.templated?.must_equal false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'resource' do
|
23
|
+
let(:http) { mock('HTTP', get: {}) }
|
24
|
+
|
25
|
+
it 'builds a resource with the hyperlink representation' do
|
26
|
+
HTTP.expects(:new).returns(http, {})
|
27
|
+
Resource.expects(:new).with({}, entry_point)
|
28
|
+
|
29
|
+
Link.new({}, entry_point).resource
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'expand' do
|
34
|
+
it 'buils a Link with the templated URI representation' do
|
35
|
+
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
36
|
+
|
37
|
+
Link.expects(:new).with(anything, entry_point, {id: '1'})
|
38
|
+
link.expand(id: '1')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises if no uri variables are given' do
|
42
|
+
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
43
|
+
|
44
|
+
proc { link.resource }.must_raise MissingURITemplateVariablesException
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'url' do
|
49
|
+
it 'raises when missing required uri_variables' do
|
50
|
+
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
51
|
+
|
52
|
+
lambda { link.url }.must_raise MissingURITemplateVariablesException
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'expands an uri template with variables' do
|
56
|
+
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point, {id: 1})
|
57
|
+
|
58
|
+
link.url.must_equal '/orders?id=1'
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns the link when no uri template' do
|
62
|
+
link = Link.new({'href' => '/orders'}, entry_point)
|
63
|
+
link.url.must_equal '/orders'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'method_missing' do
|
68
|
+
before do
|
69
|
+
stub_request(:get, "http://myapi.org/orders").
|
70
|
+
to_return(body: '{"resource": "This is the resource"}')
|
71
|
+
Resource.expects(:new).returns(resource).at_least_once
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:link) { Link.new({'href' => 'http://myapi.org/orders'}, entry_point) }
|
75
|
+
let(:resource) { mock('Resource') }
|
76
|
+
|
77
|
+
it 'delegates unkown methods to the resource' do
|
78
|
+
resource.expects(:embedded)
|
79
|
+
|
80
|
+
link.embedded
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'raises an error when the method does not exist in the resource' do
|
84
|
+
lambda { link.this_method_does_not_exist }.must_raise(NoMethodError)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'responds to missing methods' do
|
88
|
+
resource.expects(:respond_to?).with('embedded').returns(true)
|
89
|
+
link.respond_to?(:embedded).must_equal true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/resource_collection'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe ResourceCollection do
|
6
|
+
let(:entry_point) { stub('Entry point', config: {base_uri: '/'}) }
|
7
|
+
|
8
|
+
let(:representation) do
|
9
|
+
JSON.parse( File.read('test/fixtures/element.json'))
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:resources) do
|
13
|
+
ResourceCollection.new(representation['_embedded'], entry_point)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'is a collection' do
|
17
|
+
ResourceCollection.ancestors.must_include Collection
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'initializes the collection with resources' do
|
21
|
+
resources.must_respond_to :author
|
22
|
+
resources.must_respond_to :episodes
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns resource objects for each resource' do
|
26
|
+
resources.author.must_be_kind_of Resource
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'also builds arras of resource' do
|
30
|
+
resources.episodes.must_be_kind_of Array
|
31
|
+
resources.episodes.first.must_be_kind_of Resource
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -3,66 +3,60 @@ require 'hyperclient/resource'
|
|
3
3
|
|
4
4
|
module Hyperclient
|
5
5
|
describe Resource do
|
6
|
-
let(:
|
7
|
-
File.read('test/fixtures/element.json')
|
8
|
-
end
|
9
|
-
|
10
|
-
let(:parsed_representation) do
|
11
|
-
JSON.parse(representation)
|
12
|
-
end
|
6
|
+
let(:entry_point) { mock('Entry point') }
|
13
7
|
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
describe 'initialize' do
|
9
|
+
it 'initializes its links' do
|
10
|
+
LinkCollection.expects(:new).with({"self" => { "href" => "/orders/523" }}, entry_point)
|
17
11
|
|
18
|
-
|
19
|
-
it 'merges the resource url with the entry point' do
|
20
|
-
resource = Resource.new('/path/to/resource')
|
21
|
-
resource.url.to_s.must_equal 'http://api.example.org/path/to/resource'
|
12
|
+
Resource.new({'_links' => {"self" => { "href" => "/orders/523" } }}, entry_point)
|
22
13
|
end
|
23
14
|
|
24
|
-
it '
|
25
|
-
|
26
|
-
resource.url.to_s.must_equal '/search={terms}'
|
27
|
-
end
|
28
|
-
end
|
15
|
+
it 'initializes its attributes' do
|
16
|
+
Attributes.expects(:new).with({foo: :bar})
|
29
17
|
|
30
|
-
|
31
|
-
before do
|
32
|
-
stub_request(:get, 'http://api.example.org')
|
18
|
+
Resource.new({foo: :bar}, entry_point)
|
33
19
|
end
|
34
20
|
|
35
|
-
it 'initializes
|
36
|
-
|
21
|
+
it 'initializes links' do
|
22
|
+
ResourceCollection.expects(:new).with({"orders" => []}, entry_point)
|
37
23
|
|
38
|
-
|
24
|
+
Resource.new({'_embedded' => {"orders" => [] }}, entry_point)
|
39
25
|
end
|
26
|
+
end
|
40
27
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
resource.name.must_equal 'posts'
|
28
|
+
describe 'accessors' do
|
29
|
+
let(:resource) do
|
30
|
+
Resource.new({}, entry_point)
|
45
31
|
end
|
46
|
-
end
|
47
32
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
33
|
+
describe 'links' do
|
34
|
+
it 'returns a LinkCollection' do
|
35
|
+
resource.links.must_be_kind_of LinkCollection
|
36
|
+
end
|
52
37
|
end
|
53
38
|
|
54
|
-
|
55
|
-
|
56
|
-
|
39
|
+
describe 'attributes' do
|
40
|
+
it 'returns a Attributes' do
|
41
|
+
resource.attributes.must_be_kind_of Attributes
|
42
|
+
end
|
43
|
+
end
|
57
44
|
|
58
|
-
|
45
|
+
describe 'embedded' do
|
46
|
+
it 'returns a ResourceCollection' do
|
47
|
+
resource.embedded.must_be_kind_of ResourceCollection
|
48
|
+
end
|
59
49
|
end
|
50
|
+
end
|
60
51
|
|
61
|
-
|
62
|
-
|
52
|
+
it 'uses its self Link to handle HTTP connections' do
|
53
|
+
self_link = mock('Self Link')
|
54
|
+
self_link.expects(:get)
|
63
55
|
|
64
|
-
|
65
|
-
|
56
|
+
LinkCollection.expects(:new).returns({'self' => self_link})
|
57
|
+
resource = Resource.new({}, entry_point)
|
58
|
+
|
59
|
+
resource.get
|
66
60
|
end
|
67
61
|
end
|
68
62
|
end
|