frenetic 0.0.12 → 0.0.20.alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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