frenetic 0.0.20.alpha.6 → 1.0.0.alpha.1
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 +7 -0
- data/.ruby-version +1 -1
- data/Appraisals +9 -0
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/frenetic.gemspec +8 -5
- data/gemfiles/faraday_08.gemfile +10 -0
- data/gemfiles/faraday_08.gemfile.lock +77 -0
- data/gemfiles/faraday_09.gemfile +10 -0
- data/gemfiles/faraday_09.gemfile.lock +77 -0
- data/lib/frenetic.rb +57 -30
- data/lib/frenetic/briefly_memoizable.rb +34 -0
- data/lib/frenetic/concerns/collection_rest_methods.rb +1 -1
- data/lib/frenetic/concerns/hal_linked.rb +5 -35
- data/lib/frenetic/concerns/member_rest_methods.rb +0 -2
- data/lib/frenetic/concerns/structured.rb +0 -5
- data/lib/frenetic/connection.rb +110 -0
- data/lib/frenetic/errors.rb +112 -0
- data/lib/frenetic/hypermedia_link.rb +74 -0
- data/lib/frenetic/hypermedia_link_set.rb +43 -0
- data/lib/frenetic/middleware/hal_json.rb +9 -12
- data/lib/frenetic/resource.rb +22 -6
- data/lib/frenetic/resource_collection.rb +0 -1
- data/lib/frenetic/resource_mockery.rb +55 -1
- data/lib/frenetic/version.rb +1 -1
- data/spec/{concerns/breifly_memoizable_spec.rb → briefly_memoizable_spec.rb} +10 -18
- data/spec/concerns/hal_linked_spec.rb +49 -62
- data/spec/concerns/member_rest_methods_spec.rb +8 -10
- data/spec/concerns/structured_spec.rb +70 -75
- data/spec/connection_spec.rb +137 -0
- data/spec/fixtures/test_api_requests.rb +8 -2
- data/spec/frenetic_spec.rb +221 -133
- data/spec/hypermedia_link_set_spec.rb +155 -0
- data/spec/hypermedia_link_spec.rb +153 -0
- data/spec/middleware/hal_json_spec.rb +13 -15
- data/spec/resource_collection_spec.rb +17 -16
- data/spec/resource_mockery_spec.rb +69 -0
- data/spec/resource_spec.rb +110 -63
- data/spec/support/rspec.rb +0 -1
- metadata +88 -75
- data/lib/frenetic/concerns/briefly_memoizable.rb +0 -34
- data/lib/frenetic/concerns/configurable.rb +0 -59
- data/lib/frenetic/concerns/resource_mockery.rb +0 -48
- data/lib/frenetic/configuration.rb +0 -88
- data/spec/concerns/configurable_spec.rb +0 -50
- data/spec/concerns/resource_mockery_spec.rb +0 -56
- data/spec/configuration_spec.rb +0 -134
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frenetic::HypermediaLinkSet do
|
4
|
+
subject(:instance) { described_class.new links }
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
context 'with a single link' do
|
8
|
+
let(:links) { { href:'foo' } }
|
9
|
+
|
10
|
+
it 'converts the argument to an array' do
|
11
|
+
expect(subject.size).to eq 1
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'transforms it into HypermediaLink' do
|
15
|
+
expect(subject.first).to be_an_instance_of Frenetic::HypermediaLink
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#href' do
|
21
|
+
subject { super().href tmpl_data }
|
22
|
+
|
23
|
+
context 'without template data' do
|
24
|
+
let(:tmpl_data) {}
|
25
|
+
|
26
|
+
context 'with a single link in the set' do
|
27
|
+
let(:link_a) do
|
28
|
+
Frenetic::HypermediaLink.new href:'/foo/bar'
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:links) { [link_a] }
|
32
|
+
|
33
|
+
it 'returns the HREF of the first link' do
|
34
|
+
allow(link_a).to receive(:href).and_call_original
|
35
|
+
expect(subject).to eq '/foo/bar'
|
36
|
+
expect(link_a).to have_received(:href)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'with multiple links in the set' do
|
41
|
+
let(:links) do
|
42
|
+
[
|
43
|
+
{ href:'/foo/bar' },
|
44
|
+
{ href:'/baz/qux' }
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the first link in the set' do
|
49
|
+
expect(subject).to eq '/foo/bar'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with template data' do
|
55
|
+
let(:tmpl_data) { { id:1 } }
|
56
|
+
|
57
|
+
let(:link_a) do
|
58
|
+
Frenetic::HypermediaLink.new href:'/foo/{id}', templated:true
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:links) { [link_a] }
|
62
|
+
|
63
|
+
it 'finds most relevant link' do
|
64
|
+
allow(instance).to receive(:find_relevant_link)
|
65
|
+
subject
|
66
|
+
expect(instance).to have_received(:find_relevant_link)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'passes along the template data' do
|
70
|
+
allow(link_a).to receive(:href).and_call_original
|
71
|
+
subject
|
72
|
+
expect(link_a).to have_received(:href).with(tmpl_data)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#find_relevant_link' do
|
78
|
+
let(:tmpl_data) { { id:1 } }
|
79
|
+
|
80
|
+
subject { super().find_relevant_link tmpl_data }
|
81
|
+
|
82
|
+
context 'with a single, matching link' do
|
83
|
+
let(:links) do
|
84
|
+
[
|
85
|
+
{ href:'/foo/{id}', templated:true }
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns the matching link' do
|
90
|
+
expect(subject).to be_an_instance_of Frenetic::HypermediaLink
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'with multiple matching links' do
|
95
|
+
let(:link_a) do
|
96
|
+
Frenetic::HypermediaLink.new( href:'/foo/{id}', templated:true )
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:links) do
|
100
|
+
[
|
101
|
+
link_a,
|
102
|
+
{ href:'/bar/{id}', templated:true }
|
103
|
+
]
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns the first matching link' do
|
107
|
+
expect(subject).to eq link_a
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'with no matching links' do
|
112
|
+
let(:links) do
|
113
|
+
[
|
114
|
+
{ href:'/foo/{fname}', templated:true },
|
115
|
+
{ href:'/foo/{lname}', templated:true }
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'raises an error' do
|
120
|
+
expect{ subject }.to raise_error Frenetic::HypermediaError
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#[]' do
|
126
|
+
subject { super()[rel] }
|
127
|
+
|
128
|
+
let(:link_b) do
|
129
|
+
Frenetic::HypermediaLink.new( href:'/bar', rel:'bar' )
|
130
|
+
end
|
131
|
+
|
132
|
+
let(:links) do
|
133
|
+
[
|
134
|
+
{ href:'/foo', rel:'foo' },
|
135
|
+
link_b
|
136
|
+
]
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'for a relation that exists' do
|
140
|
+
let(:rel) { :bar }
|
141
|
+
|
142
|
+
it 'returns the desired link' do
|
143
|
+
expect(subject).to eq link_b
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'for a relation that does not exist' do
|
148
|
+
let(:rel) { :baz }
|
149
|
+
|
150
|
+
it 'returns nothing' do
|
151
|
+
expect(subject).to be_nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Frenetic::HypermediaLink do
|
4
|
+
let(:basic_link) do
|
5
|
+
{ href:'/api/my_link' }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:templated_link) do
|
9
|
+
{ href:'/api/my_link/{id}-{title}', templated:true }
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:relation_link) do
|
13
|
+
{ href:'/api/foo', rel:'foo' }
|
14
|
+
end
|
15
|
+
|
16
|
+
subject { described_class.new(link) }
|
17
|
+
|
18
|
+
describe '#href' do
|
19
|
+
subject { super().href data }
|
20
|
+
|
21
|
+
context 'with a non-templated link' do
|
22
|
+
let(:link) { basic_link }
|
23
|
+
let(:data) {}
|
24
|
+
|
25
|
+
it 'correctly transforms the data' do
|
26
|
+
expect(subject).to eq '/api/my_link'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with a templated link' do
|
31
|
+
let(:link) { templated_link }
|
32
|
+
|
33
|
+
context 'and complate template data' do
|
34
|
+
let(:data) { { id:1, title:'title' } }
|
35
|
+
|
36
|
+
it 'correctly transforms the data' do
|
37
|
+
expect(subject).to eq '/api/my_link/1-title'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'and incomplete template data' do
|
42
|
+
let(:data) { { id:1 } }
|
43
|
+
|
44
|
+
it 'raises an error' do
|
45
|
+
expect{subject}.to raise_error Frenetic::HypermediaError
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'and non-Hash template data' do
|
50
|
+
let(:templated_link) do
|
51
|
+
{ href:'/api/my_link/{id}', templated:true }
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:data) { 1 }
|
55
|
+
|
56
|
+
it 'infers the template data' do
|
57
|
+
expect(subject).to eq '/api/my_link/1'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#templated?' do
|
64
|
+
subject { super().templated? }
|
65
|
+
|
66
|
+
context 'with a non-templated link' do
|
67
|
+
let(:link) { basic_link }
|
68
|
+
|
69
|
+
it 'returns FALSE' do
|
70
|
+
expect(subject).to eq false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'with a templated link' do
|
75
|
+
let(:link) { templated_link }
|
76
|
+
|
77
|
+
it 'returns TRUE' do
|
78
|
+
expect(subject).to eq true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#expandable?' do
|
84
|
+
subject { super().expandable? data }
|
85
|
+
|
86
|
+
let(:link) { templated_link }
|
87
|
+
|
88
|
+
context 'when the data can fully fulfill the template requirements' do
|
89
|
+
let(:data) { { id:1, title:'title' } }
|
90
|
+
|
91
|
+
it 'returns TRUE' do
|
92
|
+
expect(subject).to eq true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'when the data cannot fully fulfill the template requirements' do
|
97
|
+
let(:data) { { id:1 } }
|
98
|
+
|
99
|
+
it 'returns FALSE' do
|
100
|
+
expect(subject).to eq false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when the data provides more than the template requires' do
|
105
|
+
let(:data) { { id:1, title:'title', garbage:true } }
|
106
|
+
|
107
|
+
it 'returns TRUE' do
|
108
|
+
expect(subject).to eq true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with a non-template URL' do
|
113
|
+
let(:link) { basic_link }
|
114
|
+
|
115
|
+
let(:data) { { id:1 } }
|
116
|
+
|
117
|
+
it 'returns FALSE' do
|
118
|
+
expect(subject).to eq false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#template' do
|
124
|
+
subject { super().template }
|
125
|
+
|
126
|
+
let(:link) { templated_link }
|
127
|
+
|
128
|
+
it 'returns an Addressable Template' do
|
129
|
+
expect(subject).to be_an_instance_of Addressable::Template
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#as_json' do
|
134
|
+
subject { super().as_json }
|
135
|
+
|
136
|
+
let(:link) { templated_link }
|
137
|
+
|
138
|
+
it 'returns a Hash representation of the link' do
|
139
|
+
expect(subject).to include 'href' => '/api/my_link/{id}-{title}'
|
140
|
+
expect(subject).to include 'templated' => true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#rel' do
|
145
|
+
subject { super().rel }
|
146
|
+
|
147
|
+
let(:link) { relation_link }
|
148
|
+
|
149
|
+
it 'returns the relation name' do
|
150
|
+
expect(subject).to eq 'foo'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -1,13 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Frenetic::Middleware::HalJson do
|
4
|
-
|
5
4
|
def process(body, content_type = nil, options = {}, status = 200)
|
6
5
|
env = {
|
7
|
-
body:
|
8
|
-
request:
|
9
|
-
response_headers: Faraday::Utils::Headers.new(
|
10
|
-
status:
|
6
|
+
body: body,
|
7
|
+
request: options,
|
8
|
+
response_headers: Faraday::Utils::Headers.new(headers),
|
9
|
+
status: status
|
11
10
|
}
|
12
11
|
env[:response_headers]['content-type'] = content_type if content_type
|
13
12
|
|
@@ -15,9 +14,7 @@ describe Frenetic::Middleware::HalJson do
|
|
15
14
|
end
|
16
15
|
|
17
16
|
let(:options) { Hash.new }
|
18
|
-
|
19
17
|
let(:headers) { Hash.new }
|
20
|
-
|
21
18
|
let(:middleware) do
|
22
19
|
described_class.new(lambda {|env|
|
23
20
|
Faraday::Response.new(env)
|
@@ -51,7 +48,7 @@ describe Frenetic::Middleware::HalJson do
|
|
51
48
|
|
52
49
|
subject { process(body) }
|
53
50
|
|
54
|
-
it '
|
51
|
+
it 'parses the body' do
|
55
52
|
expect(subject.body).to include 'name' => 'My Name'
|
56
53
|
end
|
57
54
|
end
|
@@ -61,23 +58,24 @@ describe Frenetic::Middleware::HalJson do
|
|
61
58
|
|
62
59
|
context 'from the server' do
|
63
60
|
let(:status) { 500 }
|
61
|
+
let(:error) do
|
62
|
+
{ 'status' => status.to_s, error:'500 Server Error' }.to_json
|
63
|
+
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
it 'should raise an error' do
|
65
|
+
it 'raises an error' do
|
68
66
|
expect{ subject }.to raise_error Frenetic::ServerError
|
69
67
|
end
|
70
68
|
end
|
71
69
|
|
72
70
|
context 'cause by the client' do
|
73
71
|
let(:status) { 404 }
|
72
|
+
let(:error) do
|
73
|
+
{ 'status' => status.to_s, error:'404 Not Found' }.to_json
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
it 'should raise an error' do
|
76
|
+
it 'raises an error' do
|
78
77
|
expect{ subject }.to raise_error Frenetic::ClientError
|
79
78
|
end
|
80
79
|
end
|
81
80
|
end
|
82
|
-
|
83
81
|
end
|
@@ -43,34 +43,36 @@ describe Frenetic::ResourceCollection do
|
|
43
43
|
|
44
44
|
subject(:instance) { described_class.new(MyTempResource, collection_response) }
|
45
45
|
|
46
|
-
it '
|
47
|
-
subject.collection_key.
|
46
|
+
it 'knows where the resources are located' do
|
47
|
+
expect(subject.collection_key).to eq 'my_temp_resources'
|
48
48
|
end
|
49
49
|
|
50
|
-
it '
|
51
|
-
subject.resource_type.
|
50
|
+
it 'knows which resource it represents' do
|
51
|
+
expect(subject.resource_type).to eq 'my_temp_resource'
|
52
52
|
end
|
53
53
|
|
54
|
-
it '
|
55
|
-
subject.size.
|
54
|
+
it 'extracts the embedded resources' do
|
55
|
+
expect(subject.size).to eq 2
|
56
56
|
end
|
57
57
|
|
58
|
-
it '
|
59
|
-
subject.first.
|
58
|
+
it 'parses the embedded resources' do
|
59
|
+
expect(subject.first).to be_a MyTempResource
|
60
60
|
end
|
61
61
|
|
62
|
-
it '
|
63
|
-
subject.api.
|
62
|
+
it 'is able to make API calls' do
|
63
|
+
expect(subject.api).to be_an_instance_of Frenetic
|
64
64
|
end
|
65
65
|
|
66
|
-
it '
|
67
|
-
subject.links.
|
66
|
+
it 'has links' do
|
67
|
+
expect(subject.links).to_not be_empty
|
68
68
|
end
|
69
69
|
|
70
70
|
context 'for a non-embedded resource' do
|
71
71
|
subject { described_class.new(MyTempResource) }
|
72
72
|
|
73
|
-
it
|
73
|
+
it 'is empty' do
|
74
|
+
expect(subject).to be_empty
|
75
|
+
end
|
74
76
|
end
|
75
77
|
|
76
78
|
describe '#get' do
|
@@ -78,9 +80,8 @@ describe Frenetic::ResourceCollection do
|
|
78
80
|
|
79
81
|
subject { super().get(1) }
|
80
82
|
|
81
|
-
it '
|
82
|
-
subject.
|
83
|
+
it 'issues a GET the full representation' do
|
84
|
+
expect(subject).to be_an_instance_of MyTempResource
|
83
85
|
end
|
84
86
|
end
|
85
|
-
|
86
87
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'frenetic/resource_mockery'
|
4
|
+
|
5
|
+
describe Frenetic::ResourceMockery do
|
6
|
+
let(:my_temp_resource) do
|
7
|
+
Class.new(Frenetic::Resource)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:my_mocked_resource) do
|
11
|
+
Class.new(my_temp_resource) do
|
12
|
+
def self.default_attributes
|
13
|
+
{ qux:'qux' }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
stub_const 'MyNamespace::MyMockedResource', my_mocked_resource
|
20
|
+
MyNamespace::MyMockedResource.send :include, described_class
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:params) { { foo:1, bar:'baz' } }
|
24
|
+
|
25
|
+
subject { MyNamespace::MyMockedResource.new params }
|
26
|
+
|
27
|
+
it 'violates some basic CS principles by telling the parent-class of its existence' do
|
28
|
+
expect(my_temp_resource.instance_variables).to include :@mock_class
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#properties' do
|
32
|
+
subject { super().properties }
|
33
|
+
|
34
|
+
it 'returns a hash of available properties' do
|
35
|
+
expect(subject).to include 'foo' => 'fixnum'
|
36
|
+
expect(subject).to include 'bar' => 'string'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#attributes' do
|
41
|
+
subject { super().attributes }
|
42
|
+
|
43
|
+
it 'returns a hash of the resources attributes' do
|
44
|
+
expect(subject).to include 'foo' => 1
|
45
|
+
expect(subject).to include 'bar' => 'baz'
|
46
|
+
expect(subject).to include 'qux' => 'qux'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.default_attributes' do
|
51
|
+
let(:my_mocked_resource) { Class.new(my_temp_resource) }
|
52
|
+
|
53
|
+
subject { MyNamespace::MyMockedResource.default_attributes }
|
54
|
+
|
55
|
+
it 'allows implementors to specify sane defaults' do
|
56
|
+
expect(subject).to eq Hash.new
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#default_attributes' do
|
61
|
+
let(:my_mocked_resource) { Class.new(my_temp_resource) }
|
62
|
+
|
63
|
+
subject { MyNamespace::MyMockedResource.new.default_attributes }
|
64
|
+
|
65
|
+
it 'proxies to the class method' do
|
66
|
+
expect(subject).to eq Hash.new
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|