hyperclient 1.0.1 → 2.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/coverage.yml +21 -0
- data/.github/workflows/danger-comment.yml +11 -0
- data/.github/workflows/danger.yml +13 -0
- data/.github/workflows/lint.yml +16 -0
- data/.github/workflows/test.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +40 -31
- data/AGENTS.md +26 -0
- data/CHANGELOG.md +21 -3
- data/CONTRIBUTING.md +11 -1
- data/Dangerfile +2 -0
- data/Gemfile +10 -7
- data/README.md +49 -44
- data/RELEASING.md +47 -41
- data/Rakefile +0 -8
- data/examples/oxford/README.md +42 -0
- data/examples/oxford/app.rb +48 -0
- data/examples/oxford/client.rb +13 -0
- data/examples/oxford/places.json +21 -0
- data/examples/oxford/server.rb +13 -0
- data/features/steps/api_navigation.rb +3 -3
- data/features/steps/default_config.rb +2 -2
- data/features/support/api.rb +22 -11
- data/features/support/env.rb +15 -1
- data/hyperclient.gemspec +4 -4
- data/lib/hyperclient/entry_point.rb +3 -3
- data/lib/hyperclient/link.rb +13 -4
- data/lib/hyperclient/resource.rb +13 -3
- data/lib/hyperclient/resource_collection.rb +1 -3
- data/lib/hyperclient/version.rb +1 -1
- data/test/hyperclient/attributes_test.rb +9 -0
- data/test/hyperclient/collection_test.rb +22 -0
- data/test/hyperclient/curie_test.rb +7 -0
- data/test/hyperclient/entry_point_test.rb +35 -13
- data/test/hyperclient/link_collection_test.rb +10 -0
- data/test/hyperclient/link_test.rb +128 -1
- data/test/hyperclient/resource_test.rb +36 -2
- data/test/hyperclient_test.rb +6 -5
- data/test/oxford_example_test.rb +52 -0
- data/test/test_helper.rb +19 -1
- metadata +25 -33
- data/.travis.yml +0 -24
- data/examples/splines_api.rb +0 -39
|
@@ -32,6 +32,7 @@ module Hyperclient
|
|
|
32
32
|
|
|
33
33
|
describe 'plain link' do
|
|
34
34
|
let(:plain_link) { links.self }
|
|
35
|
+
|
|
35
36
|
it 'must be correct' do
|
|
36
37
|
_(plain_link._url).must_equal '/productions/1'
|
|
37
38
|
end
|
|
@@ -39,6 +40,7 @@ module Hyperclient
|
|
|
39
40
|
|
|
40
41
|
describe 'templated link' do
|
|
41
42
|
let(:templated_link) { links.search }
|
|
43
|
+
|
|
42
44
|
it 'must expand' do
|
|
43
45
|
_(templated_link._expand(search: 'gizmos')._url).must_equal '/productions/1?categories=gizmos'
|
|
44
46
|
end
|
|
@@ -47,6 +49,7 @@ module Hyperclient
|
|
|
47
49
|
describe 'curied link collection' do
|
|
48
50
|
let(:curied_link) { links['image:thumbnail'] }
|
|
49
51
|
let(:curie) { links._curies['image'] }
|
|
52
|
+
|
|
50
53
|
it 'must expand' do
|
|
51
54
|
_(curied_link._expand(version: 'small')._url).must_equal '/images/thumbnails/small.jpg'
|
|
52
55
|
end
|
|
@@ -71,9 +74,16 @@ module Hyperclient
|
|
|
71
74
|
|
|
72
75
|
describe 'null link value' do
|
|
73
76
|
let(:null_link) { links.null_link }
|
|
77
|
+
|
|
74
78
|
it 'must be nil' do
|
|
75
79
|
_(null_link).must_be_nil
|
|
76
80
|
end
|
|
77
81
|
end
|
|
82
|
+
|
|
83
|
+
describe 'invalid collection' do
|
|
84
|
+
it 'raises an error when the collection does not respond to collect' do
|
|
85
|
+
_(proc { LinkCollection.new('invalid', {}, entry_point) }).must_raise RuntimeError
|
|
86
|
+
end
|
|
87
|
+
end
|
|
78
88
|
end
|
|
79
89
|
end
|
|
@@ -11,11 +11,13 @@ module Hyperclient
|
|
|
11
11
|
describe prop do
|
|
12
12
|
it 'returns the property value' do
|
|
13
13
|
link = Link.new('key', { prop => 'value' }, entry_point)
|
|
14
|
+
|
|
14
15
|
_(link.send("_#{prop}")).must_equal 'value'
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
it 'returns nil if the property is not present' do
|
|
18
19
|
link = Link.new('key', {}, entry_point)
|
|
20
|
+
|
|
19
21
|
_(link.send("_#{prop}")).must_be_nil
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -53,11 +55,13 @@ module Hyperclient
|
|
|
53
55
|
describe 'required argument' do
|
|
54
56
|
it 'builds a Link with the templated URI representation' do
|
|
55
57
|
link = Link.new('key', { 'href' => '/orders/{id}', 'templated' => true }, entry_point)
|
|
58
|
+
|
|
56
59
|
_(link._expand(id: '1')._url).must_equal '/orders/1'
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
it 'expands an uri template without variables' do
|
|
60
63
|
link = Link.new('key', { 'href' => '/orders/{id}', 'templated' => true }, entry_point)
|
|
64
|
+
|
|
61
65
|
_(link._expand._url).must_equal '/orders/'
|
|
62
66
|
_(link._url).must_equal '/orders/'
|
|
63
67
|
end
|
|
@@ -66,27 +70,32 @@ module Hyperclient
|
|
|
66
70
|
describe 'query string argument' do
|
|
67
71
|
it 'builds a Link with the templated URI representation' do
|
|
68
72
|
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
|
73
|
+
|
|
69
74
|
_(link._expand(id: '1')._url).must_equal '/orders?id=1'
|
|
70
75
|
end
|
|
71
76
|
|
|
72
77
|
it 'expands an uri template without variables' do
|
|
73
78
|
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
|
79
|
+
|
|
74
80
|
_(link._expand._url).must_equal '/orders'
|
|
75
81
|
_(link._url).must_equal '/orders'
|
|
76
82
|
end
|
|
77
83
|
|
|
78
84
|
it 'does not expand unknown variables' do
|
|
79
85
|
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
|
86
|
+
|
|
80
87
|
_(link._expand(unknown: '1')._url).must_equal '/orders'
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
it 'only expands known variables' do
|
|
84
91
|
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
|
|
92
|
+
|
|
85
93
|
_(link._expand(unknown: '1', id: '2')._url).must_equal '/orders?id=2'
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
it 'only expands templated links' do
|
|
89
97
|
link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => false }, entry_point)
|
|
98
|
+
|
|
90
99
|
_(link._expand(id: '1')._url).must_equal '/orders{?id}'
|
|
91
100
|
end
|
|
92
101
|
end
|
|
@@ -119,6 +128,7 @@ module Hyperclient
|
|
|
119
128
|
|
|
120
129
|
it 'returns the link when no uri template' do
|
|
121
130
|
link = Link.new('key', { 'href' => '/orders' }, entry_point)
|
|
131
|
+
|
|
122
132
|
_(link._url).must_equal '/orders'
|
|
123
133
|
end
|
|
124
134
|
|
|
@@ -141,9 +151,53 @@ module Hyperclient
|
|
|
141
151
|
|
|
142
152
|
link._resource
|
|
143
153
|
end
|
|
154
|
+
|
|
155
|
+
it 'fetches a fresh resource after a mutating request instead of returning the cached response (#107)' do
|
|
156
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
|
157
|
+
|
|
158
|
+
stub_request(entry_point.connection) do |stub|
|
|
159
|
+
stub.post('http://api.example.org/productions/1') { [201, {}, { 'status' => 'created' }] }
|
|
160
|
+
stub.get('http://api.example.org/productions/1') { [200, {}, { 'status' => 'fetched' }] }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
link._post('foo' => 'bar')
|
|
164
|
+
|
|
165
|
+
_(link._resource.status).must_equal 'fetched'
|
|
166
|
+
end
|
|
144
167
|
end
|
|
145
168
|
|
|
146
169
|
describe 'get' do
|
|
170
|
+
%w[application/hal+json application/json].each do |content_type|
|
|
171
|
+
it "parses a #{content_type} response into a resource" do
|
|
172
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
|
173
|
+
|
|
174
|
+
stub_request(entry_point.connection) do |stub|
|
|
175
|
+
stub.get('http://api.example.org/productions/1') do
|
|
176
|
+
[200, { 'Content-Type' => content_type },
|
|
177
|
+
'{"title":"Production","_links":{"self":{"href":"/productions/1"}}}']
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
resource = link._get
|
|
182
|
+
|
|
183
|
+
_(resource.title).must_equal 'Production'
|
|
184
|
+
_(resource._links.self._url).must_equal '/productions/1'
|
|
185
|
+
_(resource._response.body).must_be_kind_of Hash
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'raises a parsing error for an invalid JSON response' do
|
|
190
|
+
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
|
191
|
+
|
|
192
|
+
stub_request(entry_point.connection) do |stub|
|
|
193
|
+
stub.get('http://api.example.org/productions/1') do
|
|
194
|
+
[200, { 'Content-Type' => 'application/hal+json' }, '{"title":']
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
_(-> { link._get }).must_raise Faraday::ParsingError
|
|
199
|
+
end
|
|
200
|
+
|
|
147
201
|
it 'sends a GET request with the link url' do
|
|
148
202
|
link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
|
|
149
203
|
|
|
@@ -266,7 +320,7 @@ module Hyperclient
|
|
|
266
320
|
link = Link.new('key', { 'href' => '/productions/1' }, 'foo')
|
|
267
321
|
|
|
268
322
|
_(link.inspect).must_include 'Link'
|
|
269
|
-
_(link.inspect).must_include '
|
|
323
|
+
_(link.inspect).must_include({ 'href' => '/productions/1' }.inspect)
|
|
270
324
|
end
|
|
271
325
|
end
|
|
272
326
|
|
|
@@ -313,6 +367,37 @@ module Hyperclient
|
|
|
313
367
|
|
|
314
368
|
_(resource.next._links.next._url).must_equal 'http://api.example.org/page3'
|
|
315
369
|
end
|
|
370
|
+
|
|
371
|
+
it 'iterates over paginated, embedded items across pages' do
|
|
372
|
+
resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point)
|
|
373
|
+
|
|
374
|
+
stub_request(entry_point.connection) do |stub|
|
|
375
|
+
stub.get('http://api.example.org/orders') do
|
|
376
|
+
[200, {}, { '_links' => { 'next' => { 'href' => 'http://api.example.org/orders?page=2' } },
|
|
377
|
+
'_embedded' => { 'orders' => [{ 'id' => 1 }] } }]
|
|
378
|
+
end
|
|
379
|
+
stub.get('http://api.example.org/orders?page=2') do
|
|
380
|
+
[200, {}, { '_embedded' => { 'orders' => [{ 'id' => 2 }] } }]
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
ids = resource._links.orders.map(&:id)
|
|
385
|
+
|
|
386
|
+
_(ids).must_equal [1, 2]
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
it 'returns an Enumerator when called without a block' do
|
|
390
|
+
resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point)
|
|
391
|
+
|
|
392
|
+
stub_request(entry_point.connection) do |stub|
|
|
393
|
+
stub.get('http://api.example.org/orders') { [200, {}, { '_embedded' => { 'orders' => [{ 'id' => 1 }] } }] }
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
enum = resource._links.orders.each
|
|
397
|
+
|
|
398
|
+
_(enum).must_be_kind_of Enumerator
|
|
399
|
+
_(enum.to_a.first.id).must_equal 1
|
|
400
|
+
end
|
|
316
401
|
end
|
|
317
402
|
|
|
318
403
|
describe 'resource' do
|
|
@@ -341,13 +426,55 @@ module Hyperclient
|
|
|
341
426
|
it 'responds to missing methods' do
|
|
342
427
|
resource.expects(:respond_to?).with('orders').returns(false)
|
|
343
428
|
resource.expects(:respond_to?).with('embedded').returns(true)
|
|
429
|
+
|
|
344
430
|
_(link.respond_to?(:embedded)).must_equal true
|
|
345
431
|
end
|
|
346
432
|
|
|
433
|
+
it 'responds to missing methods that delegate to another resource' do
|
|
434
|
+
delegate = mock('Delegate')
|
|
435
|
+
resource.expects(:respond_to?).with('orders').returns(true)
|
|
436
|
+
resource.expects(:send).with('orders').returns(delegate)
|
|
437
|
+
delegate.expects(:respond_to?).with('foo').returns(true)
|
|
438
|
+
|
|
439
|
+
_(link.respond_to?(:foo)).must_equal true
|
|
440
|
+
end
|
|
441
|
+
|
|
347
442
|
it 'does not delegate to_ary to resource' do
|
|
348
443
|
resource.expects(:to_ary).never
|
|
444
|
+
|
|
349
445
|
_([[link, link]].flatten).must_equal [link, link]
|
|
350
446
|
end
|
|
447
|
+
|
|
448
|
+
it 'delegates through delegate_method when the resource responds but returns nil' do
|
|
449
|
+
delegate = mock('Delegate')
|
|
450
|
+
resource.expects(:respond_to?).with('foo').returns(true)
|
|
451
|
+
resource.expects(:send).with(:foo).returns(nil)
|
|
452
|
+
resource.expects(:respond_to?).with('orders').returns(true)
|
|
453
|
+
resource.expects(:send).with('orders').returns(delegate)
|
|
454
|
+
delegate.expects(:respond_to?).with('foo').returns(true)
|
|
455
|
+
delegate.expects(:send).with(:foo).returns('delegated result')
|
|
456
|
+
|
|
457
|
+
_(link.foo).must_equal 'delegated result'
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
it 'returns nil from delegate_method when the resource has no matching key' do
|
|
461
|
+
keyless_link = Link.new(nil, { 'href' => 'http://myapi.org/orders' }, entry_point)
|
|
462
|
+
resource.expects(:respond_to?).with('foo').returns(true)
|
|
463
|
+
resource.expects(:send).with(:foo).returns(nil)
|
|
464
|
+
|
|
465
|
+
_(keyless_link.foo).must_be_nil
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
it 'returns nil from delegate_method when the delegate does not respond to the method' do
|
|
469
|
+
delegate = mock('Delegate')
|
|
470
|
+
resource.expects(:respond_to?).with('foo').returns(true)
|
|
471
|
+
resource.expects(:send).with(:foo).returns(nil)
|
|
472
|
+
resource.expects(:respond_to?).with('orders').returns(true)
|
|
473
|
+
resource.expects(:send).with('orders').returns(delegate)
|
|
474
|
+
delegate.expects(:respond_to?).with('foo').returns(false)
|
|
475
|
+
|
|
476
|
+
_(link.foo).must_be_nil
|
|
477
|
+
end
|
|
351
478
|
end
|
|
352
479
|
end
|
|
353
480
|
end
|
|
@@ -13,7 +13,7 @@ module Hyperclient
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it 'initializes its attributes' do
|
|
16
|
-
Attributes.expects(:new).with(foo: :bar)
|
|
16
|
+
Attributes.expects(:new).with({ foo: :bar })
|
|
17
17
|
|
|
18
18
|
Resource.new({ foo: :bar }, entry_point)
|
|
19
19
|
end
|
|
@@ -60,7 +60,9 @@ module Hyperclient
|
|
|
60
60
|
|
|
61
61
|
describe '_links' do
|
|
62
62
|
it '_expand' do
|
|
63
|
-
resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders/{id}', 'templated' => true } } },
|
|
63
|
+
resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders/{id}', 'templated' => true } } },
|
|
64
|
+
entry_point)
|
|
65
|
+
|
|
64
66
|
_(resource._links.orders._expand(id: 1)._url).must_equal '/orders/1'
|
|
65
67
|
_(resource.orders._expand(id: 1)._url).must_equal '/orders/1'
|
|
66
68
|
_(resource.orders(id: 1)._url).must_equal '/orders/1'
|
|
@@ -93,16 +95,19 @@ module Hyperclient
|
|
|
93
95
|
describe 'method_missing' do
|
|
94
96
|
it 'delegates to attributes' do
|
|
95
97
|
resource._attributes.expects(:foo).returns('bar')
|
|
98
|
+
|
|
96
99
|
_(resource.foo).must_equal 'bar'
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
it 'delegates to links' do
|
|
100
103
|
resource._links.expects(:foo).returns('bar')
|
|
104
|
+
|
|
101
105
|
_(resource.foo).must_equal 'bar'
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
it 'delegates to embedded' do
|
|
105
109
|
resource._embedded.expects(:foo).returns('bar')
|
|
110
|
+
|
|
106
111
|
_(resource.foo).must_equal 'bar'
|
|
107
112
|
end
|
|
108
113
|
|
|
@@ -110,11 +115,13 @@ module Hyperclient
|
|
|
110
115
|
resource._attributes.expects('respond_to?').with('foo').returns(false)
|
|
111
116
|
resource._links.expects('respond_to?').with('foo').returns(false)
|
|
112
117
|
resource._embedded.expects('respond_to?').with('foo').returns(false)
|
|
118
|
+
|
|
113
119
|
_(-> { resource.foo }).must_raise NoMethodError
|
|
114
120
|
end
|
|
115
121
|
|
|
116
122
|
it 'delegates []' do
|
|
117
123
|
resource._attributes.expects(:foo).returns('bar')
|
|
124
|
+
|
|
118
125
|
_(resource['foo']).must_equal 'bar'
|
|
119
126
|
end
|
|
120
127
|
|
|
@@ -132,6 +139,7 @@ module Hyperclient
|
|
|
132
139
|
describe 'with a default value' do
|
|
133
140
|
it 'returns the value for keys that exist' do
|
|
134
141
|
resource._attributes.expects(:foo).returns('bar')
|
|
142
|
+
|
|
135
143
|
_(resource.fetch('foo', 'default value')).must_equal 'bar'
|
|
136
144
|
end
|
|
137
145
|
|
|
@@ -143,6 +151,7 @@ module Hyperclient
|
|
|
143
151
|
describe 'with a block' do
|
|
144
152
|
it 'returns the value for keys that exist' do
|
|
145
153
|
resource._attributes.expects(:foo).returns('bar')
|
|
154
|
+
|
|
146
155
|
_(resource.fetch('foo') { 'default value' }).must_equal 'bar'
|
|
147
156
|
end
|
|
148
157
|
|
|
@@ -219,5 +228,30 @@ module Hyperclient
|
|
|
219
228
|
end
|
|
220
229
|
end
|
|
221
230
|
end
|
|
231
|
+
|
|
232
|
+
describe 'inspect' do
|
|
233
|
+
it 'outputs a custom-friendly output' do
|
|
234
|
+
resource = Resource.new({ '_links' => { 'self' => { 'href' => '/orders/523' } }, 'title' => 'Order' },
|
|
235
|
+
entry_point)
|
|
236
|
+
|
|
237
|
+
_(resource.inspect).must_include 'Resource'
|
|
238
|
+
_(resource.inspect).must_include 'self_link:'
|
|
239
|
+
_(resource.inspect).must_include 'attributes:'
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe '#to_h and #to_hash' do
|
|
244
|
+
let(:resource) do
|
|
245
|
+
Resource.new({ '_links' => {}, 'title' => 'Order', 'total' => 42 }, entry_point)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'returns the attributes as a Hash from #to_hash' do
|
|
249
|
+
_(resource.to_hash).must_equal('title' => 'Order', 'total' => 42)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
it 'returns the same result from #to_h as #to_hash' do
|
|
253
|
+
_(resource.to_h).must_equal(resource.to_hash)
|
|
254
|
+
end
|
|
255
|
+
end
|
|
222
256
|
end
|
|
223
257
|
end
|
data/test/hyperclient_test.rb
CHANGED
|
@@ -13,7 +13,7 @@ describe Hyperclient do
|
|
|
13
13
|
let(:client) do
|
|
14
14
|
Hyperclient.new('http://api.example.org') do |client|
|
|
15
15
|
client.connection(default: true) do |conn|
|
|
16
|
-
conn.use Faraday::Request::
|
|
16
|
+
conn.use Faraday::Request::Instrumentation
|
|
17
17
|
end
|
|
18
18
|
client.headers['Access-Token'] = 'token'
|
|
19
19
|
end
|
|
@@ -31,11 +31,12 @@ describe Hyperclient do
|
|
|
31
31
|
|
|
32
32
|
it 'creates a Faraday connection with the default block plus any additional handlers' do
|
|
33
33
|
handlers = client.connection.builder.handlers
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
_(handlers).must_include Faraday::Request::Instrumentation
|
|
35
36
|
_(handlers).must_include Faraday::Response::RaiseError
|
|
36
|
-
_(handlers).must_include
|
|
37
|
-
_(handlers).must_include
|
|
38
|
-
_(handlers).must_include
|
|
37
|
+
_(handlers).must_include Faraday::FollowRedirects::Middleware
|
|
38
|
+
_(handlers).must_include Faraday::HalJson::Request
|
|
39
|
+
_(handlers).must_include Faraday::HalJson::Response
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'hyperclient'
|
|
3
|
+
require 'webmock'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require_relative '../examples/oxford/app'
|
|
6
|
+
|
|
7
|
+
describe OxfordExample::App do
|
|
8
|
+
let(:app) { OxfordExample::App.new }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
WebMock.enable!
|
|
12
|
+
WebMock.stub_request(:any, %r{\Ahttp://127\.0\.0\.1:9292/}).to_return do |request|
|
|
13
|
+
status, headers, body = app.call('REQUEST_METHOD' => request.method.to_s.upcase,
|
|
14
|
+
'PATH_INFO' => request.uri.path)
|
|
15
|
+
{ status: status, headers: headers, body: body.join }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after do
|
|
20
|
+
WebMock.reset!
|
|
21
|
+
WebMock.disable!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'runs the documented client without external requests' do
|
|
25
|
+
output, = capture_io { load File.expand_path('../examples/oxford/client.rb', __dir__) }
|
|
26
|
+
|
|
27
|
+
_(output).must_include 'Balliol College: Broad Street OX1 3BJ'
|
|
28
|
+
_(output).must_include 'Followed primary_place: Balliol College'
|
|
29
|
+
_(output).must_include 'Archived depiction URL (not fetched):'
|
|
30
|
+
_(output).must_include 'CURIE: /docs/places'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'supports explicit navigation, hash attributes, and embedded resources' do
|
|
34
|
+
api = Hyperclient.new('http://127.0.0.1:9292/')
|
|
35
|
+
place = api._links.places._embedded.places.first
|
|
36
|
+
|
|
37
|
+
_(place[:name]).must_equal 'Balliol College'
|
|
38
|
+
_(place.to_h['id']).must_equal 'oxpoints:23232339'
|
|
39
|
+
_(place._embedded.files.first.type).must_equal 'depiction'
|
|
40
|
+
_(api._links['oxford:places']._embedded.places.first.name).must_equal 'Balliol College'
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'serves HAL, rejects writes, and reports missing resources' do
|
|
44
|
+
status, headers, = app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/')
|
|
45
|
+
|
|
46
|
+
_(status).must_equal 200
|
|
47
|
+
_(headers['content-type']).must_equal 'application/hal+json'
|
|
48
|
+
_(app.call('REQUEST_METHOD' => 'POST', 'PATH_INFO' => '/').first).must_equal 405
|
|
49
|
+
_(app.call('REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/missing').first).must_equal 404
|
|
50
|
+
_(app.call('REQUEST_METHOD' => 'HEAD', 'PATH_INFO' => '/').last).must_equal []
|
|
51
|
+
end
|
|
52
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
$LOAD_PATH << 'lib'
|
|
2
2
|
|
|
3
|
+
if ENV['COVERAGE']
|
|
4
|
+
require 'simplecov'
|
|
5
|
+
require 'simplecov-lcov'
|
|
6
|
+
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
|
7
|
+
SimpleCov::Formatter::LcovFormatter.config do |c|
|
|
8
|
+
c.report_with_single_file = true
|
|
9
|
+
c.lcov_file_name = 'lcov.info'
|
|
10
|
+
c.single_report_path = 'coverage/lcov.info'
|
|
11
|
+
end
|
|
12
|
+
SimpleCov.start do
|
|
13
|
+
add_filter '/test/'
|
|
14
|
+
add_filter '/features/'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
3
18
|
require 'minitest/autorun'
|
|
4
19
|
require 'minitest/pride'
|
|
5
20
|
require 'mocha/minitest'
|
|
6
21
|
require 'json'
|
|
7
22
|
|
|
8
|
-
|
|
23
|
+
require 'faraday'
|
|
24
|
+
require 'faraday/request/instrumentation'
|
|
25
|
+
|
|
26
|
+
Minitest::Assertions.class_eval do
|
|
9
27
|
def stub_request(conn, adapter_class = Faraday::Adapter::Test, &stubs_block)
|
|
10
28
|
adapter_handler = conn.builder.handlers.find { |h| h.klass < Faraday::Adapter }
|
|
11
29
|
if adapter_handler
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperclient
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oriol Gual
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -30,16 +30,16 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: '2'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: '2'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: faraday-follow_redirects
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
@@ -53,19 +53,19 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: faraday_hal_middleware
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
61
|
+
version: '0.2'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
68
|
+
version: '0.2'
|
|
69
69
|
description: Hyperclient is a Ruby Hypermedia API client.
|
|
70
70
|
email:
|
|
71
71
|
- oriol.gual@gmail.com
|
|
@@ -73,11 +73,17 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
+
- ".github/dependabot.yml"
|
|
77
|
+
- ".github/workflows/coverage.yml"
|
|
78
|
+
- ".github/workflows/danger-comment.yml"
|
|
79
|
+
- ".github/workflows/danger.yml"
|
|
80
|
+
- ".github/workflows/lint.yml"
|
|
81
|
+
- ".github/workflows/test.yml"
|
|
76
82
|
- ".gitignore"
|
|
77
83
|
- ".rubocop.yml"
|
|
78
84
|
- ".rubocop_todo.yml"
|
|
79
|
-
- ".travis.yml"
|
|
80
85
|
- ".yardopts"
|
|
86
|
+
- AGENTS.md
|
|
81
87
|
- CHANGELOG.md
|
|
82
88
|
- CONTRIBUTING.md
|
|
83
89
|
- Dangerfile
|
|
@@ -88,7 +94,11 @@ files:
|
|
|
88
94
|
- RELEASING.md
|
|
89
95
|
- Rakefile
|
|
90
96
|
- UPGRADING.md
|
|
91
|
-
- examples/
|
|
97
|
+
- examples/oxford/README.md
|
|
98
|
+
- examples/oxford/app.rb
|
|
99
|
+
- examples/oxford/client.rb
|
|
100
|
+
- examples/oxford/places.json
|
|
101
|
+
- examples/oxford/server.rb
|
|
92
102
|
- features/api_navigation.feature
|
|
93
103
|
- features/default_config.feature
|
|
94
104
|
- features/steps/api_navigation.rb
|
|
@@ -119,10 +129,12 @@ files:
|
|
|
119
129
|
- test/hyperclient/resource_collection_test.rb
|
|
120
130
|
- test/hyperclient/resource_test.rb
|
|
121
131
|
- test/hyperclient_test.rb
|
|
132
|
+
- test/oxford_example_test.rb
|
|
122
133
|
- test/test_helper.rb
|
|
123
134
|
homepage: https://github.com/codegram/hyperclient/
|
|
124
135
|
licenses: []
|
|
125
|
-
metadata:
|
|
136
|
+
metadata:
|
|
137
|
+
rubygems_mfa_required: 'true'
|
|
126
138
|
post_install_message:
|
|
127
139
|
rdoc_options: []
|
|
128
140
|
require_paths:
|
|
@@ -138,28 +150,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
138
150
|
- !ruby/object:Gem::Version
|
|
139
151
|
version: '0'
|
|
140
152
|
requirements: []
|
|
141
|
-
rubygems_version: 3.
|
|
153
|
+
rubygems_version: 3.5.16
|
|
142
154
|
signing_key:
|
|
143
155
|
specification_version: 4
|
|
144
156
|
summary: ''
|
|
145
|
-
test_files:
|
|
146
|
-
- features/api_navigation.feature
|
|
147
|
-
- features/default_config.feature
|
|
148
|
-
- features/steps/api_navigation.rb
|
|
149
|
-
- features/steps/default_config.rb
|
|
150
|
-
- features/support/api.rb
|
|
151
|
-
- features/support/env.rb
|
|
152
|
-
- features/support/fixtures.rb
|
|
153
|
-
- test/fixtures/collection.json
|
|
154
|
-
- test/fixtures/element.json
|
|
155
|
-
- test/fixtures/root.json
|
|
156
|
-
- test/hyperclient/attributes_test.rb
|
|
157
|
-
- test/hyperclient/collection_test.rb
|
|
158
|
-
- test/hyperclient/curie_test.rb
|
|
159
|
-
- test/hyperclient/entry_point_test.rb
|
|
160
|
-
- test/hyperclient/link_collection_test.rb
|
|
161
|
-
- test/hyperclient/link_test.rb
|
|
162
|
-
- test/hyperclient/resource_collection_test.rb
|
|
163
|
-
- test/hyperclient/resource_test.rb
|
|
164
|
-
- test/hyperclient_test.rb
|
|
165
|
-
- test/test_helper.rb
|
|
157
|
+
test_files: []
|
data/.travis.yml
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
language: ruby
|
|
2
|
-
|
|
3
|
-
sudo: false
|
|
4
|
-
|
|
5
|
-
matrix:
|
|
6
|
-
include:
|
|
7
|
-
- rvm: 2.6.6
|
|
8
|
-
env: FARADAY_VERSION=0.9.0
|
|
9
|
-
- rvm: 2.6.6
|
|
10
|
-
env: FARADAY_VERSION=0.17.0
|
|
11
|
-
- rvm: 2.6.6
|
|
12
|
-
env: FARADAY_VERSION="~> 1.0"
|
|
13
|
-
- rvm: 2.6.6
|
|
14
|
-
script:
|
|
15
|
-
- bundle exec danger
|
|
16
|
-
- rvm: 2.3.8
|
|
17
|
-
- rvm: jruby-9.2.7.0
|
|
18
|
-
- rvm: jruby-head
|
|
19
|
-
- rvm: ruby-head
|
|
20
|
-
allow_failures:
|
|
21
|
-
- rvm: ruby-head
|
|
22
|
-
- rvm: jruby-head
|
|
23
|
-
|
|
24
|
-
bundler_args: --without development
|
data/examples/splines_api.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
require 'hyperclient'
|
|
2
|
-
|
|
3
|
-
puts "Using Hyperclient #{Hyperclient::VERSION} ..."
|
|
4
|
-
|
|
5
|
-
# create a new client
|
|
6
|
-
api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api')
|
|
7
|
-
|
|
8
|
-
# enumerate splines
|
|
9
|
-
api.splines.each do |spline|
|
|
10
|
-
puts spline.uuid.to_s
|
|
11
|
-
puts " reticulated: #{spline.reticulated ? 'yes' : 'no'}"
|
|
12
|
-
puts " thumbnail: #{spline['images:thumbnail']}"
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
api._links.splines._embedded.splines.each do |_spline|
|
|
16
|
-
# ... equivalent to the above
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
puts '*' * 10
|
|
20
|
-
|
|
21
|
-
# retrieve an existing spline
|
|
22
|
-
spline = api.spline(uuid: 123)
|
|
23
|
-
puts "Spline #{spline.uuid} is #{spline.reticulated ? 'reticulated' : 'not reticulated'}."
|
|
24
|
-
|
|
25
|
-
# puts api._links.spline._expand(uuid: 'uuid')._resource._attributes.reticulated
|
|
26
|
-
|
|
27
|
-
# spline.to_h
|
|
28
|
-
|
|
29
|
-
# create a new spline
|
|
30
|
-
spline = api.splines._post(spline: { reticulated: true })
|
|
31
|
-
puts "Created a #{spline.reticulated ? 'reticulated' : 'unreticulated'} spline #{spline.uuid}."
|
|
32
|
-
|
|
33
|
-
# update an existing spline
|
|
34
|
-
spline = api.spline(uuid: 123)._put(spline: { reticulated: true })
|
|
35
|
-
puts "Updated spline #{spline.uuid}, now #{spline.reticulated ? 'reticulated' : 'not reticulated'}."
|
|
36
|
-
|
|
37
|
-
# delete an existing spline
|
|
38
|
-
spline = api.spline(uuid: 123)._delete
|
|
39
|
-
puts "Deleted spline #{spline.uuid}."
|