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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/Appraisals +9 -0
  4. data/Gemfile +1 -1
  5. data/README.md +2 -2
  6. data/frenetic.gemspec +8 -5
  7. data/gemfiles/faraday_08.gemfile +10 -0
  8. data/gemfiles/faraday_08.gemfile.lock +77 -0
  9. data/gemfiles/faraday_09.gemfile +10 -0
  10. data/gemfiles/faraday_09.gemfile.lock +77 -0
  11. data/lib/frenetic.rb +57 -30
  12. data/lib/frenetic/briefly_memoizable.rb +34 -0
  13. data/lib/frenetic/concerns/collection_rest_methods.rb +1 -1
  14. data/lib/frenetic/concerns/hal_linked.rb +5 -35
  15. data/lib/frenetic/concerns/member_rest_methods.rb +0 -2
  16. data/lib/frenetic/concerns/structured.rb +0 -5
  17. data/lib/frenetic/connection.rb +110 -0
  18. data/lib/frenetic/errors.rb +112 -0
  19. data/lib/frenetic/hypermedia_link.rb +74 -0
  20. data/lib/frenetic/hypermedia_link_set.rb +43 -0
  21. data/lib/frenetic/middleware/hal_json.rb +9 -12
  22. data/lib/frenetic/resource.rb +22 -6
  23. data/lib/frenetic/resource_collection.rb +0 -1
  24. data/lib/frenetic/resource_mockery.rb +55 -1
  25. data/lib/frenetic/version.rb +1 -1
  26. data/spec/{concerns/breifly_memoizable_spec.rb → briefly_memoizable_spec.rb} +10 -18
  27. data/spec/concerns/hal_linked_spec.rb +49 -62
  28. data/spec/concerns/member_rest_methods_spec.rb +8 -10
  29. data/spec/concerns/structured_spec.rb +70 -75
  30. data/spec/connection_spec.rb +137 -0
  31. data/spec/fixtures/test_api_requests.rb +8 -2
  32. data/spec/frenetic_spec.rb +221 -133
  33. data/spec/hypermedia_link_set_spec.rb +155 -0
  34. data/spec/hypermedia_link_spec.rb +153 -0
  35. data/spec/middleware/hal_json_spec.rb +13 -15
  36. data/spec/resource_collection_spec.rb +17 -16
  37. data/spec/resource_mockery_spec.rb +69 -0
  38. data/spec/resource_spec.rb +110 -63
  39. data/spec/support/rspec.rb +0 -1
  40. metadata +88 -75
  41. data/lib/frenetic/concerns/briefly_memoizable.rb +0 -34
  42. data/lib/frenetic/concerns/configurable.rb +0 -59
  43. data/lib/frenetic/concerns/resource_mockery.rb +0 -48
  44. data/lib/frenetic/configuration.rb +0 -88
  45. data/spec/concerns/configurable_spec.rb +0 -50
  46. data/spec/concerns/resource_mockery_spec.rb +0 -56
  47. 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: body,
8
- request: options,
9
- response_headers: Faraday::Utils::Headers.new( headers ),
10
- status: 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 'should parse the body' do
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
- let(:error) { { 'status' => status.to_s, error:'500 Server Error' }.to_json }
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
- let(:error) { { 'status' => status.to_s, error:'404 Not Found' }.to_json }
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 'should know where the resources are located' do
47
- subject.collection_key.should == 'my_temp_resources'
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 'should know which resource it represents' do
51
- subject.resource_type.should == 'my_temp_resource'
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 'should extract the embedded resources' do
55
- subject.size.should == 2
54
+ it 'extracts the embedded resources' do
55
+ expect(subject.size).to eq 2
56
56
  end
57
57
 
58
- it 'should parse the embedded resources' do
59
- subject.first.should be_a MyTempResource
58
+ it 'parses the embedded resources' do
59
+ expect(subject.first).to be_a MyTempResource
60
60
  end
61
61
 
62
- it 'should be able to make API calls' do
63
- subject.api.should be_an_instance_of Frenetic
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 'should have links' do
67
- subject.links.should_not be_empty
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 { should be_empty }
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 'should GET the full representation' do
82
- subject.should be_an_instance_of MyTempResource
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