cardiac 0.2.0.pre2
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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/LICENSE +22 -0
- data/Rakefile +66 -0
- data/cardiac-0.2.0.pre2.gem +0 -0
- data/cardiac.gemspec +48 -0
- data/lib/cardiac/declarations.rb +70 -0
- data/lib/cardiac/errors.rb +65 -0
- data/lib/cardiac/log_subscriber.rb +55 -0
- data/lib/cardiac/model/attributes.rb +146 -0
- data/lib/cardiac/model/base.rb +161 -0
- data/lib/cardiac/model/callbacks.rb +47 -0
- data/lib/cardiac/model/declarations.rb +106 -0
- data/lib/cardiac/model/dirty.rb +117 -0
- data/lib/cardiac/model/locale/en.yml +7 -0
- data/lib/cardiac/model/operations.rb +49 -0
- data/lib/cardiac/model/persistence.rb +171 -0
- data/lib/cardiac/model/querying.rb +129 -0
- data/lib/cardiac/model/validations.rb +124 -0
- data/lib/cardiac/model.rb +17 -0
- data/lib/cardiac/operation_builder.rb +75 -0
- data/lib/cardiac/operation_handler.rb +215 -0
- data/lib/cardiac/railtie.rb +20 -0
- data/lib/cardiac/reflections.rb +85 -0
- data/lib/cardiac/representation.rb +124 -0
- data/lib/cardiac/resource/adapter.rb +178 -0
- data/lib/cardiac/resource/builder.rb +107 -0
- data/lib/cardiac/resource/codec_methods.rb +58 -0
- data/lib/cardiac/resource/config_methods.rb +39 -0
- data/lib/cardiac/resource/extension_methods.rb +115 -0
- data/lib/cardiac/resource/request_methods.rb +138 -0
- data/lib/cardiac/resource/subresource.rb +88 -0
- data/lib/cardiac/resource/uri_methods.rb +176 -0
- data/lib/cardiac/resource.rb +77 -0
- data/lib/cardiac/util.rb +120 -0
- data/lib/cardiac/version.rb +3 -0
- data/lib/cardiac.rb +61 -0
- data/spec/rails-3.2/Gemfile +9 -0
- data/spec/rails-3.2/Gemfile.lock +136 -0
- data/spec/rails-3.2/Rakefile +10 -0
- data/spec/rails-3.2/app_root/app/assets/javascripts/application.js +15 -0
- data/spec/rails-3.2/app_root/app/assets/stylesheets/application.css +13 -0
- data/spec/rails-3.2/app_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails-3.2/app_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails-3.2/app_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails-3.2/app_root/config/application.rb +29 -0
- data/spec/rails-3.2/app_root/config/boot.rb +13 -0
- data/spec/rails-3.2/app_root/config/database.yml +25 -0
- data/spec/rails-3.2/app_root/config/environment.rb +5 -0
- data/spec/rails-3.2/app_root/config/environments/development.rb +10 -0
- data/spec/rails-3.2/app_root/config/environments/production.rb +11 -0
- data/spec/rails-3.2/app_root/config/environments/test.rb +11 -0
- data/spec/rails-3.2/app_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails-3.2/app_root/config/initializers/inflections.rb +15 -0
- data/spec/rails-3.2/app_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails-3.2/app_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails-3.2/app_root/config/initializers/session_store.rb +8 -0
- data/spec/rails-3.2/app_root/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails-3.2/app_root/config/locales/en.yml +5 -0
- data/spec/rails-3.2/app_root/config/routes.rb +2 -0
- data/spec/rails-3.2/app_root/db/test.sqlite3 +0 -0
- data/spec/rails-3.2/app_root/log/test.log +2403 -0
- data/spec/rails-3.2/app_root/public/404.html +26 -0
- data/spec/rails-3.2/app_root/public/422.html +26 -0
- data/spec/rails-3.2/app_root/public/500.html +25 -0
- data/spec/rails-3.2/app_root/public/favicon.ico +0 -0
- data/spec/rails-3.2/app_root/script/rails +6 -0
- data/spec/rails-3.2/spec/spec_helper.rb +25 -0
- data/spec/rails-4.0/Gemfile +9 -0
- data/spec/rails-4.0/Gemfile.lock +132 -0
- data/spec/rails-4.0/Rakefile +10 -0
- data/spec/rails-4.0/app_root/app/assets/javascripts/application.js +15 -0
- data/spec/rails-4.0/app_root/app/assets/stylesheets/application.css +13 -0
- data/spec/rails-4.0/app_root/app/controllers/application_controller.rb +3 -0
- data/spec/rails-4.0/app_root/app/helpers/application_helper.rb +2 -0
- data/spec/rails-4.0/app_root/app/views/layouts/application.html.erb +14 -0
- data/spec/rails-4.0/app_root/config/application.rb +28 -0
- data/spec/rails-4.0/app_root/config/boot.rb +13 -0
- data/spec/rails-4.0/app_root/config/database.yml +25 -0
- data/spec/rails-4.0/app_root/config/environment.rb +5 -0
- data/spec/rails-4.0/app_root/config/environments/development.rb +9 -0
- data/spec/rails-4.0/app_root/config/environments/production.rb +11 -0
- data/spec/rails-4.0/app_root/config/environments/test.rb +10 -0
- data/spec/rails-4.0/app_root/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails-4.0/app_root/config/initializers/inflections.rb +15 -0
- data/spec/rails-4.0/app_root/config/initializers/mime_types.rb +5 -0
- data/spec/rails-4.0/app_root/config/initializers/secret_token.rb +7 -0
- data/spec/rails-4.0/app_root/config/initializers/session_store.rb +8 -0
- data/spec/rails-4.0/app_root/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails-4.0/app_root/config/locales/en.yml +5 -0
- data/spec/rails-4.0/app_root/config/routes.rb +2 -0
- data/spec/rails-4.0/app_root/db/test.sqlite3 +0 -0
- data/spec/rails-4.0/app_root/log/development.log +50 -0
- data/spec/rails-4.0/app_root/log/test.log +2399 -0
- data/spec/rails-4.0/app_root/public/404.html +26 -0
- data/spec/rails-4.0/app_root/public/422.html +26 -0
- data/spec/rails-4.0/app_root/public/500.html +25 -0
- data/spec/rails-4.0/app_root/public/favicon.ico +0 -0
- data/spec/rails-4.0/app_root/script/rails +6 -0
- data/spec/rails-4.0/spec/spec_helper.rb +25 -0
- data/spec/shared/cardiac/declarations_spec.rb +103 -0
- data/spec/shared/cardiac/model/base_spec.rb +446 -0
- data/spec/shared/cardiac/operation_builder_spec.rb +96 -0
- data/spec/shared/cardiac/operation_handler_spec.rb +82 -0
- data/spec/shared/cardiac/representation/reflection_spec.rb +73 -0
- data/spec/shared/cardiac/resource/adapter_spec.rb +83 -0
- data/spec/shared/cardiac/resource/builder_spec.rb +52 -0
- data/spec/shared/cardiac/resource/codec_methods_spec.rb +63 -0
- data/spec/shared/cardiac/resource/config_methods_spec.rb +52 -0
- data/spec/shared/cardiac/resource/extension_methods_spec.rb +215 -0
- data/spec/shared/cardiac/resource/request_methods_spec.rb +186 -0
- data/spec/shared/cardiac/resource/uri_methods_spec.rb +212 -0
- data/spec/shared/support/client_execution.rb +28 -0
- data/spec/spec_helper.rb +24 -0
- metadata +463 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cardiac::ExtensionMethods do
|
|
4
|
+
|
|
5
|
+
let(:base_url) { 'http://localhost/prefix/segment/suffix?q=foobar' }
|
|
6
|
+
let(:base_uri) { URI(base_url) }
|
|
7
|
+
let(:resource) { Cardiac::Resource.new(base_uri) }
|
|
8
|
+
let(:void) { lambda{||} }
|
|
9
|
+
|
|
10
|
+
subject { resource }
|
|
11
|
+
|
|
12
|
+
describe '#operation()' do
|
|
13
|
+
subject do
|
|
14
|
+
lambda{|n,b| resource.operation(n, b).send(:build_extension_module) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should define operations on an extension module' do
|
|
18
|
+
mod = subject[:foo, lambda{|| :bar }]
|
|
19
|
+
expect(mod).to be_a(Module)
|
|
20
|
+
expect(mod).to be_method_defined(:foo)
|
|
21
|
+
expect(Object.new.tap{|x| x.extend mod }.foo).to eq(:bar)
|
|
22
|
+
expect(resource).not_to respond_to(:foo)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should not execute the implementation block' do
|
|
26
|
+
expect { subject[:all, lambda{|| raise 'should not happen' }] }.not_to raise_error
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should not be allowed to override a subresource' do
|
|
30
|
+
resource.send(:subresources_values) << [:all, void, nil]
|
|
31
|
+
expect { subject[:all, void] }.to raise_error(ArgumentError, ":all has already been defined as a subresource")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'must be identified by a Symbol or String' do
|
|
35
|
+
expect { subject[1, void] }.to raise_error(ArgumentError)
|
|
36
|
+
expect { subject[nil, void] }.to raise_error(ArgumentError)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'must be implemented by a Proc' do
|
|
40
|
+
expect { subject[1, :get] }.to raise_error(ArgumentError)
|
|
41
|
+
expect { subject[nil, 'get'] }.to raise_error(ArgumentError)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe '#extending()' do
|
|
46
|
+
it 'must be implemented by a Module' do
|
|
47
|
+
expect { resource.extending(Proc.new{}){ def foo; :bar end }.send(:build_extension_module) }.to raise_error(ArgumentError)
|
|
48
|
+
expect { resource.extending(Module.new){ def foo; :bar end }.send(:build_extension_module) }.not_to raise_error
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'should not require an extension block' do
|
|
52
|
+
expect { resource.extending(Module.new){}.send(:build_extension_module) }.not_to raise_error
|
|
53
|
+
expect { resource.extending(Module.new).send(:build_extension_module) }.not_to raise_error
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'should not allow an extension block with arity != 0' do
|
|
57
|
+
expect{ resource.extending{|a| def foo; :bar end }.send(:build_extension_module) }.to raise_error(ArgumentError)
|
|
58
|
+
expect{ resource.extending{ def foo; :bar end }.send(:build_extension_module) }.not_to raise_error
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'should define operations on an extension module' do
|
|
62
|
+
mod = resource.extending{ def foo; :bar end }.send(:build_extension_module)
|
|
63
|
+
expect(mod).to be_a(Module)
|
|
64
|
+
expect(mod).to be_method_defined(:foo)
|
|
65
|
+
expect(Object.new.tap{|x| x.extend mod }.foo).to eq(:bar)
|
|
66
|
+
expect(resource).not_to respond_to(:foo)
|
|
67
|
+
expect(mod).not_to respond_to(:foo)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#subresource()' do
|
|
72
|
+
subject do
|
|
73
|
+
lambda{|n,b,e| resource.subresource(n, b, &e).send(:build_extension_module) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
let(:receiver) { double() }
|
|
77
|
+
let(:model) { double(to_param: '1') }
|
|
78
|
+
|
|
79
|
+
it 'should not be allowed to override an operation' do
|
|
80
|
+
resource.send(:operations_values) << [:all, lambda{||}]
|
|
81
|
+
expect { subject[:all, void, void] }.to raise_error(ArgumentError, ":all has already been defined as an operation")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'must be identified by a Symbol or String' do
|
|
85
|
+
expect { subject[1, void, void] }.to raise_error(ArgumentError)
|
|
86
|
+
expect { subject[nil, void, void] }.to raise_error(ArgumentError)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'must be implemented by a Proc' do
|
|
90
|
+
expect { subject[1, :get, void] }.to raise_error(ArgumentError)
|
|
91
|
+
expect { subject[nil, 'get', void] }.to raise_error(ArgumentError)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'should not require an extension block' do
|
|
95
|
+
expect { subject[:all, void, nil] }.not_to raise_error
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'should not allow an extension block with arity != 0' do
|
|
99
|
+
expect { subject[:all, void, void] }.not_to raise_error
|
|
100
|
+
expect { subject[:all, void, lambda{|a| }] }.to raise_error(ArgumentError)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
shared_examples 'building with ResourceBuilder' do
|
|
104
|
+
let!(:implementation_block) { lambda{|x| path(x.to_param) } }
|
|
105
|
+
|
|
106
|
+
let!(:extension_block){}
|
|
107
|
+
|
|
108
|
+
let! :subresource do
|
|
109
|
+
resource.subresource(:instance, implementation_block, &extension_block).send(:build_extension_module)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
subject! do
|
|
113
|
+
subresource
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# FYI - Something roughly similar would be done by "a" or "the" proxy/builder.
|
|
117
|
+
let :receiver do
|
|
118
|
+
Cardiac::ResourceBuilder.new(resource)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
let! :result do
|
|
122
|
+
expect_any_instance_of(Cardiac::Subresource).to receive(:path).with("1").and_call_original
|
|
123
|
+
expect(model).to receive(:to_param).with(no_args)
|
|
124
|
+
receiver.instance(model)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'is included on the Resource extension module' do
|
|
128
|
+
is_expected.to be_a(Module)
|
|
129
|
+
is_expected.not_to respond_to(:instance)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# FIXME: not critical, but it would be nice to define them correctly instead of _sufficiently_
|
|
133
|
+
it 'defines method arities sufficiently' do
|
|
134
|
+
expect([1,-1]).to be_include(subject.instance_method(:instance).arity)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'does not extend the Resource' do
|
|
138
|
+
expect(resource).not_to respond_to(:instance)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'is an instance method' do
|
|
142
|
+
is_expected.to be_method_defined(:instance)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'returns a builder' do
|
|
146
|
+
expect(result).to be_a(Cardiac::ResourceBuilder)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'does not extend the builder with the subresource method' do
|
|
150
|
+
expect(result).not_to respond_to(:instance)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it 'builds a subresource on the builder' do
|
|
154
|
+
expect(result.to_resource.to_uri.path).to eq('/prefix/segment/suffix/1')
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'does not build on the resource' do
|
|
158
|
+
expect(resource.to_uri.path).to eq('/prefix/segment/suffix')
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
shared_examples 'building and extending with ResourceBuilder' do |ext_name|
|
|
163
|
+
include_examples 'building with ResourceBuilder'
|
|
164
|
+
|
|
165
|
+
let(:subresult) { result.__send__(ext_name) }
|
|
166
|
+
|
|
167
|
+
it 'extends the builder with the extension method' do
|
|
168
|
+
expect(result).to respond_to(ext_name)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'returns a new builder from the extension method' do
|
|
172
|
+
expect(subresult).to be_a(Cardiac::ResourceBuilder)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe 'without an extension block' do
|
|
177
|
+
include_examples 'building with ResourceBuilder'
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe 'with an extension method' do
|
|
181
|
+
include_examples 'building and extending with ResourceBuilder', :foo do
|
|
182
|
+
let(:extension_block) { proc { def foo ; https ; end } }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'applies the extension block to the subresource on the builder' do
|
|
186
|
+
subr = subresult.to_resource
|
|
187
|
+
expect(subr.to_uri.path).to eq('/prefix/segment/suffix/1')
|
|
188
|
+
expect(resource.to_uri.path).to eq('/prefix/segment/suffix')
|
|
189
|
+
expect(subresult.to_resource.to_uri.scheme).to eq('https')
|
|
190
|
+
expect(resource.to_uri.scheme).to eq('http')
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe 'with an extension operation' do
|
|
195
|
+
include_examples 'building and extending with ResourceBuilder', :foo do
|
|
196
|
+
let(:extension_block) { proc { operation :foo, lambda{|| https } } }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# FIXME: this does not pass due to builder semantics, but the analogue
|
|
200
|
+
# works correctly with subresources - see spec/cardiac/model/base_spec.rb
|
|
201
|
+
#it 'does not extend the new builder with the operation' do
|
|
202
|
+
# subresult.should_not respond_to(:foo)
|
|
203
|
+
#end
|
|
204
|
+
|
|
205
|
+
it 'applies the extension block to the subresource on the builder' do
|
|
206
|
+
subr = subresult.to_resource
|
|
207
|
+
expect(subr.to_uri.path).to eq('/prefix/segment/suffix/1')
|
|
208
|
+
expect(resource.to_uri.path).to eq('/prefix/segment/suffix')
|
|
209
|
+
expect(subresult.to_resource.to_uri.scheme).to eq('https')
|
|
210
|
+
expect(resource.to_uri.scheme).to eq('http')
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cardiac::RequestMethods do
|
|
4
|
+
|
|
5
|
+
let(:base_url) { 'http://localhost/prefix/segment?q=foobar' }
|
|
6
|
+
let(:base_uri) { URI(base_url) }
|
|
7
|
+
let(:resource) { Cardiac::Resource.new(base_uri) }
|
|
8
|
+
let(:default_headers) { {:accepts => Cardiac::RequestMethods::DEFAULT_ACCEPTS } }
|
|
9
|
+
|
|
10
|
+
subject { resource }
|
|
11
|
+
|
|
12
|
+
describe 'headers built with' do
|
|
13
|
+
subject do
|
|
14
|
+
lambda{|*v| builder[resource, *v] ; resource.send(:build_headers).symbolize_keys.except(:accepts) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
after :each do
|
|
18
|
+
headers = resource.send(:build_headers)
|
|
19
|
+
expect(resource.reset_headers.send(:build_headers)).to eq(default_headers)
|
|
20
|
+
expect(resource.headers(headers).send(:build_headers)).to eq(headers)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
shared_examples 'header building' do |builder|
|
|
24
|
+
let(:builder) { builder }
|
|
25
|
+
|
|
26
|
+
describe "[{content_type: 'application/xml'}]" do
|
|
27
|
+
subject { super()[{content_type: 'application/xml'}] }
|
|
28
|
+
it { is_expected.to eq({content_type: 'application/xml'}) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "[{content_type: 'application/xml'},{content_type: 'application/json'}]" do
|
|
32
|
+
subject { super()[{content_type: 'application/xml'},{content_type: 'application/json'}] }
|
|
33
|
+
|
|
34
|
+
it 'has 1 item' do
|
|
35
|
+
is_expected.to eq({content_type: 'application/json'})
|
|
36
|
+
expect(subject.size).to eq(1)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe "[{'content_type' => 'application/json'},{content_type: 'application/xml'}]" do
|
|
41
|
+
subject { super()[{'content_type' => 'application/json'},{content_type: 'application/xml'}] }
|
|
42
|
+
|
|
43
|
+
it 'has 1 item' do
|
|
44
|
+
is_expected.to eq({content_type: 'application/xml'})
|
|
45
|
+
expect(subject.size).to eq(1)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#headers()' do
|
|
51
|
+
include_examples 'header building', lambda{|r,*v| r.headers(*v) }
|
|
52
|
+
|
|
53
|
+
describe "[{content_type: 'application/xml'},{content_type: false}]" do
|
|
54
|
+
subject { super()[{content_type: 'application/xml'},{content_type: false}] }
|
|
55
|
+
it { is_expected.to eq({content_type: false}) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe '#header()' do
|
|
60
|
+
let(:builder) { lambda{|r,*l| l.each{|k,v| r.header(k,v) } } }
|
|
61
|
+
|
|
62
|
+
describe "[[:content_type, 'application/xml']]" do
|
|
63
|
+
subject { super()[[:content_type, 'application/xml']] }
|
|
64
|
+
it { is_expected.to eq({content_type: 'application/xml'}) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "[['content_type', 'application/json']]" do
|
|
68
|
+
subject { super()[['content_type', 'application/json']] }
|
|
69
|
+
it { is_expected.to eq({content_type: 'application/json'}) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "[[:content_type, 'application/json'],['content_type', false]]" do
|
|
73
|
+
subject { super()[[:content_type, 'application/json'],['content_type', false]] }
|
|
74
|
+
it { is_expected.to eq({}) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe "[['content_type', 'application/json'],[:content_type, false]]" do
|
|
78
|
+
subject { super()[['content_type', 'application/json'],[:content_type, false]] }
|
|
79
|
+
it { is_expected.to eq({}) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "[[:content_type, false],[:content_type, 'application/xml']]" do
|
|
83
|
+
subject { super()[[:content_type, false],[:content_type, 'application/xml']] }
|
|
84
|
+
it { is_expected.to eq({content_type: 'application/xml'}) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "[['content_type', false],['content_type', 'application/json']]" do
|
|
88
|
+
subject { super()[['content_type', false],['content_type', 'application/json']] }
|
|
89
|
+
it { is_expected.to eq({content_type: 'application/json'}) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "[[:content_type,'application/xml'],[:content_type,'application/json']]" do
|
|
93
|
+
subject { super()[[:content_type,'application/xml'],[:content_type,'application/json']] }
|
|
94
|
+
|
|
95
|
+
it 'has 1 item' do
|
|
96
|
+
is_expected.to eq({content_type: 'application/json'})
|
|
97
|
+
expect(subject.size).to eq(1)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe "[['content_type', 'application/json'],[:content_type,'application/xml']]" do
|
|
102
|
+
subject { super()[['content_type', 'application/json'],[:content_type,'application/xml']] }
|
|
103
|
+
|
|
104
|
+
it 'has 1 item' do
|
|
105
|
+
is_expected.to eq({content_type: 'application/xml'})
|
|
106
|
+
expect(subject.size).to eq(1)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe '#options()' do
|
|
112
|
+
include_examples 'header building', lambda{|r,*l| l.each{|o| r.options(headers: o) } }
|
|
113
|
+
|
|
114
|
+
describe "[{content_type: 'application/xml'},{content_type: false}]" do
|
|
115
|
+
subject { super()[{content_type: 'application/xml'},{content_type: false}] }
|
|
116
|
+
it { is_expected.to eq({content_type: false}) }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe 'options built with' do
|
|
122
|
+
subject do
|
|
123
|
+
lambda{|*v| builder[resource, *v] ; resource.send(:build_options) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
after(:each) do
|
|
127
|
+
options = resource.send(:build_options)
|
|
128
|
+
expect(resource.reset_options.send(:build_options)).to eq({})
|
|
129
|
+
expect(resource.options(options).send(:build_options)).to eq(options)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe '#options()' do
|
|
133
|
+
let(:builder){ lambda{|r,*v| r.options(*v) } }
|
|
134
|
+
|
|
135
|
+
describe '[{timeout: 60}]' do
|
|
136
|
+
subject { super()[{timeout: 60}] }
|
|
137
|
+
it { is_expected.to eq({timeout: 60}) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe '[{timeout: 60},{timeout: 30}]' do
|
|
141
|
+
subject { super()[{timeout: 60},{timeout: 30}] }
|
|
142
|
+
|
|
143
|
+
it 'has 1 item' do
|
|
144
|
+
is_expected.to eq({timeout: 30})
|
|
145
|
+
expect(subject.size).to eq(1)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "[{'timeout' => 60},{timeout: 30}]" do
|
|
150
|
+
subject { super()[{'timeout' => 60},{timeout: 30}] }
|
|
151
|
+
|
|
152
|
+
it 'has 1 item' do
|
|
153
|
+
is_expected.to eq({timeout: 30})
|
|
154
|
+
expect(subject.size).to eq(1)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe '#option()' do
|
|
160
|
+
let(:builder){ lambda{|r,*l| l.each{|k,v| r.option(k,v) } } }
|
|
161
|
+
|
|
162
|
+
describe '[[:timeout, 60]]' do
|
|
163
|
+
subject { super()[[:timeout, 60]] }
|
|
164
|
+
it { is_expected.to eq({timeout: 60}) }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe '[[:timeout, 60],[:timeout, 30]]' do
|
|
168
|
+
subject { super()[[:timeout, 60],[:timeout, 30]] }
|
|
169
|
+
|
|
170
|
+
it 'has 1 item' do
|
|
171
|
+
is_expected.to eq({timeout: 30})
|
|
172
|
+
expect(subject.size).to eq(1)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe "[['timeout', 60],[:timeout, 30]]" do
|
|
177
|
+
subject { super()[['timeout', 60],[:timeout, 30]] }
|
|
178
|
+
|
|
179
|
+
it 'has 1 item' do
|
|
180
|
+
is_expected.to eq({timeout: 30})
|
|
181
|
+
expect(subject.size).to eq(1)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cardiac::UriMethods do
|
|
4
|
+
|
|
5
|
+
let(:base_url) { 'http://localhost/prefix/segment?q=foobar' }
|
|
6
|
+
let(:base_uri) { URI(base_url) }
|
|
7
|
+
let(:resource) { Cardiac::Resource.new(base_uri) }
|
|
8
|
+
|
|
9
|
+
subject { resource }
|
|
10
|
+
|
|
11
|
+
describe '#build_query' do
|
|
12
|
+
subject { super().send(:build_query) }
|
|
13
|
+
it { is_expected.to eq('q=foobar')}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '#to_url' do
|
|
17
|
+
subject { super().to_url }
|
|
18
|
+
it { is_expected.to eq(base_url) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#to_uri' do
|
|
22
|
+
subject { super().to_uri }
|
|
23
|
+
it { is_expected.to eq(base_uri) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe 'scheme built with' do
|
|
27
|
+
# Follow each example with sanity checks.
|
|
28
|
+
after(:each) do
|
|
29
|
+
scheme = resource.send(:build_scheme)
|
|
30
|
+
expect(resource.scheme(nil).to_uri.scheme).to eq(base_uri.scheme)
|
|
31
|
+
expect(resource.scheme(scheme).to_uri.scheme).to eq(scheme)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '#scheme()' do
|
|
35
|
+
subject { lambda{|v| resource.scheme(v).to_uri.scheme } }
|
|
36
|
+
|
|
37
|
+
describe '[:http]' do
|
|
38
|
+
subject { super()[:http] }
|
|
39
|
+
it { is_expected.to eq('http') }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "['http']" do
|
|
43
|
+
subject { super()['http'] }
|
|
44
|
+
it { is_expected.to eq('http') }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '[:https]' do
|
|
48
|
+
subject { super()[:https] }
|
|
49
|
+
it { is_expected.to eq('https') }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "['https']" do
|
|
53
|
+
subject { super()['https'] }
|
|
54
|
+
it { is_expected.to eq('https') }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '[nil]' do
|
|
58
|
+
subject { super()[nil] }
|
|
59
|
+
it { is_expected.to eq(base_uri.scheme) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'disallows invalid schemes' do
|
|
63
|
+
expect { subject['ftp'] }.to raise_error(ArgumentError)
|
|
64
|
+
expect { subject[0] }.to raise_error(ArgumentError)
|
|
65
|
+
expect { subject[:mailto] }.to raise_error(ArgumentError)
|
|
66
|
+
expect { subject[false] }.to raise_error(ArgumentError)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
describe '#ssl()' do
|
|
70
|
+
subject { lambda{|v| resource.ssl(v).to_uri.scheme } }
|
|
71
|
+
|
|
72
|
+
describe '[false]' do
|
|
73
|
+
subject { super()[false] }
|
|
74
|
+
it { is_expected.to eq('http') }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '[true]' do
|
|
78
|
+
subject { super()[true] }
|
|
79
|
+
it { is_expected.to eq('https') }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '[nil]' do
|
|
83
|
+
subject { super()[nil] }
|
|
84
|
+
it { is_expected.to eq(base_uri.scheme) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
describe '#http()' do
|
|
88
|
+
subject { lambda{|| resource.http.to_uri.scheme } }
|
|
89
|
+
|
|
90
|
+
describe '[]' do
|
|
91
|
+
subject { super()[] }
|
|
92
|
+
it { is_expected.to eq('http') }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
describe '#https()' do
|
|
96
|
+
subject { lambda{|| resource.https.to_uri.scheme } }
|
|
97
|
+
|
|
98
|
+
describe '[]' do
|
|
99
|
+
subject { super()[] }
|
|
100
|
+
it { is_expected.to eq('https') }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe 'query built with' do
|
|
106
|
+
subject do
|
|
107
|
+
lambda{|*v| builder[resource, *v] ; resource.send(:build_query) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Follow each example with sanity checks.
|
|
111
|
+
after(:each) do
|
|
112
|
+
query = resource.send(:build_query)
|
|
113
|
+
expect(resource.reset_query.send(:build_query)).not_to be_present
|
|
114
|
+
expect(resource.query(query).send(:build_query)).to eq(query)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Shared examples that must work for any query builder.
|
|
118
|
+
shared_examples 'query building' do |builder|
|
|
119
|
+
let(:builder) { builder }
|
|
120
|
+
|
|
121
|
+
describe "['q=1']" do
|
|
122
|
+
subject { super()['q=1'] }
|
|
123
|
+
it { is_expected.to eq('q=1') }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "['q=4&r=3','s=2&t=1']" do
|
|
127
|
+
subject { super()['q=4&r=3','s=2&t=1'] }
|
|
128
|
+
it { is_expected.to eq('q=4&r=3&s=2&t=1') }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "[{q: 3, s: 1}, 'r=2&t=1']" do
|
|
132
|
+
subject { super()[{q: 3, s: 1}, 'r=2&t=1'] }
|
|
133
|
+
it { is_expected.to eq('q=3&s=1&r=2&t=1') }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe "['q=1&t=4',{r: 2, s: 3}]" do
|
|
137
|
+
subject { super()['q=1&t=4',{r: 2, s: 3}] }
|
|
138
|
+
it { is_expected.to eq('q=1&t=4&r=2&s=3') }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'disallows invalid queries' do
|
|
142
|
+
expect { subject[%w(a b c)] }.to raise_error(ArgumentError)
|
|
143
|
+
expect { subject[0] }.to raise_error(ArgumentError)
|
|
144
|
+
expect { subject[:mailto] }.to raise_error(ArgumentError)
|
|
145
|
+
expect { subject[false] }.to raise_error(ArgumentError)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'does not modify queries built with a single argument' do
|
|
149
|
+
resource.reset_query
|
|
150
|
+
expect(subject['q=1&q=2']).to eq('q=1&q=2')
|
|
151
|
+
resource.reset_query
|
|
152
|
+
expect(subject['q=1&r=1&q=2']).to eq('q=1&r=1&q=2')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'does modify queries built with two or more arguments' do
|
|
156
|
+
resource.reset_query
|
|
157
|
+
expect(subject['q=1&q=2', 'r=1']).to eq('q=2&r=1')
|
|
158
|
+
resource.reset_query
|
|
159
|
+
expect(subject['q=1&r=1&q=2', 'r=2']).to eq('q=2&r=2')
|
|
160
|
+
resource.reset_query
|
|
161
|
+
expect(subject['q=1&r=1&q=2', 'r=2&q=3']).to eq('q=3&r=2')
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe '#query()' do
|
|
166
|
+
include_examples 'query building', Proc.new{|r,*v| r.query(*v) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe '#options()' do
|
|
170
|
+
include_examples 'query building', Proc.new{|r,*l| l.each{|o| r.options(params: o) } }
|
|
171
|
+
|
|
172
|
+
after(:each) { expect(resource.options_values).to be_empty }
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'sets the host' do
|
|
177
|
+
expect(subject.host('example.com').to_uri.host).to eq('example.com')
|
|
178
|
+
expect(subject.host('127.0.0.1').to_uri.host).to eq('127.0.0.1')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'sets the port for HTTP' do
|
|
182
|
+
expect(subject.http.to_uri.port).to eq(80)
|
|
183
|
+
expect(subject.http.port(8080).to_uri.port).to eq(8080)
|
|
184
|
+
expect(subject.http.port(nil).to_uri.port).to eq(80)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'sets the port for HTTPS' do
|
|
188
|
+
expect(subject.https.to_uri.port).to eq(443)
|
|
189
|
+
expect(subject.https.port(8080).to_uri.port).to eq(8080)
|
|
190
|
+
expect(subject.https.port(nil).to_uri.port).to eq(443)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'merges query parameters' do
|
|
194
|
+
expect(subject.query(q: 'barfoo').to_uri.query).to eq('q=barfoo')
|
|
195
|
+
expect(subject.query(q: '1', t: '2').to_uri.query).to eq('q=1&t=2')
|
|
196
|
+
expect(subject.reset_query(t: '3').query(q: 1).query(t: 2).to_uri.query).to eq('t=2&q=1')
|
|
197
|
+
expect(subject.reset_query(q: 'foobar').options(params: {r: 2, s: 'barfoo'}).to_uri.query).to eq('q=foobar&r=2&s=barfoo')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'deep_merges query parameters' do
|
|
201
|
+
expect(subject.query(q: {r: 1}).to_uri.query).to eq('q[r]=1')
|
|
202
|
+
expect(subject.query(q: {s: 2}).to_uri.query).to eq('q[r]=1&q[s]=2')
|
|
203
|
+
expect(subject.query('q'=>{t: 3}).to_uri.query).to eq('q[r]=1&q[s]=2&q[t]=3')
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'resolves relative paths' do
|
|
207
|
+
expect(subject.path('suffix').to_url).to eq('http://localhost/prefix/suffix?q=foobar')
|
|
208
|
+
expect(subject.path('/suffix').to_url).to eq('http://localhost/suffix?q=foobar')
|
|
209
|
+
expect(subject.path('../suffix').to_url).to eq('http://localhost/suffix?q=foobar')
|
|
210
|
+
expect(subject.path('suffix/../addendum').to_url).to eq('http://localhost/addendum?q=foobar')
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
shared_context 'Client responses' do
|
|
2
|
+
|
|
3
|
+
let!(:mock_response_klass) { Struct.new(:body, :code, :headers) }
|
|
4
|
+
|
|
5
|
+
let!(:mock_success) { mock_response_klass.new("{\"segment\" : {\"id\": 1, \"name\": \"John Doe\"}}",
|
|
6
|
+
200, 'Content-type' => 'application/json') }
|
|
7
|
+
|
|
8
|
+
let!(:mock_failure) { mock_response_klass.new("<html><head><title>Failed</title></head><body><h1>Failed</h1></body></html>",
|
|
9
|
+
404, 'Content-type' => 'text/html') }
|
|
10
|
+
|
|
11
|
+
let!(:response_builder) { Proc.new{|mock,*args|
|
|
12
|
+
mock = send(:"mock_#{mock}") if Symbol===mock
|
|
13
|
+
Rack::Client::Simple::CollapsedResponse.new(mock.code, mock.headers, StringIO.new(mock.body))
|
|
14
|
+
} }
|
|
15
|
+
|
|
16
|
+
let!(:response_handler) { Proc.new{|mock,args|
|
|
17
|
+
response_builder[mock, args]
|
|
18
|
+
} }
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
shared_context 'Client execution' do |verb,mock_response|
|
|
23
|
+
before :example do
|
|
24
|
+
allow_any_instance_of(Cardiac::OperationHandler).to receive(:perform_request) do
|
|
25
|
+
response_handler[mock_response]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Configure Rails Environment
|
|
2
|
+
ENV["RAILS_ENV"] ||= "test"
|
|
3
|
+
|
|
4
|
+
require 'active_support/all'
|
|
5
|
+
require 'active_model'
|
|
6
|
+
require 'cardiac'
|
|
7
|
+
|
|
8
|
+
require 'awesome_print'
|
|
9
|
+
|
|
10
|
+
require 'rspec/core'
|
|
11
|
+
|
|
12
|
+
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
|
|
13
|
+
|
|
14
|
+
RSpec.configure do |config|
|
|
15
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
|
16
|
+
config.run_all_when_everything_filtered = true
|
|
17
|
+
config.filter_run :focus
|
|
18
|
+
|
|
19
|
+
# Run specs in random order to surface order dependencies. If you find an
|
|
20
|
+
# order dependency and want to debug it, you can fix the order by providing
|
|
21
|
+
# the seed, which is printed after each run.
|
|
22
|
+
# --seed 1234
|
|
23
|
+
config.order = 'random'
|
|
24
|
+
end
|