cardiac 0.2.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/LICENSE +22 -0
  4. data/Rakefile +66 -0
  5. data/cardiac-0.2.0.pre2.gem +0 -0
  6. data/cardiac.gemspec +48 -0
  7. data/lib/cardiac/declarations.rb +70 -0
  8. data/lib/cardiac/errors.rb +65 -0
  9. data/lib/cardiac/log_subscriber.rb +55 -0
  10. data/lib/cardiac/model/attributes.rb +146 -0
  11. data/lib/cardiac/model/base.rb +161 -0
  12. data/lib/cardiac/model/callbacks.rb +47 -0
  13. data/lib/cardiac/model/declarations.rb +106 -0
  14. data/lib/cardiac/model/dirty.rb +117 -0
  15. data/lib/cardiac/model/locale/en.yml +7 -0
  16. data/lib/cardiac/model/operations.rb +49 -0
  17. data/lib/cardiac/model/persistence.rb +171 -0
  18. data/lib/cardiac/model/querying.rb +129 -0
  19. data/lib/cardiac/model/validations.rb +124 -0
  20. data/lib/cardiac/model.rb +17 -0
  21. data/lib/cardiac/operation_builder.rb +75 -0
  22. data/lib/cardiac/operation_handler.rb +215 -0
  23. data/lib/cardiac/railtie.rb +20 -0
  24. data/lib/cardiac/reflections.rb +85 -0
  25. data/lib/cardiac/representation.rb +124 -0
  26. data/lib/cardiac/resource/adapter.rb +178 -0
  27. data/lib/cardiac/resource/builder.rb +107 -0
  28. data/lib/cardiac/resource/codec_methods.rb +58 -0
  29. data/lib/cardiac/resource/config_methods.rb +39 -0
  30. data/lib/cardiac/resource/extension_methods.rb +115 -0
  31. data/lib/cardiac/resource/request_methods.rb +138 -0
  32. data/lib/cardiac/resource/subresource.rb +88 -0
  33. data/lib/cardiac/resource/uri_methods.rb +176 -0
  34. data/lib/cardiac/resource.rb +77 -0
  35. data/lib/cardiac/util.rb +120 -0
  36. data/lib/cardiac/version.rb +3 -0
  37. data/lib/cardiac.rb +61 -0
  38. data/spec/rails-3.2/Gemfile +9 -0
  39. data/spec/rails-3.2/Gemfile.lock +136 -0
  40. data/spec/rails-3.2/Rakefile +10 -0
  41. data/spec/rails-3.2/app_root/app/assets/javascripts/application.js +15 -0
  42. data/spec/rails-3.2/app_root/app/assets/stylesheets/application.css +13 -0
  43. data/spec/rails-3.2/app_root/app/controllers/application_controller.rb +3 -0
  44. data/spec/rails-3.2/app_root/app/helpers/application_helper.rb +2 -0
  45. data/spec/rails-3.2/app_root/app/views/layouts/application.html.erb +14 -0
  46. data/spec/rails-3.2/app_root/config/application.rb +29 -0
  47. data/spec/rails-3.2/app_root/config/boot.rb +13 -0
  48. data/spec/rails-3.2/app_root/config/database.yml +25 -0
  49. data/spec/rails-3.2/app_root/config/environment.rb +5 -0
  50. data/spec/rails-3.2/app_root/config/environments/development.rb +10 -0
  51. data/spec/rails-3.2/app_root/config/environments/production.rb +11 -0
  52. data/spec/rails-3.2/app_root/config/environments/test.rb +11 -0
  53. data/spec/rails-3.2/app_root/config/initializers/backtrace_silencers.rb +7 -0
  54. data/spec/rails-3.2/app_root/config/initializers/inflections.rb +15 -0
  55. data/spec/rails-3.2/app_root/config/initializers/mime_types.rb +5 -0
  56. data/spec/rails-3.2/app_root/config/initializers/secret_token.rb +7 -0
  57. data/spec/rails-3.2/app_root/config/initializers/session_store.rb +8 -0
  58. data/spec/rails-3.2/app_root/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/rails-3.2/app_root/config/locales/en.yml +5 -0
  60. data/spec/rails-3.2/app_root/config/routes.rb +2 -0
  61. data/spec/rails-3.2/app_root/db/test.sqlite3 +0 -0
  62. data/spec/rails-3.2/app_root/log/test.log +2403 -0
  63. data/spec/rails-3.2/app_root/public/404.html +26 -0
  64. data/spec/rails-3.2/app_root/public/422.html +26 -0
  65. data/spec/rails-3.2/app_root/public/500.html +25 -0
  66. data/spec/rails-3.2/app_root/public/favicon.ico +0 -0
  67. data/spec/rails-3.2/app_root/script/rails +6 -0
  68. data/spec/rails-3.2/spec/spec_helper.rb +25 -0
  69. data/spec/rails-4.0/Gemfile +9 -0
  70. data/spec/rails-4.0/Gemfile.lock +132 -0
  71. data/spec/rails-4.0/Rakefile +10 -0
  72. data/spec/rails-4.0/app_root/app/assets/javascripts/application.js +15 -0
  73. data/spec/rails-4.0/app_root/app/assets/stylesheets/application.css +13 -0
  74. data/spec/rails-4.0/app_root/app/controllers/application_controller.rb +3 -0
  75. data/spec/rails-4.0/app_root/app/helpers/application_helper.rb +2 -0
  76. data/spec/rails-4.0/app_root/app/views/layouts/application.html.erb +14 -0
  77. data/spec/rails-4.0/app_root/config/application.rb +28 -0
  78. data/spec/rails-4.0/app_root/config/boot.rb +13 -0
  79. data/spec/rails-4.0/app_root/config/database.yml +25 -0
  80. data/spec/rails-4.0/app_root/config/environment.rb +5 -0
  81. data/spec/rails-4.0/app_root/config/environments/development.rb +9 -0
  82. data/spec/rails-4.0/app_root/config/environments/production.rb +11 -0
  83. data/spec/rails-4.0/app_root/config/environments/test.rb +10 -0
  84. data/spec/rails-4.0/app_root/config/initializers/backtrace_silencers.rb +7 -0
  85. data/spec/rails-4.0/app_root/config/initializers/inflections.rb +15 -0
  86. data/spec/rails-4.0/app_root/config/initializers/mime_types.rb +5 -0
  87. data/spec/rails-4.0/app_root/config/initializers/secret_token.rb +7 -0
  88. data/spec/rails-4.0/app_root/config/initializers/session_store.rb +8 -0
  89. data/spec/rails-4.0/app_root/config/initializers/wrap_parameters.rb +14 -0
  90. data/spec/rails-4.0/app_root/config/locales/en.yml +5 -0
  91. data/spec/rails-4.0/app_root/config/routes.rb +2 -0
  92. data/spec/rails-4.0/app_root/db/test.sqlite3 +0 -0
  93. data/spec/rails-4.0/app_root/log/development.log +50 -0
  94. data/spec/rails-4.0/app_root/log/test.log +2399 -0
  95. data/spec/rails-4.0/app_root/public/404.html +26 -0
  96. data/spec/rails-4.0/app_root/public/422.html +26 -0
  97. data/spec/rails-4.0/app_root/public/500.html +25 -0
  98. data/spec/rails-4.0/app_root/public/favicon.ico +0 -0
  99. data/spec/rails-4.0/app_root/script/rails +6 -0
  100. data/spec/rails-4.0/spec/spec_helper.rb +25 -0
  101. data/spec/shared/cardiac/declarations_spec.rb +103 -0
  102. data/spec/shared/cardiac/model/base_spec.rb +446 -0
  103. data/spec/shared/cardiac/operation_builder_spec.rb +96 -0
  104. data/spec/shared/cardiac/operation_handler_spec.rb +82 -0
  105. data/spec/shared/cardiac/representation/reflection_spec.rb +73 -0
  106. data/spec/shared/cardiac/resource/adapter_spec.rb +83 -0
  107. data/spec/shared/cardiac/resource/builder_spec.rb +52 -0
  108. data/spec/shared/cardiac/resource/codec_methods_spec.rb +63 -0
  109. data/spec/shared/cardiac/resource/config_methods_spec.rb +52 -0
  110. data/spec/shared/cardiac/resource/extension_methods_spec.rb +215 -0
  111. data/spec/shared/cardiac/resource/request_methods_spec.rb +186 -0
  112. data/spec/shared/cardiac/resource/uri_methods_spec.rb +212 -0
  113. data/spec/shared/support/client_execution.rb +28 -0
  114. data/spec/spec_helper.rb +24 -0
  115. 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
@@ -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