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