frenetic 0.0.12 → 0.0.20.alpha.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.
Files changed (43) hide show
  1. data/.gitignore +1 -0
  2. data/.irbrc +3 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +0 -1
  5. data/Gemfile +1 -3
  6. data/README.md +138 -125
  7. data/frenetic.gemspec +5 -6
  8. data/lib/frenetic.rb +31 -43
  9. data/lib/frenetic/concerns/collection_rest_methods.rb +13 -0
  10. data/lib/frenetic/concerns/configurable.rb +59 -0
  11. data/lib/frenetic/concerns/hal_linked.rb +59 -0
  12. data/lib/frenetic/concerns/member_rest_methods.rb +15 -0
  13. data/lib/frenetic/concerns/structured.rb +53 -0
  14. data/lib/frenetic/configuration.rb +40 -76
  15. data/lib/frenetic/middleware/hal_json.rb +23 -0
  16. data/lib/frenetic/resource.rb +77 -62
  17. data/lib/frenetic/resource_collection.rb +46 -0
  18. data/lib/frenetic/version.rb +2 -2
  19. data/spec/concerns/configurable_spec.rb +50 -0
  20. data/spec/concerns/hal_linked_spec.rb +116 -0
  21. data/spec/concerns/member_rest_methods_spec.rb +41 -0
  22. data/spec/concerns/structured_spec.rb +214 -0
  23. data/spec/configuration_spec.rb +99 -0
  24. data/spec/fixtures/test_api_requests.rb +88 -0
  25. data/spec/frenetic_spec.rb +137 -0
  26. data/spec/middleware/hal_json_spec.rb +83 -0
  27. data/spec/resource_collection_spec.rb +80 -0
  28. data/spec/resource_spec.rb +211 -0
  29. data/spec/spec_helper.rb +4 -13
  30. data/spec/support/rspec.rb +5 -0
  31. data/spec/support/webmock.rb +3 -0
  32. metadata +59 -57
  33. data/.rvmrc +0 -1
  34. data/lib/frenetic/hal_json.rb +0 -23
  35. data/lib/frenetic/hal_json/response_wrapper.rb +0 -43
  36. data/lib/recursive_open_struct.rb +0 -79
  37. data/spec/fixtures/vcr_cassettes/description_error_unauthorized.yml +0 -36
  38. data/spec/fixtures/vcr_cassettes/description_success.yml +0 -38
  39. data/spec/lib/frenetic/configuration_spec.rb +0 -189
  40. data/spec/lib/frenetic/hal_json/response_wrapper_spec.rb +0 -70
  41. data/spec/lib/frenetic/hal_json_spec.rb +0 -68
  42. data/spec/lib/frenetic/resource_spec.rb +0 -182
  43. data/spec/lib/frenetic_spec.rb +0 -129
