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.
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