hyperclient 0.2.0 → 0.3.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/.gitignore +0 -1
- data/.rvmrc +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +96 -0
- data/Guardfile +7 -0
- data/Rakefile +5 -1
- data/Readme.md +19 -9
- data/features/api_navigation.feature +23 -0
- data/features/default_config.feature +19 -0
- data/features/steps/api_navigation.rb +33 -0
- data/features/steps/default_config.rb +29 -0
- data/features/support/api.rb +24 -0
- data/features/support/env.rb +4 -0
- data/features/support/fixtures.rb +43 -0
- data/hyperclient.gemspec +9 -7
- data/lib/faraday/connection.rb +17 -0
- data/lib/faraday/request/digest_authentication.rb +83 -0
- data/lib/hyperclient/attributes.rb +5 -1
- data/lib/hyperclient/collection.rb +7 -1
- data/lib/hyperclient/entry_point.rb +37 -27
- data/lib/hyperclient/link.rb +41 -16
- data/lib/hyperclient/link_collection.rb +1 -0
- data/lib/hyperclient/resource.rb +8 -3
- data/lib/hyperclient/version.rb +1 -1
- data/lib/hyperclient.rb +11 -3
- data/test/faraday/connection_test.rb +29 -0
- data/test/faraday/digest_authentication_test.rb +41 -0
- data/test/hyperclient/entry_point_test.rb +15 -32
- data/test/hyperclient/link_test.rb +93 -12
- data/test/hyperclient_test.rb +12 -0
- data/test/test_helper.rb +1 -1
- metadata +119 -67
- data/lib/hyperclient/http.rb +0 -165
- data/test/hyperclient/http_test.rb +0 -214
data/lib/hyperclient/link.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'hyperclient/http'
|
2
1
|
require 'hyperclient/resource'
|
3
2
|
require 'uri_template'
|
4
3
|
|
@@ -6,11 +5,6 @@ module Hyperclient
|
|
6
5
|
# Internal: The Link is used to let a Resource interact with the API.
|
7
6
|
#
|
8
7
|
class Link
|
9
|
-
extend Forwardable
|
10
|
-
# Public: Delegate all HTTP methods (get, post, put, delete, options and
|
11
|
-
# head) to the http connection.
|
12
|
-
def_delegators :http, :get, :post, :put, :delete, :options, :head
|
13
|
-
|
14
8
|
# Public: Initializes a new Link.
|
15
9
|
#
|
16
10
|
# link - The String with the URI of the link.
|
@@ -23,15 +17,10 @@ module Hyperclient
|
|
23
17
|
@uri_variables = uri_variables
|
24
18
|
end
|
25
19
|
|
26
|
-
# Public: Returns the Resource which the Link is pointing to.
|
27
|
-
def resource
|
28
|
-
@resource ||=Resource.new(http.get, @entry_point)
|
29
|
-
end
|
30
|
-
|
31
20
|
# Public: Indicates if the link is an URITemplate or a regular URI.
|
32
21
|
#
|
33
22
|
# Returns true if it is templated.
|
34
|
-
# Returns false if it
|
23
|
+
# Returns false if it not templated.
|
35
24
|
def templated?
|
36
25
|
!!@link['templated']
|
37
26
|
end
|
@@ -56,12 +45,48 @@ module Hyperclient
|
|
56
45
|
@url ||= URITemplate.new(@link['href']).expand(@uri_variables)
|
57
46
|
end
|
58
47
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
48
|
+
# Public: Returns the Resource which the Link is pointing to.
|
49
|
+
def resource
|
50
|
+
@resource ||=Resource.new(get.body, @entry_point)
|
51
|
+
end
|
52
|
+
|
53
|
+
def connection
|
54
|
+
@entry_point.connection
|
55
|
+
end
|
56
|
+
|
57
|
+
def get
|
58
|
+
connection.get(url)
|
59
|
+
end
|
60
|
+
|
61
|
+
def options
|
62
|
+
connection.run_request(:options, url, nil, nil)
|
63
63
|
end
|
64
64
|
|
65
|
+
def head
|
66
|
+
connection.head(url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete
|
70
|
+
connection.delete(url)
|
71
|
+
end
|
72
|
+
|
73
|
+
def post(params)
|
74
|
+
connection.post(url, params)
|
75
|
+
end
|
76
|
+
|
77
|
+
def put(params)
|
78
|
+
connection.put(url, params)
|
79
|
+
end
|
80
|
+
|
81
|
+
def patch(params)
|
82
|
+
connection.patch(url, params)
|
83
|
+
end
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
"#<#{self.class.name} #{@link}>"
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
65
90
|
# Internal: Delegate the method to the API if it exists.
|
66
91
|
#
|
67
92
|
# This allows `api.links.posts.embedded` instead of
|
@@ -16,6 +16,7 @@ module Hyperclient
|
|
16
16
|
# entry_point - The EntryPoint object to inject the cofnigutation.
|
17
17
|
#
|
18
18
|
def initialize(collection, entry_point)
|
19
|
+
raise "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:inject)
|
19
20
|
@collection = (collection || {}).inject({}) do |hash, (name, link)|
|
20
21
|
hash.update(name => Link.new(link, entry_point))
|
21
22
|
end
|
data/lib/hyperclient/resource.rb
CHANGED
@@ -19,12 +19,13 @@ module Hyperclient
|
|
19
19
|
attr_reader :embedded
|
20
20
|
|
21
21
|
# Public: Delegate all HTTP methods (get, post, put, delete, options and
|
22
|
-
# head) to its
|
22
|
+
# head) to its self link.
|
23
23
|
def_delegators :self_link, :get, :post, :put, :delete, :options, :head
|
24
24
|
|
25
25
|
# Public: Initializes a Resource.
|
26
|
+
#
|
26
27
|
# representation - The hash with the HAL representation of the Resource.
|
27
|
-
# entry_point - The EntryPoint object to inject the
|
28
|
+
# entry_point - The EntryPoint object to inject the configutation.
|
28
29
|
def initialize(representation, entry_point)
|
29
30
|
@links = LinkCollection.new(representation['_links'], entry_point)
|
30
31
|
@embedded = ResourceCollection.new(representation['_embedded'], entry_point)
|
@@ -32,9 +33,13 @@ module Hyperclient
|
|
32
33
|
@entry_point = entry_point
|
33
34
|
end
|
34
35
|
|
36
|
+
def inspect
|
37
|
+
"#<#{self.class.name} self_link:#{self_link.inspect} attributes:#{@attributes.inspect}>"
|
38
|
+
end
|
39
|
+
|
35
40
|
private
|
36
41
|
# Internal: Returns the self Link of the Resource. Used to handle the HTTP
|
37
|
-
#
|
42
|
+
# methods.
|
38
43
|
def self_link
|
39
44
|
@links['self']
|
40
45
|
end
|
data/lib/hyperclient/version.rb
CHANGED
data/lib/hyperclient.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
+
require 'hyperclient/entry_point'
|
2
|
+
require "hyperclient/version"
|
3
|
+
|
1
4
|
# Public: Hyperclient namespace.
|
2
5
|
#
|
3
6
|
module Hyperclient
|
7
|
+
# Public: Convenience method to create new EntryPoints.
|
8
|
+
#
|
9
|
+
# url - A String with the url of the API.
|
10
|
+
#
|
11
|
+
# Returns a Hyperclient::EntryPoint
|
12
|
+
def self.new(url)
|
13
|
+
Hyperclient::EntryPoint.new(url)
|
14
|
+
end
|
4
15
|
end
|
5
|
-
|
6
|
-
require 'hyperclient/entry_point'
|
7
|
-
require "hyperclient/version"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require_relative '../../lib/faraday/connection'
|
3
|
+
|
4
|
+
module Faraday
|
5
|
+
describe Connection do
|
6
|
+
describe 'digest_auth' do
|
7
|
+
let(:connection) do
|
8
|
+
Faraday.new('http://api.example.org/') do |builder|
|
9
|
+
builder.request :url_encoded
|
10
|
+
builder.adapter :net_http
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'inserts the DigestAuth middleware at the top' do
|
15
|
+
connection.digest_auth('user', 'password')
|
16
|
+
|
17
|
+
connection.builder.handlers.first.klass.must_equal Faraday::Request::DigestAuth
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'passes the user and password to the middleware' do
|
21
|
+
connection.digest_auth('user', 'password')
|
22
|
+
|
23
|
+
Faraday::Request::DigestAuth.expects(:new).with(anything, 'user', 'password').returns(stub_everything)
|
24
|
+
|
25
|
+
connection.get('/')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'faraday/request/digest_authentication'
|
3
|
+
|
4
|
+
module Faraday
|
5
|
+
describe Request::DigestAuth do
|
6
|
+
let(:connection) do
|
7
|
+
Faraday.new('http://api.example.org/') do |builder|
|
8
|
+
builder.request :digest, 'USER', 'PASS'
|
9
|
+
builder.adapter :net_http
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'when the server does not return a 401' do
|
14
|
+
it 'does nothing' do
|
15
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
16
|
+
to_return(status: 500, body: 'Foo body')
|
17
|
+
|
18
|
+
response = connection.get('/productions/1')
|
19
|
+
response.body.must_equal 'Foo body'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'when the server returns a 401' do
|
24
|
+
let(:first_call_headers) { 'Digest realm="MyApp", algorithm=MD5' }
|
25
|
+
let(:second_call_headers) { 'Digest username="USER", realm="MyApp", uri="/", algorithm="MD5"' }
|
26
|
+
it 'authenticates using digest' do
|
27
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
28
|
+
with(body: nil).
|
29
|
+
to_return(status: 401, headers: {'www-authenticate' => first_call_headers})
|
30
|
+
|
31
|
+
stub_request(:get, 'http://api.example.org/productions/1').
|
32
|
+
with(body: "{\"foo\":1}",
|
33
|
+
headers: {'Authorization' => %r{second_call_headers}}).
|
34
|
+
to_return(body: '{"resource": "This is the resource"}',
|
35
|
+
headers: {content_type: 'application/json'})
|
36
|
+
|
37
|
+
connection.get('/productions/1')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -3,48 +3,31 @@ require 'hyperclient/entry_point'
|
|
3
3
|
|
4
4
|
module Hyperclient
|
5
5
|
describe EntryPoint do
|
6
|
-
let(:
|
6
|
+
let(:entry_point) do
|
7
7
|
EntryPoint.new 'http://my.api.org'
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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'
|
10
|
+
describe 'connection' do
|
11
|
+
it 'creates a Faraday connection with the entry point url' do
|
12
|
+
entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
|
18
13
|
end
|
19
14
|
|
20
|
-
it '
|
21
|
-
|
22
|
-
|
23
|
-
api = EntryPoint.new('http://my.api.org', options)
|
24
|
-
|
25
|
-
api.config[:headers].must_include 'accept-encoding'
|
15
|
+
it 'creates a Faraday connection with the default headers' do
|
16
|
+
entry_point.headers['Content-Type'].must_equal 'application/json'
|
17
|
+
entry_point.headers['Accept'].must_equal 'application/json'
|
26
18
|
end
|
27
19
|
|
28
|
-
it '
|
29
|
-
|
30
|
-
|
31
|
-
|
20
|
+
it 'creates a Faraday connection with the default block' do
|
21
|
+
handlers = entry_point.connection.builder.handlers
|
22
|
+
handlers.must_include FaradayMiddleware::EncodeJson
|
23
|
+
handlers.must_include FaradayMiddleware::ParseJson
|
24
|
+
handlers.must_include Faraday::Adapter::NetHttp
|
32
25
|
end
|
33
26
|
end
|
34
27
|
|
35
|
-
describe '
|
36
|
-
it '
|
37
|
-
|
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)
|
28
|
+
describe 'initialize' do
|
29
|
+
it 'sets a Link with the entry point url' do
|
30
|
+
entry_point.url.must_equal 'http://my.api.org'
|
48
31
|
end
|
49
32
|
end
|
50
33
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
require_relative '../test_helper'
|
2
2
|
require 'hyperclient/link'
|
3
|
+
require 'hyperclient/entry_point'
|
3
4
|
|
4
5
|
module Hyperclient
|
5
6
|
describe Link do
|
6
|
-
let(:entry_point)
|
7
|
+
let(:entry_point) do
|
8
|
+
EntryPoint.new('http://api.example.org/')
|
9
|
+
end
|
7
10
|
|
8
11
|
describe 'templated?' do
|
9
12
|
it 'returns true if the link is templated' do
|
@@ -19,17 +22,6 @@ module Hyperclient
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
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
25
|
describe 'expand' do
|
34
26
|
it 'buils a Link with the templated URI representation' do
|
35
27
|
link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
|
@@ -64,6 +56,95 @@ module Hyperclient
|
|
64
56
|
end
|
65
57
|
end
|
66
58
|
|
59
|
+
describe 'resource' do
|
60
|
+
it 'builds a resource with the link href representation' do
|
61
|
+
Resource.expects(:new).with({}, entry_point)
|
62
|
+
|
63
|
+
link = Link.new({'href' => '/'}, entry_point)
|
64
|
+
link.expects(:get).returns(mock(body: {}))
|
65
|
+
|
66
|
+
link.resource
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe 'connection' do
|
71
|
+
it 'returns the entry point connection' do
|
72
|
+
Link.new({}, entry_point).connection.must_equal entry_point.connection
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'get' do
|
77
|
+
it 'sends a GET request with the link url' do
|
78
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
79
|
+
|
80
|
+
entry_point.connection.expects(:get).with('/productions/1')
|
81
|
+
link.get
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'options' do
|
86
|
+
it 'sends a OPTIONS request with the link url' do
|
87
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
88
|
+
|
89
|
+
entry_point.connection.expects(:run_request).with(:options, '/productions/1', nil, nil)
|
90
|
+
link.options
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'head' do
|
95
|
+
it 'sends a HEAD request with the link url' do
|
96
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
97
|
+
|
98
|
+
entry_point.connection.expects(:head).with('/productions/1')
|
99
|
+
link.head
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'delete' do
|
104
|
+
it 'sends a DELETE request with the link url' do
|
105
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
106
|
+
|
107
|
+
entry_point.connection.expects(:delete).with('/productions/1')
|
108
|
+
link.delete
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'post' do
|
113
|
+
it 'sends a POST request with the link url and params' do
|
114
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
115
|
+
|
116
|
+
entry_point.connection.expects(:post).with('/productions/1', {'foo' => 'bar'})
|
117
|
+
link.post({'foo' => 'bar'})
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe 'put' do
|
122
|
+
it 'sends a PUT request with the link url and params' do
|
123
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
124
|
+
|
125
|
+
entry_point.connection.expects(:put).with('/productions/1', {'foo' => 'bar'})
|
126
|
+
link.put({'foo' => 'bar'})
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'patch' do
|
131
|
+
it 'sends a PATCH request with the link url and params' do
|
132
|
+
link = Link.new({'href' => '/productions/1'}, entry_point)
|
133
|
+
|
134
|
+
entry_point.connection.expects(:patch).with('/productions/1', {'foo' => 'bar'})
|
135
|
+
link.patch({'foo' => 'bar'})
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'inspect' do
|
140
|
+
it 'outputs a custom-friendly output' do
|
141
|
+
link = Link.new({'href'=>'/productions/1'}, 'foo')
|
142
|
+
|
143
|
+
link.inspect.must_include 'Link'
|
144
|
+
link.inspect.must_include '"href"=>"/productions/1"'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
67
148
|
describe 'method_missing' do
|
68
149
|
before do
|
69
150
|
stub_request(:get, "http://myapi.org/orders").
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'hyperclient'
|
3
|
+
|
4
|
+
describe Hyperclient do
|
5
|
+
describe 'new' do
|
6
|
+
it 'creates a new EntryPoint with the url' do
|
7
|
+
Hyperclient::EntryPoint.expects(:new).with('http://api.example.org')
|
8
|
+
|
9
|
+
Hyperclient.new('http://api.example.org')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|