party_resource 0.0.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.
@@ -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
+