party_resource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,173 @@
1
+ require File.expand_path(File.join(__FILE__, '..', '..', 'spec_helper'))
2
+ require 'fixtures/test_base_class'
3
+ require 'fixtures/other_class'
4
+ require 'fixtures/test_class'
5
+
6
+ describe TestClass do
7
+
8
+ let(:object) {TestClass.new(:foo)}
9
+
10
+ describe 'class level call' do
11
+ it 'raises an argument error when called with the wrong number of arguments' do
12
+ lambda { TestClass.find }.should raise_error(ArgumentError)
13
+ end
14
+
15
+ it 'finds an object' do
16
+ stub_request(:get, "http://fred:pass@myserver/path/find/99.ext").to_return(:body => 'some data')
17
+ TestClass.find(99).should == TestClass.new('some data')
18
+ end
19
+
20
+ it 'finds an object with extra options' do
21
+ stub_request(:get, "http://fred:pass@myserver/path/find/99.ext?extra=options").to_return(:body => 'some data')
22
+ TestClass.find(99, :extra => 'options').should == TestClass.new('some data')
23
+ end
24
+
25
+ it 'uses other connectors' do
26
+ stub_request(:get, 'http://otherserver/url').to_return(:body => 'from the otherserver')
27
+ OtherPartyClass.test.should == 'from the otherserver'
28
+ end
29
+ end
30
+
31
+ describe 'instance level call' do
32
+ it 'gets the result' do
33
+ stub_request(:put, "http://fred:pass@myserver/path/update/99.ext").to_return(:body => 'updated data')
34
+ TestClass.new(:var => 99).update
35
+ end
36
+ end
37
+
38
+ describe 'building connected objects' do
39
+ it 'build the requested response object' do
40
+ stub_request(:put, "http://fred:pass@myserver/path/update/99.ext").to_return(:body => 'updated data')
41
+ TestClass.new(:var => 99).update.should == OtherClass.new('updated data')
42
+ end
43
+
44
+ it 'passes the raw result back when requested' do
45
+ stub_request(:post, "http://fred:pass@myserver/path/save/file").with(:body => "data=somedata").to_return(:body => 'saved data')
46
+ TestClass.save('somedata').should == 'saved data'
47
+ end
48
+
49
+ it 'builds the result using the specified method' do
50
+ stub_request(:delete, "http://fred:pass@myserver/path/delete").to_return(:body => 'deleted OK')
51
+ TestClass.destroy.should be_true
52
+ end
53
+
54
+ it 'builds the result using the specified proc' do
55
+ stub_request(:get, "http://fred:pass@myserver/path/foo?value=908").to_return(:body => 'foo data')
56
+ TestClass.foo(908).should == 'New foo data Improved'
57
+ end
58
+
59
+ it 'builds each value in an array individually' do
60
+ stub_request(:get, "http://fred:pass@myserver/path/foo?value=908").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '[foo,data]')
61
+ TestClass.foo(908).should == ['New foo Improved', 'New data Improved']
62
+ end
63
+
64
+ it 'passes "included" variables to the new object' do
65
+ v2 = mock(:v2)
66
+ stub_request(:get, "http://fred:pass@myserver/path/include").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{}')
67
+ test = TestClass.from_json(:value2 => v2)
68
+ test.include.should == OtherClass.new(:thing => v2)
69
+ end
70
+
71
+ context 'error cases' do
72
+ it 'raises ResourceNotFound' do
73
+ stub_request(:delete, "http://fred:pass@myserver/path/delete").to_return(:status => 404)
74
+ lambda { TestClass.destroy }.should raise_error(PartyResource::ResourceNotFound)
75
+ end
76
+
77
+ it 'raises ResourceInvalid' do
78
+ stub_request(:delete, "http://fred:pass@myserver/path/delete").to_return(:status => 422)
79
+ lambda { TestClass.destroy }.should raise_error(PartyResource::ResourceInvalid)
80
+ end
81
+
82
+ it 'raises ClientError' do
83
+ stub_request(:delete, "http://fred:pass@myserver/path/delete").to_return(:status => 405)
84
+ lambda { TestClass.destroy }.should raise_error(PartyResource::ClientError)
85
+ end
86
+
87
+ it 'raises ServerError' do
88
+ stub_request(:delete, "http://fred:pass@myserver/path/delete").to_return(:status => 501)
89
+ lambda { TestClass.destroy }.should raise_error(PartyResource::ServerError)
90
+ end
91
+
92
+ it 'rescues exceptions' do
93
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:status => 404)
94
+ TestClass.fetch_json.should be_nil
95
+ end
96
+
97
+ end
98
+ end
99
+
100
+ describe 'populating properties' do
101
+ it 'populates result values' do
102
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{value2:"value2", value4:"ignored"}')
103
+ result = TestClass.fetch_json
104
+ result.value2.should == 'value2'
105
+ result.value3.should == nil
106
+ result.should_not be_respond_to(:value4)
107
+ end
108
+
109
+ it 'populates renamed values' do
110
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{input_name:"value"}')
111
+ result = TestClass.fetch_json
112
+ result.value.should == 'value'
113
+ end
114
+
115
+ it 'populates nested values' do
116
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{block:{var:"value"}}')
117
+ result = TestClass.fetch_json
118
+ result.nested_value.should == 'value'
119
+ end
120
+
121
+ it 'falls back to populating based on the property name if from is not found' do
122
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{value:"value"}')
123
+ result = TestClass.fetch_json
124
+ result.value.should == 'value'
125
+ end
126
+
127
+ it 'populates a property as another class' do
128
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{other: "value"}')
129
+ result = TestClass.fetch_json
130
+ result.other.should == OtherClass.new('value')
131
+ end
132
+
133
+ it 'populates a property with a proc' do
134
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{processed: "value"}')
135
+ result = TestClass.fetch_json
136
+ result.processed.should == "Processed: value"
137
+ end
138
+
139
+ it 'does not build null data' do
140
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{processed: null}')
141
+ result = TestClass.fetch_json
142
+ result.processed.should == nil
143
+ end
144
+
145
+ it 'builds each value when populating an array' do
146
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{processed: [1,2,3,4]}')
147
+ result = TestClass.fetch_json
148
+ result.processed.should == ['Processed: 1','Processed: 2','Processed: 3','Processed: 4']
149
+ end
150
+
151
+ context 'and inherited class' do
152
+ it 'self refers to the child class' do
153
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{value2:"value2", child_property:"child"}')
154
+ result = InheritedTestClass.fetch_json
155
+ result.should be_a(InheritedTestClass)
156
+ end
157
+
158
+ it 'all local and inherited properties are available' do
159
+ stub_request(:get, "http://fred:pass@myserver/path/big_data").to_return(:headers => {'Content-Type' => 'text/json'}, :body => '{value2:"value2", child_property:"child"}')
160
+ result = InheritedTestClass.fetch_json
161
+ result.child_property.should == 'child'
162
+ result.value2.should == 'value2'
163
+ end
164
+ end
165
+ end
166
+
167
+ describe '#to_property_hash' do
168
+ it 'converts an object to external hash representation' do
169
+ obj = TestClass.from_json(:value => 'v1', :value2 => 'v2', :nested_value => 'nv', :processed => 'Milk', :child => {:thing => 'Happiness'})
170
+ obj.to_properties_hash.should == {:input_name => 'v1', :value2 => 'v2', :block => {:var => 'nv'}, :output_name => 'Processed: Milk', :child => {:thing => 'Happiness'}}
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'party_resource'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+ require 'webmock/rspec'
8
+
9
+ include WebMock
10
+
11
+ module LetMock
12
+ def let_mock(name, options = {})
13
+ let(name) { mock(name, options) }
14
+ end
15
+ end
16
+
17
+ Spec::Runner.configure do |config|
18
+ config.extend(LetMock)
19
+ end
20
+
@@ -0,0 +1,83 @@
1
+ require File.expand_path(File.join(__FILE__, '..', '..', '..', 'spec_helper'))
2
+
3
+ describe PartyResource::Connector::Base do
4
+ describe "creation" do
5
+ subject { PartyResource::Connector::Base.new(:test, options) }
6
+
7
+ let_mock(:username)
8
+ let_mock(:password)
9
+ let_mock(:normalized_uri)
10
+ let_mock(:original_uri)
11
+
12
+ before do
13
+ HTTParty.stub(:normalize_base_uri => normalized_uri)
14
+ end
15
+
16
+ context "with a base_uri" do
17
+ let(:options) { { :base_uri => original_uri } }
18
+
19
+ it 'normalizes the base_uri' do
20
+ HTTParty.should_receive(:normalize_base_uri).with(original_uri)
21
+ subject.options.should == {:base_uri => normalized_uri }
22
+ end
23
+ end
24
+
25
+ context 'with username' do
26
+ let(:options) { { :username => username } }
27
+
28
+ it 'stores the username' do
29
+ subject.options.should == {:basic_auth => {:username => username, :password => nil} }
30
+ end
31
+ end
32
+
33
+ context 'with password' do
34
+ let(:options) { { :password => password } }
35
+
36
+ it 'stores the password' do
37
+ subject.options.should == {:basic_auth => {:password => password, :username => nil} }
38
+ end
39
+ end
40
+
41
+ context 'with all options' do
42
+ let(:options) { { :base_uri => original_uri, :username => username, :password => password } }
43
+
44
+ it 'stores the options' do
45
+ subject.options.should == {:base_uri => normalized_uri,
46
+ :basic_auth => {:password => password, :username => username } }
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#fetch' do
52
+ let(:options) { {:base_uri => 'http://myserver.test/path'} }
53
+ let_mock(:data, :empty? => false)
54
+ let_mock(:path)
55
+ let_mock(:return_data, :code => 200)
56
+ let(:request) { mock(:request, :path => path, :verb => verb, :http_data => data) }
57
+
58
+ subject { PartyResource::Connector::Base.new(:test, options) }
59
+
60
+ [:put, :post, :delete, :get].each do |http_verb|
61
+ context "for #{http_verb} requests" do
62
+ let(:verb) { http_verb }
63
+ it "fetches the request using HTTParty" do
64
+ HTTParty.should_receive(verb).with(path, data).and_return(return_data)
65
+ subject.fetch(request).should == return_data
66
+ end
67
+
68
+ end
69
+ end
70
+
71
+ context 'error cases' do
72
+ let(:request) { mock(:request, :path => mock(:path), :verb => :get, :http_data => mock(:data)) }
73
+
74
+ it 'raises an exception' do
75
+ return_data = mock(:data, :code => 404)
76
+ HTTParty.should_receive(:get).and_return(return_data)
77
+ lambda{ subject.fetch(request) }.should raise_error(PartyResource::ConnectionError)
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.join(__FILE__, '..', '..', 'spec_helper'))
2
+
3
+ describe PartyResource::Connector do
4
+ describe '#lookup' do
5
+
6
+ let(:test_connector) { mock(:connector) }
7
+ let(:default_connector) { mock(:connector) }
8
+ let(:connectors) { { :test => test_connector, :other => default_connector } }
9
+
10
+ before do
11
+ PartyResource::Connector.repository.stub(:connectors => connectors)
12
+ end
13
+
14
+ it "returns the named connectors" do
15
+ PartyResource::Connector.lookup(:test).should == test_connector
16
+ end
17
+
18
+ it "returns the default connector for nil name" do
19
+ PartyResource::Connector.repository.stub(:default => :other)
20
+
21
+ PartyResource::Connector.lookup(nil).should == default_connector
22
+ end
23
+
24
+ it 'raises a NoConnector error it the connector could not be found' do
25
+ lambda { PartyResource::Connector.lookup(:missing_name) }.should raise_error(PartyResource::NoConnector)
26
+ end
27
+ end
28
+
29
+ describe '#default=' do
30
+ it 'sets the default connector name' do
31
+ name = mock(:name)
32
+ PartyResource::Connector.default = name
33
+ PartyResource::Connector.repository.default.should == name
34
+ end
35
+ end
36
+
37
+ describe '#new' do
38
+ let_mock(:name)
39
+ let_mock(:options)
40
+ let_mock(:connector)
41
+
42
+ before do
43
+ @repository = PartyResource::Connector::Repository.new
44
+ PartyResource::Connector.stub(:repository => @repository)
45
+ PartyResource::Connector::Base.stub(:new => connector)
46
+ options.stub(:[]).with(:default).and_return(false)
47
+ end
48
+
49
+ it 'creates a new connector' do
50
+ PartyResource::Connector::Base.should_receive(:new).with(name, options).and_return(connector)
51
+ PartyResource::Connector.add(name, options)
52
+ PartyResource::Connector(name).should == connector
53
+ end
54
+
55
+ it 'sets the default if it is currently unset' do
56
+ name2 = mock(:name2)
57
+ PartyResource::Connector.add(name, options)
58
+ PartyResource::Connector.repository.default.should == name
59
+
60
+ PartyResource::Connector.add(name2, options)
61
+ PartyResource::Connector.repository.default.should == name
62
+ end
63
+
64
+ it 'sets the default if the default option is set' do
65
+ name2 = mock(:name2)
66
+ PartyResource::Connector.add(name, options)
67
+ PartyResource::Connector.repository.default.should == name
68
+
69
+ options.stub(:[]).with(:default).and_return(true)
70
+ PartyResource::Connector.add(name2, options)
71
+ PartyResource::Connector.repository.default.should == name2
72
+ end
73
+ end
74
+ end
75
+
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Exceptions' do
4
+
5
+ describe PartyResource::ConnectionError do
6
+ subject { PartyResource::ConnectionError }
7
+
8
+ describe 'build' do
9
+ let(:built) { subject.build(data) }
10
+ let_mock(:data)
11
+
12
+ it 'builds ResourceNotFound' do
13
+ data.stub(:code => 404)
14
+ built.should be_a(PartyResource::ResourceNotFound)
15
+ end
16
+
17
+ it 'builds ResourceInvalid' do
18
+ data.stub(:code => 422)
19
+ built.should be_a(PartyResource::ResourceInvalid)
20
+ end
21
+
22
+ it 'builds ClientError' do
23
+ data.stub(:code => 400)
24
+ built.should be_a(PartyResource::ClientError)
25
+ end
26
+
27
+ it 'builds ServerError' do
28
+ data.stub(:code => 500)
29
+ built.should be_a(PartyResource::ServerError)
30
+ end
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,227 @@
1
+ require File.expand_path(File.join(__FILE__, '..', '..', 'spec_helper'))
2
+
3
+ describe "PartyResource" do
4
+
5
+ subject do
6
+ Class.new().send(:include, PartyResource)
7
+ end
8
+ let(:object) { subject.new }
9
+
10
+ describe '.connect' do
11
+
12
+ let(:route) { mock(:route) }
13
+ let_mock(:other_options)
14
+
15
+ before do
16
+ PartyResource::Route.stub(:new => route)
17
+ end
18
+
19
+ context 'for a class level route' do
20
+ let_mock(:klass)
21
+
22
+ it "creates a class method matching the name" do
23
+ subject.connect :new_resource_method
24
+ should respond_to(:new_resource_method)
25
+ end
26
+
27
+ it "creates a new route" do
28
+ options = {:values => other_options, :as => klass}
29
+ PartyResource::Route.should_receive(:new).with({:values => other_options, :connector => nil, :as => klass})
30
+ subject.connect :new_resource_method, options
31
+ end
32
+
33
+ context 'the created method' do
34
+
35
+ before { subject.connect :new_resource_method }
36
+
37
+ it 'calls the route with the class and the arguments' do
38
+ args = [mock(:arg), mock(:arg)]
39
+ route.should_receive(:call).with(subject, *args)
40
+
41
+ subject.new_resource_method args[0], args[1]
42
+ end
43
+
44
+ end
45
+
46
+ context 'with a connector set' do
47
+ before do
48
+ subject.party_connector :foo
49
+ end
50
+
51
+ it 'passes the connector name to the route' do
52
+ PartyResource::Route.should_receive(:new).with(hash_including(:connector => :foo))
53
+ subject.connect :new_resource_method, {}
54
+ end
55
+ end
56
+ end
57
+
58
+ context 'for an instance level route' do
59
+
60
+ it "creates an instance method matching the name" do
61
+ subject.connect :new_resource_method, :on => :instance
62
+ subject.new.should respond_to(:new_resource_method)
63
+ end
64
+
65
+ it "creates a new route" do
66
+ options = {:on => :instance, :others => other_options}
67
+ PartyResource::Route.should_receive(:new).with({:others => other_options, :connector => nil, :as => :self})
68
+ subject.connect :new_resource_method, options
69
+ end
70
+
71
+ context 'the created method' do
72
+
73
+ before { subject.connect :new_resource_method, :on => :instance }
74
+
75
+ it 'calls the route with the class and the arguments' do
76
+ args = [mock(:arg), mock(:arg)]
77
+ object = subject.new
78
+ route.should_receive(:call).with(object, *args)
79
+
80
+ object.new_resource_method args[0], args[1]
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '.Connector' do
88
+ context 'with name == nil' do
89
+ it 'returns the default connector' do
90
+ connector = mock(:connector)
91
+ PartyResource::Connector.should_receive(:lookup).with(nil).and_return(connector)
92
+ PartyResource::Connector(nil).should == connector
93
+ end
94
+ end
95
+
96
+ context 'with a name given' do
97
+ it 'returns the requested connector' do
98
+ connector = mock(:connector)
99
+ name = mock(:name)
100
+ PartyResource::Connector.should_receive(:lookup).with(name).and_return(connector)
101
+ PartyResource::Connector(name).should == connector
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '.parameter_values' do
107
+ let_mock(:v1)
108
+ let_mock(:v2)
109
+ let_mock(:v3)
110
+ it 'returns internal values for requested variables' do
111
+ subject.stub(:v1 => v1, :v2 => v2, :v3 => v3)
112
+ subject.parameter_values([:v1, :v3]).should == {:v1 => v1, :v3 => v3}
113
+ end
114
+
115
+ it 'raises a MissingParameter error' do
116
+ subject.stub(:v1 => v1)
117
+ lambda { subject.parameter_values([:v1, :vx]) }.should raise_error(PartyResource::MissingParameter)
118
+ end
119
+ end
120
+
121
+ describe 'properties' do
122
+ let_mock(:v1)
123
+ let_mock(:v2)
124
+ it 'populates property values from hash' do
125
+ subject.property :name, :name2
126
+ object.send(:populate_properties, :name => v1, :name2 => v2)
127
+ object.name.should == v1
128
+ object.name2.should == v2
129
+ end
130
+
131
+ it 'renames properties from passed data' do
132
+ subject.property :name, :from => :input_name
133
+ object.send(:populate_properties, :input_name => v1, :name => v2)
134
+ object.name.should == v1
135
+ end
136
+
137
+ it 'reaches inside nested values' do
138
+ subject.property :name, :from => [:parent, :input_name]
139
+ object.send(:populate_properties, :parent => { :input_name => v1 }, :input_name => v2)
140
+ object.name.should == v1
141
+ end
142
+
143
+ it 'falls back to populating based on the property name if from is not found' do
144
+ subject.property :name, :from => :input_name
145
+ object.send(:populate_properties, :name => v2)
146
+ object.name.should == v2
147
+ end
148
+
149
+ it 'does not try to build nil values' do
150
+ klass = Class.new
151
+ subject.property :name, :as => klass
152
+ klass.should_not_receive(:new)
153
+ object.send(:populate_properties, :name => nil)
154
+ object.name.should == nil
155
+ end
156
+
157
+ it "doesn't set properties to nil if they aren't in the data hash" do
158
+ subject.property :name, :name2
159
+ object.send(:populate_properties, :name => v1)
160
+ object.name.should == v1
161
+ object.name2.should == nil
162
+ object.send(:populate_properties,:name2 => v2)
163
+ object.name.should == v1
164
+ object.name2.should == v2
165
+ end
166
+
167
+ it 'returns a hash representation of properties' do
168
+ subject.property :name, :name2
169
+ object.send(:populate_properties, :name => v1, :name2 => v2)
170
+ object.to_properties_hash.should == {:name => v1, :name2 => v2}
171
+ end
172
+
173
+ it 'does not include nil values in the properties hash' do
174
+ subject.property :name, :name2
175
+ object.send(:populate_properties, :name => v1)
176
+ object.to_properties_hash.should == {:name => v1}
177
+ end
178
+
179
+ it 'translates propery names to input names' do
180
+ subject.property :name, :from => :input_name
181
+ object.send(:populate_properties, :name => v1)
182
+ object.to_properties_hash.should == {:input_name => v1}
183
+ end
184
+
185
+ it 'translates propery names to nested input names' do
186
+ subject.property :name, :from => [:block, :input_name]
187
+ object.send(:populate_properties, :name => v1)
188
+ object.to_properties_hash.should == {:block => {:input_name => v1}}
189
+ end
190
+
191
+ it 'translates property names to their required output names' do
192
+ subject.property :name, :to => :output_name
193
+ subject.property :name2, :to => [:block, :output_name]
194
+ object.send(:populate_properties, :name => v1, :name2 => v2)
195
+ object.to_properties_hash.should == {:output_name => v1, :block => {:output_name => v2}}
196
+ end
197
+ end
198
+
199
+ describe '#properties_equal?' do
200
+ let_mock(:v1)
201
+ let(:object2) { subject.new }
202
+
203
+ before do
204
+ subject.property :name, :name2
205
+ object.send(:populate_properties, :name => v1, :name2 => nil)
206
+ end
207
+
208
+ it 'returns true if all properties are equal' do
209
+ subject.property :name, :name2
210
+ object.send(:populate_properties, :name => v1, :name2 => nil)
211
+ object2.send(:populate_properties, :name => v1)
212
+ object.should be_properties_equal(object2)
213
+ end
214
+
215
+ it 'returns false if all properties are not equal' do
216
+ object2.send(:populate_properties, :name => v1, :name2 => v1)
217
+ object.should_not be_properties_equal(object2)
218
+ end
219
+
220
+ it 'returns false if the other object does not respond to all required properties' do
221
+ object.should_not be_properties_equal(Object.new)
222
+ end
223
+ end
224
+
225
+
226
+ end
227
+