frenetic 1.0.0.alpha.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.editorconfig +16 -0
- data/.gitignore +1 -1
- data/.irbrc +1 -1
- data/.rubocop.yml +45 -0
- data/.travis.yml +2 -2
- data/Appraisals +1 -1
- data/LICENSE +1 -1
- data/README.md +15 -15
- data/Rakefile +1 -1
- data/gemfiles/faraday_08.gemfile.lock +8 -8
- data/gemfiles/faraday_09.gemfile.lock +8 -8
- data/lib/frenetic.rb +13 -10
- data/lib/frenetic/briefly_memoizable.rb +9 -7
- data/lib/frenetic/concerns/collection_rest_methods.rb +4 -5
- data/lib/frenetic/concerns/hal_linked.rb +12 -9
- data/lib/frenetic/concerns/member_rest_methods.rb +7 -10
- data/lib/frenetic/concerns/structured.rb +2 -2
- data/lib/frenetic/connection.rb +25 -17
- data/lib/frenetic/errors.rb +67 -2
- data/lib/frenetic/hypermedia_link.rb +19 -26
- data/lib/frenetic/hypermedia_link_set.rb +11 -14
- data/lib/frenetic/middleware/hal_json.rb +3 -4
- data/lib/frenetic/resource.rb +31 -25
- data/lib/frenetic/resource_collection.rb +3 -3
- data/lib/frenetic/resource_mockery.rb +7 -5
- data/lib/frenetic/version.rb +2 -2
- data/spec/briefly_memoizable_spec.rb +1 -1
- data/spec/concerns/hal_linked_spec.rb +5 -5
- data/spec/concerns/member_rest_methods_spec.rb +1 -1
- data/spec/concerns/structured_spec.rb +6 -5
- data/spec/connection_spec.rb +16 -4
- data/spec/fixtures/test_api_requests.rb +32 -28
- data/spec/frenetic_spec.rb +3 -3
- data/spec/hypermedia_link_set_spec.rb +3 -3
- data/spec/hypermedia_link_spec.rb +1 -1
- data/spec/middleware/hal_json_spec.rb +3 -3
- data/spec/resource_collection_spec.rb +3 -4
- data/spec/resource_mockery_spec.rb +29 -6
- data/spec/resource_spec.rb +30 -13
- data/spec/spec_helper.rb +1 -1
- data/spec/support/i18n.rb +1 -0
- data/spec/support/rspec.rb +1 -1
- data/spec/support/timecop.rb +1 -1
- data/spec/support/webmock.rb +1 -1
- metadata +8 -4
@@ -6,7 +6,7 @@ class Frenetic
|
|
6
6
|
include HalLinked
|
7
7
|
include CollectionRestMethods
|
8
8
|
|
9
|
-
def initialize(
|
9
|
+
def initialize(resource, params = {})
|
10
10
|
@resource_class = resource
|
11
11
|
@resources = []
|
12
12
|
@params = params || {}
|
@@ -43,7 +43,7 @@ class Frenetic
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def embedded_collection
|
46
|
-
@params.fetch('_embedded',{}).fetch(collection_key, [])
|
46
|
+
@params.fetch('_embedded', {}).fetch(collection_key, [])
|
47
47
|
end
|
48
48
|
end
|
49
|
-
end
|
49
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
require 'delegate'
|
3
3
|
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
5
|
|
5
6
|
class Frenetic
|
6
7
|
module ResourceMockery
|
@@ -20,7 +21,7 @@ class Frenetic
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def properties
|
23
|
-
@params.each_with_object({}) do |(k,v), props|
|
24
|
+
@params.each_with_object({}) do |(k, v), props|
|
24
25
|
props[k] = v.class.to_s.underscore
|
25
26
|
end
|
26
27
|
end
|
@@ -43,13 +44,14 @@ class Frenetic
|
|
43
44
|
|
44
45
|
private
|
45
46
|
|
46
|
-
def build_params(
|
47
|
+
def build_params(params)
|
48
|
+
raw_params = (params || {}).with_indifferent_access
|
47
49
|
defaults = default_attributes.with_indifferent_access
|
48
|
-
@params
|
50
|
+
@params = defaults.deep_merge(raw_params)
|
49
51
|
end
|
50
52
|
|
51
53
|
def build_structure
|
52
|
-
@structure = OpenStruct.new(
|
54
|
+
@structure = OpenStruct.new(@attrs)
|
53
55
|
end
|
54
56
|
end
|
55
|
-
end
|
57
|
+
end
|
data/lib/frenetic/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
class Frenetic
|
2
|
-
VERSION = '1.0.0
|
3
|
-
end
|
2
|
+
VERSION = '1.0.0'
|
3
|
+
end
|
@@ -21,7 +21,7 @@ describe Frenetic::HalLinked do
|
|
21
21
|
|
22
22
|
let(:_links) do
|
23
23
|
{
|
24
|
-
'_links' => { 'self' => { 'href' => '/api/self' }}
|
24
|
+
'_links' => { 'self' => { 'href' => '/api/self' } }
|
25
25
|
}
|
26
26
|
end
|
27
27
|
|
@@ -65,7 +65,7 @@ describe Frenetic::HalLinked do
|
|
65
65
|
context 'with an implied self link' do
|
66
66
|
let(:_links) do
|
67
67
|
{
|
68
|
-
'_links' => { 'self' => { 'href' => '/api/self' }}
|
68
|
+
'_links' => { 'self' => { 'href' => '/api/self' } }
|
69
69
|
}
|
70
70
|
end
|
71
71
|
|
@@ -92,7 +92,7 @@ describe Frenetic::HalLinked do
|
|
92
92
|
|
93
93
|
it 'processes the link' do
|
94
94
|
expect_any_instance_of(Frenetic::HypermediaLinkSet)
|
95
|
-
.to receive(:href).with(
|
95
|
+
.to receive(:href).with(params).and_call_original
|
96
96
|
subject
|
97
97
|
end
|
98
98
|
end
|
@@ -106,7 +106,7 @@ describe Frenetic::HalLinked do
|
|
106
106
|
before do
|
107
107
|
allow(MyTempResource)
|
108
108
|
.to receive(:namespace)
|
109
|
-
|
109
|
+
.and_return(Time.now.to_i.to_s)
|
110
110
|
end
|
111
111
|
|
112
112
|
it 'raises an error' do
|
@@ -122,4 +122,4 @@ describe Frenetic::HalLinked do
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
125
|
-
end
|
125
|
+
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Frenetic::Structured do
|
4
4
|
let(:my_temp_resource) do
|
5
5
|
Class.new do
|
6
|
-
def initialize(
|
6
|
+
def initialize(attrs = {})
|
7
7
|
@attrs = attrs
|
8
8
|
end
|
9
9
|
end
|
@@ -24,7 +24,7 @@ describe Frenetic::Structured do
|
|
24
24
|
|
25
25
|
after { instance.destroy_structure! }
|
26
26
|
|
27
|
-
subject(:instance) { MyTempResource.new(
|
27
|
+
subject(:instance) { MyTempResource.new(foo:'foo', bar:'bar') }
|
28
28
|
|
29
29
|
describe '#struct_key' do
|
30
30
|
subject { super().struct_key }
|
@@ -121,9 +121,10 @@ describe Frenetic::Structured do
|
|
121
121
|
|
122
122
|
before do
|
123
123
|
allow(instance).to receive(:signature).and_return(new_sig)
|
124
|
-
described_class.class_variable_set
|
124
|
+
described_class.class_variable_set(
|
125
|
+
'@@signatures',
|
125
126
|
'MyTempResourceFreneticResourceStruct' => old_sig
|
126
|
-
|
127
|
+
)
|
127
128
|
end
|
128
129
|
|
129
130
|
context 'with a fresh signature' do
|
@@ -206,4 +207,4 @@ describe Frenetic::Structured do
|
|
206
207
|
end
|
207
208
|
end
|
208
209
|
end
|
209
|
-
end
|
210
|
+
end
|
data/spec/connection_spec.rb
CHANGED
@@ -18,7 +18,7 @@ describe Frenetic::Connection do
|
|
18
18
|
let(:url) { nil }
|
19
19
|
|
20
20
|
it 'raises an error' do
|
21
|
-
expect{subject}.to raise_error Frenetic::ConfigError,
|
21
|
+
expect{subject}.to raise_error Frenetic::ConfigError, /Url must be present/
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -42,8 +42,20 @@ describe Frenetic::Connection do
|
|
42
42
|
expect(subject[1]).to include :url
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
context 'with specific port' do
|
46
|
+
let(:url) { 'https://example.com:8443' }
|
47
|
+
it 'converts URLs to URIs' do
|
48
|
+
expect(subject[1]).to include url:kind_of(Addressable::URI)
|
49
|
+
expect(subject[1][:url].port).to eq(8443)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'with no specific port' do
|
54
|
+
let(:url) { 'https://example.com' }
|
55
|
+
it 'converts URLs to URIs with port inferred from scheme' do
|
56
|
+
expect(subject[1]).to include url:kind_of(Addressable::URI)
|
57
|
+
expect(subject[1][:url].port).to eq(443)
|
58
|
+
end
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
@@ -134,4 +146,4 @@ describe Frenetic::Connection do
|
|
134
146
|
expect(builder).to have_received(:adapter).with(:patron)
|
135
147
|
end
|
136
148
|
end
|
137
|
-
end
|
149
|
+
end
|
@@ -1,78 +1,81 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
class HttpStubs
|
4
|
-
def initialize(
|
4
|
+
def initialize(rspec)
|
5
5
|
@rspec = rspec
|
6
6
|
end
|
7
7
|
|
8
8
|
def defaults
|
9
9
|
{
|
10
10
|
status: 200,
|
11
|
-
headers: { 'Content-Type'=>'application/json' },
|
11
|
+
headers: { 'Content-Type' => 'application/json' },
|
12
12
|
body: {}
|
13
13
|
}
|
14
14
|
end
|
15
15
|
|
16
|
-
def response(
|
17
|
-
defs
|
16
|
+
def response(params = {})
|
17
|
+
defs = defaults.dup
|
18
18
|
headers = params.delete :headers
|
19
19
|
|
20
20
|
defs[:headers].merge! headers || {}
|
21
21
|
|
22
|
-
defs.merge(
|
22
|
+
defs.merge(params).tap do |p|
|
23
23
|
p[:body] = p[:body].to_json
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
def api_html_response
|
28
|
-
@rspec.stub_request(
|
29
|
-
.to_return response(
|
28
|
+
@rspec.stub_request(:any, 'example.com/api')
|
29
|
+
.to_return response(body:'Non-JSON response', status:200)
|
30
30
|
end
|
31
31
|
|
32
|
-
def api_server_error(
|
32
|
+
def api_server_error(type = :json)
|
33
33
|
body = '500 Server Error'
|
34
34
|
|
35
35
|
body = { 'error' => body }.to_json if type == :json
|
36
36
|
|
37
|
-
@rspec.stub_request(
|
38
|
-
.to_return response(
|
37
|
+
@rspec.stub_request(:any, 'example.com/api')
|
38
|
+
.to_return response(body:body, status:500)
|
39
39
|
end
|
40
40
|
|
41
|
-
def api_client_error(
|
41
|
+
def api_client_error(type = :json)
|
42
42
|
body = '404 Not Found'
|
43
43
|
|
44
44
|
body = { 'error' => body }.to_json if type == :json
|
45
45
|
|
46
|
-
@rspec.stub_request(
|
47
|
-
.to_return defaults.merge(
|
46
|
+
@rspec.stub_request(:any, 'example.com/api')
|
47
|
+
.to_return defaults.merge(body:body, status:404)
|
48
48
|
end
|
49
49
|
|
50
50
|
def api_description
|
51
|
-
@rspec.stub_request(
|
52
|
-
.to_return response(
|
51
|
+
@rspec.stub_request(:any, 'example.com/api')
|
52
|
+
.to_return response(body:schema, headers:{ 'Cache-Control' => 'max-age=3600, public' })
|
53
53
|
end
|
54
54
|
|
55
55
|
def unknown_instance
|
56
|
-
@rspec.stub_request(
|
57
|
-
.to_return response(
|
56
|
+
@rspec.stub_request(:get, 'example.com/api/my_temp_resources/1')
|
57
|
+
.to_return response(body:{ 'error' => '404 Not Found' }, status:404)
|
58
58
|
end
|
59
59
|
|
60
60
|
def known_instance
|
61
|
-
@rspec.stub_request(
|
62
|
-
.to_return response(
|
61
|
+
@rspec.stub_request(:get, 'example.com/api/my_temp_resources/1')
|
62
|
+
.to_return response(body:{ 'name' => 'Resource Name' })
|
63
63
|
end
|
64
64
|
|
65
65
|
def known_resource
|
66
|
-
@rspec.stub_request(
|
67
|
-
.to_return response(
|
68
|
-
|
69
|
-
'
|
70
|
-
|
71
|
-
|
66
|
+
@rspec.stub_request(:get, 'example.com/api/my_temp_resources')
|
67
|
+
.to_return response(
|
68
|
+
body: {
|
69
|
+
'_embedded' => {
|
70
|
+
'my_temp_resources' => [
|
71
|
+
{ 'name' => 'Resource Name' }
|
72
|
+
]
|
73
|
+
}
|
72
74
|
}
|
73
|
-
|
75
|
+
)
|
74
76
|
end
|
75
77
|
|
78
|
+
# rubocop:disable Metrics/MethodLength
|
76
79
|
def schema
|
77
80
|
{
|
78
81
|
_embedded: {
|
@@ -110,10 +113,11 @@ class HttpStubs
|
|
110
113
|
}
|
111
114
|
}
|
112
115
|
end
|
116
|
+
# rubocop:enable Metrics/MethodLength
|
113
117
|
end
|
114
118
|
|
115
119
|
RSpec.configure do |c|
|
116
120
|
c.before :all do
|
117
|
-
@stubs = HttpStubs.new(
|
121
|
+
@stubs = HttpStubs.new(self)
|
118
122
|
end
|
119
|
-
end
|
123
|
+
end
|
data/spec/frenetic_spec.rb
CHANGED
@@ -17,7 +17,7 @@ describe Frenetic do
|
|
17
17
|
expect(subject).to include headers:kind_of(Hash)
|
18
18
|
expect(subject).to include middleware:[]
|
19
19
|
expect(subject).to include :password
|
20
|
-
expect(subject).to include ssl:{verify:true}
|
20
|
+
expect(subject).to include ssl:{ verify:true }
|
21
21
|
expect(subject).to include test_mode:false
|
22
22
|
expect(subject).to include :url
|
23
23
|
expect(subject).to include :username
|
@@ -37,7 +37,7 @@ describe Frenetic do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
describe '#configure' do
|
40
|
-
subject { instance.configure {|c| } }
|
40
|
+
subject { instance.configure {|c| c } }
|
41
41
|
|
42
42
|
it 'resets the Connection' do
|
43
43
|
prev_connection = instance.connection
|
@@ -240,4 +240,4 @@ describe Frenetic do
|
|
240
240
|
# expect(instance.connection).to have_received(:delete)
|
241
241
|
# end
|
242
242
|
# end
|
243
|
-
end
|
243
|
+
end
|
@@ -93,7 +93,7 @@ describe Frenetic::HypermediaLinkSet do
|
|
93
93
|
|
94
94
|
context 'with multiple matching links' do
|
95
95
|
let(:link_a) do
|
96
|
-
Frenetic::HypermediaLink.new(
|
96
|
+
Frenetic::HypermediaLink.new(href:'/foo/{id}', templated:true)
|
97
97
|
end
|
98
98
|
|
99
99
|
let(:links) do
|
@@ -126,7 +126,7 @@ describe Frenetic::HypermediaLinkSet do
|
|
126
126
|
subject { super()[rel] }
|
127
127
|
|
128
128
|
let(:link_b) do
|
129
|
-
Frenetic::HypermediaLink.new(
|
129
|
+
Frenetic::HypermediaLink.new(href:'/bar', rel:'bar')
|
130
130
|
end
|
131
131
|
|
132
132
|
let(:links) do
|
@@ -152,4 +152,4 @@ describe Frenetic::HypermediaLinkSet do
|
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
155
|
-
end
|
155
|
+
end
|
@@ -16,9 +16,9 @@ describe Frenetic::Middleware::HalJson do
|
|
16
16
|
let(:options) { Hash.new }
|
17
17
|
let(:headers) { Hash.new }
|
18
18
|
let(:middleware) do
|
19
|
-
described_class.new(lambda
|
19
|
+
described_class.new(lambda do |env|
|
20
20
|
Faraday::Response.new(env)
|
21
|
-
|
21
|
+
end, options)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'does not change nil body' do
|
@@ -78,4 +78,4 @@ describe Frenetic::Middleware::HalJson do
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
|
-
end
|
81
|
+
end
|
@@ -17,11 +17,10 @@ describe Frenetic::ResourceCollection do
|
|
17
17
|
|
18
18
|
before do
|
19
19
|
stub_const 'MyTempResource', my_temp_resource
|
20
|
-
|
21
20
|
@stubs.api_description
|
22
21
|
end
|
23
22
|
|
24
|
-
let(:collection_response)
|
23
|
+
let(:collection_response) do
|
25
24
|
{
|
26
25
|
'_embedded' => {
|
27
26
|
'my_temp_resources' => [
|
@@ -39,7 +38,7 @@ describe Frenetic::ResourceCollection do
|
|
39
38
|
}
|
40
39
|
}
|
41
40
|
}
|
42
|
-
|
41
|
+
end
|
43
42
|
|
44
43
|
subject(:instance) { described_class.new(MyTempResource, collection_response) }
|
45
44
|
|
@@ -84,4 +83,4 @@ describe Frenetic::ResourceCollection do
|
|
84
83
|
expect(subject).to be_an_instance_of MyTempResource
|
85
84
|
end
|
86
85
|
end
|
87
|
-
end
|
86
|
+
end
|
@@ -10,18 +10,38 @@ describe Frenetic::ResourceMockery do
|
|
10
10
|
let(:my_mocked_resource) do
|
11
11
|
Class.new(my_temp_resource) do
|
12
12
|
def self.default_attributes
|
13
|
-
{
|
13
|
+
{
|
14
|
+
qux: 'qux',
|
15
|
+
_embedded: {
|
16
|
+
embedded_resource: {
|
17
|
+
plugh: 'xyzzy'
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
14
21
|
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
25
|
+
let(:params) do
|
26
|
+
{
|
27
|
+
foo: 1,
|
28
|
+
bar: 'baz',
|
29
|
+
_embedded: {
|
30
|
+
embedded_resource: {
|
31
|
+
grault: 'garply'
|
32
|
+
},
|
33
|
+
another_resource: {
|
34
|
+
waldo: 'fred'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
18
40
|
before do
|
19
41
|
stub_const 'MyNamespace::MyMockedResource', my_mocked_resource
|
20
42
|
MyNamespace::MyMockedResource.send :include, described_class
|
21
43
|
end
|
22
44
|
|
23
|
-
let(:params) { { foo:1, bar:'baz' } }
|
24
|
-
|
25
45
|
subject { MyNamespace::MyMockedResource.new params }
|
26
46
|
|
27
47
|
it 'violates some basic CS principles by telling the parent-class of its existence' do
|
@@ -44,6 +64,9 @@ describe Frenetic::ResourceMockery do
|
|
44
64
|
expect(subject).to include 'foo' => 1
|
45
65
|
expect(subject).to include 'bar' => 'baz'
|
46
66
|
expect(subject).to include 'qux' => 'qux'
|
67
|
+
expect(subject['_embedded']['embedded_resource']).to include 'plugh' => 'xyzzy'
|
68
|
+
expect(subject['_embedded']['embedded_resource']).to include 'grault' => 'garply'
|
69
|
+
expect(subject['_embedded']['another_resource']).to include 'waldo' => 'fred'
|
47
70
|
end
|
48
71
|
end
|
49
72
|
|
@@ -53,7 +76,7 @@ describe Frenetic::ResourceMockery do
|
|
53
76
|
subject { MyNamespace::MyMockedResource.default_attributes }
|
54
77
|
|
55
78
|
it 'allows implementors to specify sane defaults' do
|
56
|
-
expect(subject).to eq
|
79
|
+
expect(subject).to eq({})
|
57
80
|
end
|
58
81
|
end
|
59
82
|
|
@@ -63,7 +86,7 @@ describe Frenetic::ResourceMockery do
|
|
63
86
|
subject { MyNamespace::MyMockedResource.new.default_attributes }
|
64
87
|
|
65
88
|
it 'proxies to the class method' do
|
66
|
-
expect(subject).to eq
|
89
|
+
expect(subject).to eq({})
|
67
90
|
end
|
68
91
|
end
|
69
|
-
end
|
92
|
+
end
|