@@ -0,0 +1,46 @@
1
+ require 'frenetic/concerns/hal_linked'
2
+ require 'frenetic/concerns/collection_rest_methods'
3
+
4
+ class Frenetic
5
+ class ResourceCollection < Delegator
6
+ include HalLinked
7
+ include CollectionRestMethods
8
+
9
+ def initialize( resource, params = {} )
10
+ @resource_class = resource
11
+ @resources = []
12
+ @params = params
13
+
14
+ extract_resources!
15
+ end
16
+
17
+ def resource_type
18
+ @resource_type ||= @resource_class.to_s.demodulize.underscore
19
+ end
20
+
21
+ def collection_key
22
+ @collection_key ||= resource_type.pluralize
23
+ end
24
+
25
+ def __getobj__
26
+ @resources
27
+ end
28
+
29
+ def __setobj__
30
+ @resources
31
+ end
32
+
33
+ def api
34
+ @resources.first.api
35
+ end
36
+
37
+ private
38
+
39
+ def extract_resources!
40
+ @resources = @params['_embedded'][collection_key].collect do |resource|
41
+ @resource_class.new resource
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  class Frenetic
2
- VERSION = '0.0.12'
3
- end
2
+ VERSION = '0.0.20.alpha.0'
3
+ end
@@ -0,0 +1,50 @@
1
+ describe Frenetic::Configurable do
2
+ let(:test_cfg) do
3
+ {
4
+ url:'http://example.org'
5
+ }
6
+ end
7
+
8
+ subject(:instance) { Frenetic.new( test_cfg ) }
9
+
10
+ describe '#config' do
11
+ subject { instance.config }
12
+
13
+ it { should_not be_empty }
14
+ end
15
+
16
+ describe '#configure' do
17
+ subject do
18
+ cfg = nil
19
+ instance.configure { |c| cfg = c }
20
+ cfg
21
+ end
22
+
23
+ it 'should yield the configuration' do
24
+ subject.should be_a Hash
25
+ end
26
+ end
27
+
28
+ describe '.configure' do
29
+ subject { Frenetic.configure{|c|} }
30
+
31
+ it 'should not exist' do
32
+ expect{ subject }.to raise_error NoMethodError
33
+ end
34
+ end
35
+
36
+ describe '#initialize' do
37
+ let(:callback) do
38
+ Proc.new { |b| }
39
+ end
40
+
41
+ subject do
42
+ Frenetic.new( &callback ).instance_variable_get( "@builder_config" )
43
+ end
44
+
45
+ it 'retain block arguments' do
46
+ subject.should eq callback
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::HalLinked do
4
+ let(:test_cfg) { { url:'http://example.com/api' } }
5
+
6
+ let(:my_temp_resource) do
7
+ cfg = test_cfg
8
+
9
+ Class.new(Frenetic::Resource) do
10
+ api_client { Frenetic.new(cfg) }
11
+ end
12
+ end
13
+
14
+ before do
15
+ stub_const 'MyTempResource', my_temp_resource
16
+
17
+ MyTempResource.send :include, described_class
18
+ end
19
+
20
+ describe '#links' do
21
+ before { @stubs.api_description }
22
+
23
+ let(:_links) do
24
+ {
25
+ '_links' => { 'self' => { 'href' => '/api/self' }}
26
+ }
27
+ end
28
+
29
+ subject { MyTempResource.new(_links).links }
30
+
31
+ it 'should return the instances links' do
32
+ subject.should include 'self'
33
+ end
34
+ end
35
+
36
+ describe '#member_url' do
37
+ before { @stubs.api_description }
38
+
39
+ subject { MyTempResource.new(_links).member_url }
40
+
41
+ let(:_links) do
42
+ {
43
+ '_links' => {
44
+ 'self' => { 'href' => '/api/self' },
45
+ 'my_temp_resource' => {
46
+ 'href' => '/api/my_temp_resource/{id}', 'templated' => true
47
+ }
48
+ }
49
+ }
50
+ end
51
+
52
+ context 'with a link that matches the resource name' do
53
+ it 'should return the named link' do
54
+ subject.should == '/api/my_temp_resource/'
55
+ end
56
+ end
57
+
58
+ context 'with an implied self link' do
59
+ let(:_links) do
60
+ {
61
+ '_links' => { 'self' => { 'href' => '/api/self' }}
62
+ }
63
+ end
64
+
65
+ it 'should return the :self link' do
66
+ subject.should == '/api/self'
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '.member_url' do
72
+ let(:args) {}
73
+
74
+ subject { MyTempResource.member_url args }
75
+
76
+ before { @stubs.api_description }
77
+
78
+ context 'for an unknown resource' do
79
+ before do
80
+ MyTempResource.stub(:namespace).and_return Time.now.to_i.to_s
81
+ end
82
+
83
+ it 'should raise an error' do
84
+ expect{ subject }.to raise_error Frenetic::HypermediaError
85
+ end
86
+ end
87
+
88
+ context 'with a non-templated URL' do
89
+ before do
90
+ MyTempResource.stub(:namespace).and_return 'abstract_resource'
91
+ end
92
+
93
+ it 'simply return the URL' do
94
+ subject.should == '/api/abstract_resource'
95
+ end
96
+ end
97
+
98
+ context 'with a templated URL' do
99
+ context 'and a non-Hash argument' do
100
+ let(:args) { 1 }
101
+
102
+ it 'should expand the URL template' do
103
+ subject.should == '/api/my_temp_resources/1'
104
+ end
105
+ end
106
+
107
+ context 'with a Hash argument' do
108
+ let(:args) { { id:1 } }
109
+
110
+ it 'should interpolate the URL' do
111
+ subject.should == '/api/my_temp_resources/1'
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::MemberRestMethods do
4
+ let(:test_cfg) { { url:'http://example.com/api' } }
5
+
6
+ let(:my_temp_resource) do
7
+ cfg = test_cfg
8
+
9
+ Class.new(Frenetic::Resource) do
10
+ api_client { Frenetic.new(cfg) }
11
+ end
12
+ end
13
+
14
+ before do
15
+ stub_const 'MyTempResource', my_temp_resource
16
+
17
+ MyTempResource.send :include, described_class
18
+ end
19
+
20
+ describe '.find' do
21
+ before { @stubs.api_description }
22
+
23
+ subject { MyTempResource.find 1 }
24
+
25
+ context 'for a known instance' do
26
+ before { @stubs.known_resource }
27
+
28
+ it 'should return the instance' do
29
+ expect(subject).to be_a MyTempResource
30
+ end
31
+ end
32
+
33
+ context 'for an unknown instance' do
34
+ before { @stubs.unknown_resource }
35
+
36
+ it 'should raise an error' do
37
+ expect{ subject }.to raise_error Frenetic::ClientError
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,214 @@
1
+ require 'spec_helper'
2
+
3
+ describe Frenetic::Structured do
4
+ let(:my_temp_resource) do
5
+ Class.new do
6
+ def initialize( attrs = {} )
7
+ @attrs = attrs
8
+ end
9
+ end
10
+ end
11
+
12
+ before do
13
+ stub_const 'MyTempResource', my_temp_resource
14
+
15
+ MyTempResource.send :include, described_class
16
+ end
17
+
18
+ after do
19
+ instance.rspec_reset
20
+
21
+ instance.destroy_structure!
22
+ end
23
+
24
+ subject(:instance) { MyTempResource.new( foo:'foo', bar:'bar' ) }
25
+
26
+ describe '#struct_key' do
27
+ subject { instance.struct_key }
28
+
29
+ it 'should return a valid, unique Ruby constant name' do
30
+ subject.should == 'MyTempResourceFreneticResourceStruct'
31
+ end
32
+ end
33
+
34
+ describe '#signature' do
35
+ subject { instance.signature }
36
+
37
+ it 'should return a unique and predictable key' do
38
+ subject.should == 'barfoo'
39
+ end
40
+ end
41
+
42
+ describe '#structure' do
43
+ subject { instance.structure }
44
+
45
+ context 'with no previously defined signature' do
46
+ before do
47
+ instance.stub( :structure_expired? ).and_return true
48
+ end
49
+
50
+ it 'should rebuild the structure' do
51
+ instance.should_receive :rebuild_structure!
52
+
53
+ subject
54
+ end
55
+ end
56
+
57
+ context 'with a fresh signature' do
58
+ before do
59
+ instance.structure
60
+
61
+ instance.stub( :structure_expired? ).and_return false
62
+ end
63
+
64
+ it 'should not rebuild the structure' do
65
+ instance.should_receive( :rebuild_structure! ).never
66
+
67
+ subject
68
+ end
69
+ end
70
+
71
+ context 'with an expired signature' do
72
+ before do
73
+ instance.structure
74
+
75
+ instance.stub( :structure_expired? ).and_return true
76
+ end
77
+
78
+ it 'should rebuild the structure' do
79
+ instance.should_receive :rebuild_structure!
80
+
81
+ subject
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#fetch_structure' do
87
+ before { instance.structure }
88
+
89
+ subject { instance.fetch_structure }
90
+
91
+ it "should return the resource's Struct class" do
92
+ subject.should == Struct::MyTempResourceFreneticResourceStruct
93
+ end
94
+ end
95
+
96
+ describe '#rebuild_structure!' do
97
+ before{ instance.structure }
98
+
99
+ subject { instance.rebuild_structure! }
100
+
101
+ it 'should destroy the previous Struct' do
102
+ instance.should_receive(:destroy_structure!).and_call_original
103
+
104
+ subject
105
+ end
106
+
107
+ it "should add cache the resource's signature" do
108
+ sigs = described_class.class_variable_get('@@signatures')
109
+
110
+ sigs.should include
111
+ end
112
+
113
+ it "should build the resource's Struct" do
114
+ subject
115
+
116
+ instance.fetch_structure.members.should == [:foo, :bar]
117
+ end
118
+ end
119
+
120
+ describe '#structure_expired?' do
121
+ subject { instance.structure_expired? }
122
+
123
+ before do
124
+ instance.stub( :signature ).and_return new_sig
125
+ described_class.class_variable_set '@@signatures', {
126
+ 'MyTempResourceFreneticResourceStruct' => old_sig
127
+ }
128
+ end
129
+
130
+ context 'with a fresh signature' do
131
+ let(:old_sig) { 'fresh' }
132
+ let(:new_sig) { 'fresh' }
133
+
134
+ it { should be_false }
135
+ end
136
+
137
+ context 'with no predefined signature' do
138
+ let(:old_sig) { nil }
139
+ let(:new_sig) { 'new' }
140
+
141
+ it { should be_true }
142
+ end
143
+
144
+ context 'with an expired signature' do
145
+ let(:old_sig) { 'old' }
146
+ let(:new_sig) { 'new' }
147
+
148
+ it { should be_true }
149
+ end
150
+ end
151
+
152
+ describe '#structure_defined?' do
153
+ subject { instance.structure_defined? }
154
+
155
+ let(:consts) { Struct.constants || [] }
156
+
157
+ before do
158
+ Struct.stub(:constants).and_return consts
159
+ end
160
+
161
+ after { Struct.rspec_reset }
162
+
163
+ it 'should check if the structure is defined' do
164
+ instance.stub(:struct_key).and_return 'Foo'
165
+
166
+ consts.should_receive( :include? ).with :Foo
167
+
168
+ subject
169
+ end
170
+ end
171
+
172
+ describe '#destroy_structure!' do
173
+ before { instance.structure }
174
+
175
+ subject { instance.destroy_structure! }
176
+
177
+ context 'with an undefined structure' do
178
+ before { instance.destroy_structure! }
179
+
180
+ it "should not attempt to remove the structure's constant" do
181
+ Struct.should_receive( :remove_const ).never
182
+
183
+ subject
184
+ end
185
+
186
+ it 'should not remove the signature from the cache' do
187
+ described_class.class_variable_get('@@signatures').should_receive( :delete ).never
188
+
189
+ subject
190
+ end
191
+ end
192
+
193
+ context 'with an predefined structure' do
194
+ it 'should remove the constant' do
195
+ Struct.constants.should include instance.struct_key.to_sym
196
+
197
+ subject
198
+
199
+ Struct.constants.should_not include instance.struct_key.to_sym
200
+ end
201
+
202
+ it 'should remove the signature from the cache' do
203
+ signature = { 'MyTempResourceFreneticResourceStruct' => 'barfoo' }
204
+
205
+ described_class.class_variable_get('@@signatures').should include signature
206
+
207
+ subject
208
+
209
+ described_class.class_variable_get('@@signatures').should_not include signature
210
+ end
211
+ end
212
+ end
213
+
214
+ end