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