hyperclient 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +55 -0
- data/.travis.yml +2 -0
- data/CHANGELOG.md +33 -10
- data/CONTRIBUTING.md +117 -0
- data/Gemfile +2 -1
- data/Guardfile +6 -6
- data/LICENSE +2 -2
- data/README.md +138 -0
- data/Rakefile +5 -3
- data/UPGRADING.md +37 -0
- data/examples/splines_api.rb +22 -0
- data/features/steps/api_navigation.rb +8 -8
- data/features/steps/default_config.rb +6 -6
- data/features/support/api.rb +4 -4
- data/features/support/fixtures.rb +1 -1
- data/hyperclient.gemspec +9 -8
- data/lib/faraday/connection.rb +2 -2
- data/lib/hyperclient.rb +1 -1
- data/lib/hyperclient/attributes.rb +1 -1
- data/lib/hyperclient/collection.rb +3 -3
- data/lib/hyperclient/curie.rb +49 -0
- data/lib/hyperclient/entry_point.rb +6 -4
- data/lib/hyperclient/link.rb +70 -58
- data/lib/hyperclient/link_collection.rb +36 -11
- data/lib/hyperclient/resource.rb +49 -18
- data/lib/hyperclient/resource_collection.rb +2 -1
- data/lib/hyperclient/version.rb +1 -1
- data/test/fixtures/element.json +12 -1
- data/test/hyperclient/attributes_test.rb +2 -2
- data/test/hyperclient/collection_test.rb +6 -7
- data/test/hyperclient/curie_test.rb +34 -0
- data/test/hyperclient/entry_point_test.rb +3 -2
- data/test/hyperclient/link_collection_test.rb +26 -5
- data/test/hyperclient/link_test.rb +111 -86
- data/test/hyperclient/resource_collection_test.rb +2 -2
- data/test/hyperclient/resource_test.rb +67 -30
- data/test/test_helper.rb +2 -2
- metadata +54 -39
- data/Gemfile.lock +0 -112
- data/MIT-LICENSE +0 -20
- data/Readme.md +0 -180
- data/examples/cyberscore.rb +0 -76
- data/examples/hal_shop.rb +0 -53
- data/lib/faraday/request/digest_authentication.rb +0 -85
- data/test/faraday/digest_authentication_test.rb +0 -41
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'hyperclient/collection'
|
2
2
|
require 'hyperclient/link'
|
3
|
+
require 'hyperclient/curie'
|
3
4
|
|
4
5
|
module Hyperclient
|
5
6
|
# Public: A wrapper class to easily acces the links in a Resource.
|
@@ -12,30 +13,54 @@ module Hyperclient
|
|
12
13
|
class LinkCollection < Collection
|
13
14
|
# Public: Initializes a LinkCollection.
|
14
15
|
#
|
15
|
-
# collection
|
16
|
+
# collection - The Hash with the links.
|
17
|
+
# curies - Link curies.
|
16
18
|
# entry_point - The EntryPoint object to inject the configuration.
|
17
|
-
def initialize(collection, entry_point)
|
18
|
-
|
19
|
+
def initialize(collection, curies, entry_point)
|
20
|
+
fail "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:collect)
|
19
21
|
|
20
|
-
@
|
21
|
-
|
22
|
+
@curies = (curies || {}).reduce({}) do |hash, curie_hash|
|
23
|
+
curie = build_curie(curie_hash, entry_point)
|
24
|
+
hash.update(curie.name => curie)
|
25
|
+
end
|
26
|
+
|
27
|
+
@collection = (collection || {}).reduce({}) do |hash, (name, link)|
|
28
|
+
hash.update(name => build_link(name, link, @curies, entry_point))
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
25
32
|
private
|
33
|
+
|
26
34
|
# Internal: Creates links from the response hash.
|
27
35
|
#
|
28
36
|
# link_or_links - A Hash or an Array of hashes with the links to build.
|
29
|
-
# entry_point
|
37
|
+
# entry_point - The EntryPoint object to inject the configuration.
|
38
|
+
# curies - Optional curies for templated links.
|
30
39
|
#
|
31
40
|
# Returns a Link or an array of Links when given an Array.
|
32
|
-
def build_link(link_or_links, entry_point)
|
41
|
+
def build_link(name, link_or_links, curies, entry_point)
|
33
42
|
return unless link_or_links
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
43
|
+
if link_or_links.respond_to?(:to_ary)
|
44
|
+
link_or_links.map do |link|
|
45
|
+
build_link(name, link, curies, entry_point)
|
46
|
+
end
|
47
|
+
elsif (curie_parts = /(?<ns>[^:]+):(?<short_name>.+)/.match(name))
|
48
|
+
curie = curies[curie_parts[:ns]]
|
49
|
+
link_or_links['href'] = curie.expand(link_or_links['href']) if curie
|
50
|
+
Link.new(name, link_or_links, entry_point)
|
51
|
+
else
|
52
|
+
Link.new(name, link_or_links, entry_point)
|
38
53
|
end
|
39
54
|
end
|
55
|
+
|
56
|
+
# Internal: Creates a curie from the response hash.
|
57
|
+
#
|
58
|
+
# curie_hash - A Hash with the curie.
|
59
|
+
# entry_point - The EntryPoint object to inject the configuration.
|
60
|
+
#
|
61
|
+
# Returns a Link or an array of Links when given an Array.
|
62
|
+
def build_curie(curie_hash, entry_point)
|
63
|
+
Curie.new(curie_hash, entry_point)
|
64
|
+
end
|
40
65
|
end
|
41
66
|
end
|
data/lib/hyperclient/resource.rb
CHANGED
@@ -10,53 +10,84 @@ module Hyperclient
|
|
10
10
|
extend Forwardable
|
11
11
|
|
12
12
|
# Public: Returns the attributes of the Resource as Attributes.
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :_attributes
|
14
14
|
|
15
15
|
# Public: Returns the links of the Resource as a LinkCollection.
|
16
|
-
attr_reader :
|
16
|
+
attr_reader :_links
|
17
17
|
|
18
18
|
# Public: Returns the embedded resource of the Resource as a
|
19
19
|
# ResourceCollection.
|
20
|
-
attr_reader :
|
20
|
+
attr_reader :_embedded
|
21
21
|
|
22
22
|
# Public: Returns the response object for the HTTP request that created this
|
23
23
|
# resource, if one exists.
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :_response
|
25
25
|
|
26
26
|
# Public: Delegate all HTTP methods (get, post, put, delete, options and
|
27
27
|
# head) to its self link.
|
28
|
-
def_delegators :
|
28
|
+
def_delegators :_self_link, :_get, :_post, :_put, :_delete, :_options, :_head
|
29
29
|
|
30
30
|
# Public: Initializes a Resource.
|
31
31
|
#
|
32
32
|
# representation - The hash with the HAL representation of the Resource.
|
33
33
|
# entry_point - The EntryPoint object to inject the configutation.
|
34
|
-
def initialize(representation, entry_point, response=nil)
|
34
|
+
def initialize(representation, entry_point, response = nil)
|
35
35
|
representation = representation ? representation.dup : {}
|
36
|
-
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
36
|
+
links = representation['_links'] || {}
|
37
|
+
@_links = LinkCollection.new(links, links['curies'], entry_point)
|
38
|
+
@_embedded = ResourceCollection.new(representation['_embedded'], entry_point)
|
39
|
+
@_attributes = Attributes.new(representation)
|
40
|
+
@_entry_point = entry_point
|
41
|
+
@_response = response
|
41
42
|
end
|
42
43
|
|
43
44
|
def inspect
|
44
|
-
"#<#{self.class.name} self_link:#{
|
45
|
+
"#<#{self.class.name} self_link:#{_self_link.inspect} attributes:#{@_attributes.inspect}>"
|
45
46
|
end
|
46
47
|
|
47
|
-
def
|
48
|
-
|
48
|
+
def _success?
|
49
|
+
_response && _response.success?
|
49
50
|
end
|
50
51
|
|
51
|
-
def
|
52
|
-
|
52
|
+
def _status
|
53
|
+
_response && _response.status
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](name)
|
57
|
+
send(name) if respond_to?(name)
|
53
58
|
end
|
54
59
|
|
55
60
|
private
|
61
|
+
|
56
62
|
# Internal: Returns the self Link of the Resource. Used to handle the HTTP
|
57
63
|
# methods.
|
58
|
-
def
|
59
|
-
@
|
64
|
+
def _self_link
|
65
|
+
@_links['self']
|
66
|
+
end
|
67
|
+
|
68
|
+
# Internal: Delegate the method to various elements of the resource.
|
69
|
+
#
|
70
|
+
# This allows `api.posts` instead of `api.links.posts.resource`
|
71
|
+
# as well as api.posts(id: 1) assuming posts is a link.
|
72
|
+
def method_missing(method, *args, &block)
|
73
|
+
if args.any? && args.first.is_a?(Hash)
|
74
|
+
_links.send(method, [], &block)._expand(*args)
|
75
|
+
else
|
76
|
+
[:_attributes, :_embedded, :_links].each do |target|
|
77
|
+
target = send(target)
|
78
|
+
return target.send(method, *args, &block) if target.respond_to?(method.to_s)
|
79
|
+
end
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: Accessory method to allow the resource respond to
|
85
|
+
# methods that will hit method_missing.
|
86
|
+
def respond_to_missing?(method, include_private = false)
|
87
|
+
[:_attributes, :_embedded, :_links].each do |target|
|
88
|
+
return true if send(target).respond_to?(method, include_private)
|
89
|
+
end
|
90
|
+
false
|
60
91
|
end
|
61
92
|
end
|
62
93
|
end
|
@@ -18,12 +18,13 @@ module Hyperclient
|
|
18
18
|
#
|
19
19
|
def initialize(collection, entry_point)
|
20
20
|
@entry_point = entry_point
|
21
|
-
@collection = (collection || {}).
|
21
|
+
@collection = (collection || {}).reduce({}) do |hash, (name, resource)|
|
22
22
|
hash.update(name => build_resource(resource))
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
|
+
|
27
28
|
def build_resource(representation)
|
28
29
|
return representation.map(&method(:build_resource)) if representation.is_a?(Array)
|
29
30
|
|
data/lib/hyperclient/version.rb
CHANGED
data/test/fixtures/element.json
CHANGED
@@ -15,7 +15,18 @@
|
|
15
15
|
"href": "/gizmos/2"
|
16
16
|
}
|
17
17
|
],
|
18
|
-
"
|
18
|
+
"curies" : [
|
19
|
+
{
|
20
|
+
"name": "image",
|
21
|
+
"href": "/images/{rel}",
|
22
|
+
"templated": true
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"null_link": null,
|
26
|
+
"image:thumbnail": {
|
27
|
+
"href": "thumbnails/{version}.jpg",
|
28
|
+
"templated": true
|
29
|
+
}
|
19
30
|
},
|
20
31
|
"title": "Real World ASP.NET MVC3",
|
21
32
|
"description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...",
|
@@ -4,7 +4,7 @@ require 'hyperclient/attributes'
|
|
4
4
|
module Hyperclient
|
5
5
|
describe Attributes do
|
6
6
|
let(:representation) do
|
7
|
-
JSON.parse(
|
7
|
+
JSON.parse(File.read('test/fixtures/element.json'))
|
8
8
|
end
|
9
9
|
|
10
10
|
let(:attributes) do
|
@@ -24,7 +24,7 @@ module Hyperclient
|
|
24
24
|
attributes.permitted.must_equal true
|
25
25
|
|
26
26
|
attributes.must_respond_to :title
|
27
|
-
attributes.title.must_equal
|
27
|
+
attributes.title.must_equal 'Real World ASP.NET MVC3'
|
28
28
|
end
|
29
29
|
|
30
30
|
# Underscores should be allowed per http://tools.ietf.org/html/draft-kelly-json-hal#appendix-B.4
|
@@ -4,7 +4,7 @@ require 'hyperclient/resource'
|
|
4
4
|
module Hyperclient
|
5
5
|
describe Collection do
|
6
6
|
let(:representation) do
|
7
|
-
JSON.parse(
|
7
|
+
JSON.parse(File.read('test/fixtures/element.json'))
|
8
8
|
end
|
9
9
|
|
10
10
|
let(:collection) do
|
@@ -28,11 +28,11 @@ module Hyperclient
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'acts as enumerable' do
|
31
|
-
names = collection.map do |name,
|
31
|
+
names = collection.map do |name, _value|
|
32
32
|
name
|
33
33
|
end
|
34
34
|
|
35
|
-
names.must_equal
|
35
|
+
names.must_equal %w(_links title description permitted _hidden_attribute _embedded)
|
36
36
|
end
|
37
37
|
|
38
38
|
describe '#to_hash' do
|
@@ -53,16 +53,16 @@ module Hyperclient
|
|
53
53
|
|
54
54
|
describe '#fetch' do
|
55
55
|
it 'returns the value for keys that exist' do
|
56
|
-
collection.fetch('title').must_equal
|
56
|
+
collection.fetch('title').must_equal 'Real World ASP.NET MVC3'
|
57
57
|
end
|
58
58
|
|
59
59
|
it 'raises an error for missing keys' do
|
60
|
-
|
60
|
+
proc { collection.fetch('missing key') }.must_raise KeyError
|
61
61
|
end
|
62
62
|
|
63
63
|
describe 'with a default value' do
|
64
64
|
it 'returns the value for keys that exist' do
|
65
|
-
collection.fetch('title', 'default').must_equal
|
65
|
+
collection.fetch('title', 'default').must_equal 'Real World ASP.NET MVC3'
|
66
66
|
end
|
67
67
|
|
68
68
|
it 'returns the default value for missing keys' do
|
@@ -73,4 +73,3 @@ module Hyperclient
|
|
73
73
|
|
74
74
|
end
|
75
75
|
end
|
76
|
-
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/curie'
|
3
|
+
require 'hyperclient/entry_point'
|
4
|
+
|
5
|
+
module Hyperclient
|
6
|
+
describe Curie do
|
7
|
+
let(:entry_point) do
|
8
|
+
EntryPoint.new('http://api.example.org/')
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'templated?' do
|
12
|
+
it 'returns true if the curie is templated' do
|
13
|
+
curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point)
|
14
|
+
|
15
|
+
curie.templated?.must_equal true
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns false if the curie is not templated' do
|
19
|
+
curie = Curie.new({ 'name' => 'image' }, entry_point)
|
20
|
+
|
21
|
+
curie.templated?.must_equal false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:curie) do
|
26
|
+
Curie.new({ 'name' => 'image', 'href' => '/images/{?rel}', 'templated' => true }, entry_point)
|
27
|
+
end
|
28
|
+
describe '_name' do
|
29
|
+
it 'returns curie name' do
|
30
|
+
curie.name.must_equal 'image'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -19,6 +19,7 @@ module Hyperclient
|
|
19
19
|
|
20
20
|
it 'creates a Faraday connection with the default block' do
|
21
21
|
handlers = entry_point.connection.builder.handlers
|
22
|
+
handlers.must_include FaradayMiddleware::FollowRedirects
|
22
23
|
handlers.must_include FaradayMiddleware::EncodeJson
|
23
24
|
handlers.must_include FaradayMiddleware::ParseJson
|
24
25
|
handlers.must_include Faraday::Adapter::NetHttp
|
@@ -27,8 +28,8 @@ module Hyperclient
|
|
27
28
|
|
28
29
|
describe 'initialize' do
|
29
30
|
it 'sets a Link with the entry point url' do
|
30
|
-
entry_point.
|
31
|
+
entry_point._url.must_equal 'http://my.api.org'
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
34
|
-
end
|
35
|
+
end
|
@@ -3,14 +3,14 @@ require 'hyperclient/link_collection'
|
|
3
3
|
|
4
4
|
module Hyperclient
|
5
5
|
describe LinkCollection do
|
6
|
-
let(:entry_point) { stub('Entry point', config: {base_uri: '/'}) }
|
6
|
+
let(:entry_point) { stub('Entry point', config: { base_uri: '/' }) }
|
7
7
|
|
8
8
|
let(:representation) do
|
9
|
-
JSON.parse(
|
9
|
+
JSON.parse(File.read('test/fixtures/element.json'))
|
10
10
|
end
|
11
11
|
|
12
12
|
let(:links) do
|
13
|
-
LinkCollection.new(representation['_links'], entry_point)
|
13
|
+
LinkCollection.new(representation['_links'], representation['_links']['curies'], entry_point)
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'is a collection' do
|
@@ -30,7 +30,28 @@ module Hyperclient
|
|
30
30
|
links['gizmos'].must_be_kind_of Array
|
31
31
|
end
|
32
32
|
|
33
|
-
describe
|
33
|
+
describe 'plain link' do
|
34
|
+
let(:plain_link) { links.self }
|
35
|
+
it 'must be correct' do
|
36
|
+
plain_link._url.must_equal '/productions/1'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'templated link' do
|
41
|
+
let(:templated_link) { links.filter }
|
42
|
+
it 'must expand' do
|
43
|
+
templated_link._expand(filter: 'gizmos')._url.must_equal '/productions/1?categories=gizmos'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'curied link collection' do
|
48
|
+
let(:curied_link) { links['image:thumbnail'] }
|
49
|
+
it 'must expand' do
|
50
|
+
curied_link._expand(version: 'small')._url.must_equal '/images/thumbnails/small.jpg'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'array of links' do
|
34
55
|
let(:gizmos) { links.gizmos }
|
35
56
|
|
36
57
|
it 'should have 2 items' do
|
@@ -44,7 +65,7 @@ module Hyperclient
|
|
44
65
|
end
|
45
66
|
end
|
46
67
|
|
47
|
-
describe
|
68
|
+
describe 'null link value' do
|
48
69
|
let(:null_link) { links.null_link }
|
49
70
|
it 'must be nil' do
|
50
71
|
null_link.must_be_nil
|
@@ -10,190 +10,196 @@ module Hyperclient
|
|
10
10
|
|
11
11
|
%w(type deprecation name profile title hreflang).each do |prop|
|
12
12
|
describe prop do
|
13
|
-
it
|
14
|
-
link = Link.new({prop => 'value'}, entry_point)
|
15
|
-
link.send(prop).must_equal 'value'
|
13
|
+
it 'returns the property value' do
|
14
|
+
link = Link.new('key', { prop => 'value' }, entry_point)
|
15
|
+
link.send("_#{prop}").must_equal 'value'
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'returns nil if the property is not present' do
|
19
|
-
link = Link.new({}, entry_point)
|
20
|
-
link.send(prop).must_equal nil
|
19
|
+
link = Link.new('key', {}, entry_point)
|
20
|
+
link.send("_#{prop}").must_equal nil
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
describe '
|
25
|
+
describe '_templated?' do
|
26
26
|
it 'returns true if the link is templated' do
|
27
|
-
link = Link.new({'templated' => true}, entry_point)
|
27
|
+
link = Link.new('key', { 'templated' => true }, entry_point)
|
28
28
|
|
29
|
-
link.
|
29
|
+
link._templated?.must_equal true
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'returns false if the link is not templated' do
|
33
|
-
link = Link.new({}, entry_point)
|
33
|
+
link = Link.new('key', {}, entry_point)
|
34
34
|
|
35
|
-
link.
|
35
|
+
link._templated?.must_equal false
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
describe '
|
39
|
+
describe '_variables' do
|
40
40
|
it 'returns a list of required variables' do
|
41
|
-
link = Link.new({'href' => '/orders{?id,owner}', 'templated' => true}, entry_point)
|
41
|
+
link = Link.new('key', { 'href' => '/orders{?id,owner}', 'templated' => true }, entry_point)
|
42
42
|
|
43
|
-
link.
|
43
|
+
link._variables.must_equal %w(id owner)
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'returns an empty array for untemplated links' do
|
47
|
-
link = Link.new({'href' => '/orders'}, entry_point)
|
47
|
+
link = Link.new('key', { 'href' => '/orders' }, entry_point)
|
48
48
|
|
49
|
-
link.
|
49
|
+
link._variables.must_equal []
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
describe '
|
53
|
+
describe '_expand' do
|
54
54
|
it 'buils a Link with the templated URI representation' do
|
55
|
-
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
55
|
+
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
56
56
|
|
57
|
-
Link.expects(:new).with(anything, entry_point,
|
58
|
-
link.
|
57
|
+
Link.expects(:new).with('key', anything, entry_point, id: '1')
|
58
|
+
link._expand(id: '1')
|
59
59
|
end
|
60
60
|
|
61
61
|
it 'raises if no uri variables are given' do
|
62
|
-
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
63
|
-
lambda { link.
|
62
|
+
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
63
|
+
lambda { link._expand }.must_raise ArgumentError
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
describe '
|
67
|
+
describe '_url' do
|
68
68
|
it 'raises when missing required uri_variables' do
|
69
|
-
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
69
|
+
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
70
70
|
|
71
|
-
lambda { link.
|
71
|
+
lambda { link._url }.must_raise MissingURITemplateVariablesException
|
72
72
|
end
|
73
73
|
|
74
74
|
it 'expands an uri template with variables' do
|
75
|
-
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point,
|
75
|
+
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1)
|
76
76
|
|
77
|
-
link.
|
77
|
+
link._url.must_equal '/orders?id=1'
|
78
78
|
end
|
79
79
|
|
80
80
|
it 'returns the link when no uri template' do
|
81
|
-
link = Link.new({'href' => '/orders'}, entry_point)
|
82
|
-
link.
|
81
|
+
link = Link.new('key', { 'href' => '/orders' }, entry_point)
|
82
|
+
link._url.must_equal '/orders'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'aliases to_s to _url' do
|
86
|
+
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1)
|
87
|
+
|
88
|
+
link.to_s.must_equal '/orders?id=1'
|
83
89
|
end
|
84
90
|
end
|
85
91
|
|
86
|
-
describe '
|
92
|
+
describe '_resource' do
|
87
93
|
it 'builds a resource with the link href representation' do
|
88
94
|
mock_response = mock(body: {}, success?: true)
|
89
95
|
|
90
96
|
Resource.expects(:new).with({}, entry_point, mock_response)
|
91
97
|
|
92
|
-
link = Link.new({'href' => '/'}, entry_point)
|
93
|
-
link.expects(:
|
98
|
+
link = Link.new('key', { 'href' => '/' }, entry_point)
|
99
|
+
link.expects(:_get).returns(mock_response)
|
94
100
|
|
95
|
-
link.
|
101
|
+
link._resource
|
96
102
|
end
|
97
103
|
|
98
|
-
it
|
104
|
+
it 'has an empty body when the response fails' do
|
99
105
|
mock_response = mock(success?: false)
|
100
106
|
|
101
107
|
Resource.expects(:new).with(nil, entry_point, mock_response)
|
102
108
|
|
103
|
-
link = Link.new({'href' => '/'}, entry_point)
|
104
|
-
link.expects(:
|
109
|
+
link = Link.new('key', { 'href' => '/' }, entry_point)
|
110
|
+
link.expects(:_get).returns(mock_response)
|
105
111
|
|
106
|
-
link.
|
112
|
+
link._resource
|
107
113
|
end
|
108
114
|
end
|
109
115
|
|
110
|
-
describe '
|
116
|
+
describe '_connection' do
|
111
117
|
it 'returns the entry point connection' do
|
112
|
-
Link.new({}, entry_point).
|
118
|
+
Link.new('key', {}, entry_point)._connection.must_equal entry_point.connection
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
116
122
|
describe 'get' do
|
117
123
|
it 'sends a GET request with the link url' do
|
118
|
-
link = Link.new({'href' => '/productions/1'}, entry_point)
|
124
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
119
125
|
|
120
126
|
entry_point.connection.expects(:get).with('/productions/1')
|
121
|
-
link.
|
127
|
+
link._get.inspect
|
122
128
|
end
|
123
129
|
end
|
124
130
|
|
125
|
-
describe '
|
131
|
+
describe '_options' do
|
126
132
|
it 'sends a OPTIONS request with the link url' do
|
127
|
-
link = Link.new({'href' => '/productions/1'}, entry_point)
|
133
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
128
134
|
|
129
135
|
entry_point.connection.expects(:run_request).with(:options, '/productions/1', nil, nil)
|
130
|
-
link.
|
136
|
+
link._options.inspect
|
131
137
|
end
|
132
138
|
end
|
133
139
|
|
134
|
-
describe '
|
140
|
+
describe '_head' do
|
135
141
|
it 'sends a HEAD request with the link url' do
|
136
|
-
link = Link.new({'href' => '/productions/1'}, entry_point)
|
142
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
137
143
|
|
138
144
|
entry_point.connection.expects(:head).with('/productions/1')
|
139
|
-
link.
|
145
|
+
link._head.inspect
|
140
146
|
end
|
141
147
|
end
|
142
148
|
|
143
|
-
describe '
|
149
|
+
describe '_delete' do
|
144
150
|
it 'sends a DELETE request with the link url' do
|
145
|
-
link = Link.new({'href' => '/productions/1'}, entry_point)
|
151
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
146
152
|
|
147
153
|
entry_point.connection.expects(:delete).with('/productions/1')
|
148
|
-
link.
|
154
|
+
link._delete.inspect
|
149
155
|
end
|
150
156
|
end
|
151
157
|
|
152
|
-
describe '
|
153
|
-
let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
|
158
|
+
describe '_post' do
|
159
|
+
let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
|
154
160
|
|
155
161
|
it 'sends a POST request with the link url and params' do
|
156
|
-
entry_point.connection.expects(:post).with('/productions/1',
|
157
|
-
link.
|
162
|
+
entry_point.connection.expects(:post).with('/productions/1', 'foo' => 'bar')
|
163
|
+
link._post('foo' => 'bar').inspect
|
158
164
|
end
|
159
165
|
|
160
166
|
it 'defaults params to an empty hash' do
|
161
167
|
entry_point.connection.expects(:post).with('/productions/1', {})
|
162
|
-
link.
|
168
|
+
link._post.inspect
|
163
169
|
end
|
164
170
|
end
|
165
171
|
|
166
|
-
describe '
|
167
|
-
let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
|
172
|
+
describe '_put' do
|
173
|
+
let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
|
168
174
|
|
169
175
|
it 'sends a PUT request with the link url and params' do
|
170
|
-
entry_point.connection.expects(:put).with('/productions/1',
|
171
|
-
link.
|
176
|
+
entry_point.connection.expects(:put).with('/productions/1', 'foo' => 'bar')
|
177
|
+
link._put('foo' => 'bar').inspect
|
172
178
|
end
|
173
179
|
|
174
180
|
it 'defaults params to an empty hash' do
|
175
181
|
entry_point.connection.expects(:put).with('/productions/1', {})
|
176
|
-
link.
|
182
|
+
link._put.inspect
|
177
183
|
end
|
178
184
|
end
|
179
185
|
|
180
|
-
describe '
|
181
|
-
let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
|
186
|
+
describe '_patch' do
|
187
|
+
let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
|
182
188
|
|
183
189
|
it 'sends a PATCH request with the link url and params' do
|
184
|
-
entry_point.connection.expects(:patch).with('/productions/1',
|
185
|
-
link.
|
190
|
+
entry_point.connection.expects(:patch).with('/productions/1', 'foo' => 'bar')
|
191
|
+
link._patch('foo' => 'bar').inspect
|
186
192
|
end
|
187
193
|
|
188
194
|
it 'defaults params to an empty hash' do
|
189
195
|
entry_point.connection.expects(:patch).with('/productions/1', {})
|
190
|
-
link.
|
196
|
+
link._patch.inspect
|
191
197
|
end
|
192
198
|
end
|
193
199
|
|
194
200
|
describe 'inspect' do
|
195
201
|
it 'outputs a custom-friendly output' do
|
196
|
-
link = Link.new({'href'=>'/productions/1'}, 'foo')
|
202
|
+
link = Link.new('key', { 'href' => '/productions/1' }, 'foo')
|
197
203
|
|
198
204
|
link.inspect.must_include 'Link'
|
199
205
|
link.inspect.must_include '"href"=>"/productions/1"'
|
@@ -201,34 +207,53 @@ module Hyperclient
|
|
201
207
|
end
|
202
208
|
|
203
209
|
describe 'method_missing' do
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
210
|
+
describe 'delegation' do
|
211
|
+
it 'delegates when link key matches' do
|
212
|
+
resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point)
|
213
|
+
stub_request(:get, 'http://api.example.org/orders').to_return(body: { '_embedded' => { 'orders' => [{ 'id' => 1 }] } })
|
214
|
+
resource.orders._embedded.orders.first.id.must_equal 1
|
215
|
+
resource.orders.first.id.must_equal 1
|
216
|
+
end
|
217
|
+
|
218
|
+
it "doesn't delegate when link key doesn't match" do
|
219
|
+
resource = Resource.new({ '_links' => { 'foos' => { 'href' => '/orders' } } }, entry_point)
|
220
|
+
stub_request(:get, 'http://api.example.org/orders').to_return(body: { '_embedded' => { 'orders' => [{ 'id' => 1 }] } })
|
221
|
+
resource.foos._embedded.orders.first.id.must_equal 1
|
222
|
+
resource.foos.first.must_equal nil
|
223
|
+
end
|
208
224
|
end
|
209
225
|
|
210
|
-
|
211
|
-
|
226
|
+
describe 'resource' do
|
227
|
+
before do
|
228
|
+
stub_request(:get, 'http://myapi.org/orders')
|
229
|
+
.to_return(body: '{"resource": "This is the resource"}')
|
230
|
+
Resource.stubs(:new).returns(resource)
|
231
|
+
end
|
212
232
|
|
213
|
-
|
214
|
-
|
215
|
-
resource.expects(:embedded)
|
233
|
+
let(:resource) { mock('Resource') }
|
234
|
+
let(:link) { Link.new('orders', { 'href' => 'http://myapi.org/orders' }, entry_point) }
|
216
235
|
|
217
|
-
|
218
|
-
|
236
|
+
it 'delegates unkown methods to the resource' do
|
237
|
+
Resource.expects(:new).returns(resource).at_least_once
|
238
|
+
resource.expects(:embedded)
|
219
239
|
|
220
|
-
|
221
|
-
|
222
|
-
end
|
240
|
+
link.embedded
|
241
|
+
end
|
223
242
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
end
|
243
|
+
it 'raises an error when the method does not exist in the resource' do
|
244
|
+
lambda { link.this_method_does_not_exist }.must_raise NoMethodError
|
245
|
+
end
|
228
246
|
|
229
|
-
|
230
|
-
|
231
|
-
|
247
|
+
it 'responds to missing methods' do
|
248
|
+
resource.expects(:respond_to?).with('orders').returns(false)
|
249
|
+
resource.expects(:respond_to?).with('embedded').returns(true)
|
250
|
+
link.respond_to?(:embedded).must_equal true
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'does not delegate to_ary to resource' do
|
254
|
+
resource.expects(:to_ary).never
|
255
|
+
[[link, link]].flatten.must_equal [link, link]
|
256
|
+
end
|
232
257
|
end
|
233
258
|
end
|
234
259
|
end
|