substation 0.0.10.beta2 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. data/.travis.yml +7 -3
  2. data/Changelog.md +99 -24
  3. data/Gemfile +8 -1
  4. data/Gemfile.devtools +37 -21
  5. data/Guardfile +1 -1
  6. data/README.md +118 -46
  7. data/TODO.md +1 -0
  8. data/config/flay.yml +2 -2
  9. data/config/flog.yml +1 -1
  10. data/config/reek.yml +41 -16
  11. data/config/rubocop.yml +44 -0
  12. data/config/yardstick.yml +1 -1
  13. data/lib/substation.rb +41 -5
  14. data/lib/substation/chain.rb +73 -84
  15. data/lib/substation/chain/definition.rb +147 -0
  16. data/lib/substation/chain/dsl.rb +150 -112
  17. data/lib/substation/chain/dsl/config.rb +55 -0
  18. data/lib/substation/chain/dsl/module_builder.rb +84 -0
  19. data/lib/substation/dispatcher.rb +20 -229
  20. data/lib/substation/dsl/guard.rb +96 -0
  21. data/lib/substation/dsl/registry.rb +181 -0
  22. data/lib/substation/environment.rb +126 -23
  23. data/lib/substation/environment/dsl.rb +31 -12
  24. data/lib/substation/processor.rb +238 -7
  25. data/lib/substation/processor/builder.rb +26 -0
  26. data/lib/substation/processor/config.rb +24 -0
  27. data/lib/substation/processor/evaluator.rb +66 -42
  28. data/lib/substation/processor/evaluator/handler.rb +54 -0
  29. data/lib/substation/processor/evaluator/result.rb +24 -0
  30. data/lib/substation/processor/executor.rb +46 -0
  31. data/lib/substation/processor/nest.rb +40 -0
  32. data/lib/substation/processor/transformer.rb +46 -0
  33. data/lib/substation/processor/wrapper.rb +16 -5
  34. data/lib/substation/request.rb +29 -27
  35. data/lib/substation/response.rb +19 -34
  36. data/lib/substation/response/api.rb +37 -0
  37. data/lib/substation/response/exception.rb +20 -0
  38. data/lib/substation/response/exception/output.rb +59 -0
  39. data/lib/substation/response/failure.rb +11 -0
  40. data/lib/substation/response/success.rb +11 -0
  41. data/lib/substation/version.rb +3 -1
  42. data/spec/demo/core.rb +64 -0
  43. data/spec/demo/core/action.rb +49 -0
  44. data/spec/demo/core/action/create_person.rb +28 -0
  45. data/spec/demo/core/errors.rb +16 -0
  46. data/spec/demo/core/facade.rb +38 -0
  47. data/spec/demo/core/handler.rb +21 -0
  48. data/spec/demo/core/handler/acceptor.rb +47 -0
  49. data/spec/demo/core/handler/authenticator.rb +36 -0
  50. data/spec/demo/core/handler/authorizer.rb +38 -0
  51. data/spec/demo/core/input.rb +15 -0
  52. data/spec/demo/core/observers.rb +10 -0
  53. data/spec/demo/core/validator.rb +21 -0
  54. data/spec/demo/domain/actor.rb +29 -0
  55. data/spec/demo/domain/dto/person.rb +18 -0
  56. data/spec/demo/domain/environment.rb +55 -0
  57. data/spec/demo/domain/storage.rb +49 -0
  58. data/spec/demo/web.rb +26 -0
  59. data/spec/demo/web/errors.rb +9 -0
  60. data/spec/demo/web/facade.rb +60 -0
  61. data/spec/demo/web/handler/deserializer.rb +36 -0
  62. data/spec/demo/web/presenter.rb +38 -0
  63. data/spec/demo/web/presenter/person.rb +19 -0
  64. data/spec/demo/web/renderer.rb +45 -0
  65. data/spec/demo/web/sanitizer.rb +35 -0
  66. data/spec/demo/web/sanitizer/person.rb +20 -0
  67. data/spec/demo/web/views.rb +28 -0
  68. data/spec/integration/demo/core_spec.rb +97 -0
  69. data/spec/integration/demo/web_spec.rb +114 -0
  70. data/spec/shared/context/integration/demo.rb +33 -0
  71. data/spec/shared/context/unit/chain.rb +13 -0
  72. data/spec/shared/context/unit/processor.rb +58 -0
  73. data/spec/shared/context/unit/request.rb +8 -0
  74. data/spec/shared/examples/integration/demo.rb +35 -0
  75. data/spec/shared/examples/unit/processor.rb +72 -0
  76. data/spec/spec_helper.rb +52 -23
  77. data/spec/unit/substation/chain/definition_spec.rb +141 -0
  78. data/spec/unit/substation/chain/dsl/config/dsl_module_spec.rb +13 -0
  79. data/spec/unit/substation/chain/dsl/config/registry_spec.rb +13 -0
  80. data/spec/unit/substation/chain/dsl/config_spec.rb +18 -0
  81. data/spec/unit/substation/chain/dsl/module_builder_spec.rb +77 -0
  82. data/spec/unit/substation/chain/dsl_spec.rb +175 -0
  83. data/spec/unit/substation/chain_spec.rb +303 -0
  84. data/spec/unit/substation/dispatcher_spec.rb +68 -0
  85. data/spec/unit/substation/dsl/guard_spec.rb +72 -0
  86. data/spec/unit/substation/dsl/registry_spec.rb +181 -0
  87. data/spec/unit/substation/environment/dsl_spec.rb +156 -0
  88. data/spec/unit/substation/environment_spec.rb +259 -0
  89. data/spec/unit/substation/processor/builder_spec.rb +21 -0
  90. data/spec/unit/substation/processor/config_spec.rb +40 -0
  91. data/spec/unit/substation/processor/evaluator/handler_spec.rb +20 -0
  92. data/spec/unit/substation/processor/evaluator/pivot_spec.rb +42 -0
  93. data/spec/unit/substation/processor/evaluator/request_spec.rb +11 -0
  94. data/spec/unit/substation/processor/evaluator/result/failure_spec.rb +14 -0
  95. data/spec/unit/substation/processor/evaluator/result/success_spec.rb +14 -0
  96. data/spec/unit/substation/processor/evaluator/result_spec.rb +13 -0
  97. data/spec/unit/substation/processor/evaluator_spec.rb +18 -0
  98. data/spec/unit/substation/processor/executor/null_spec.rb +25 -0
  99. data/spec/unit/substation/processor/executor_spec.rb +32 -0
  100. data/spec/unit/substation/processor/fallible_spec.rb +24 -0
  101. data/spec/unit/substation/processor/incoming_spec.rb +17 -0
  102. data/spec/unit/substation/processor/nest/incoming_spec.rb +56 -0
  103. data/spec/unit/substation/processor/nest_spec.rb +6 -0
  104. data/spec/unit/substation/processor/outgoing_spec.rb +47 -0
  105. data/spec/unit/substation/processor/transformer/incoming_spec.rb +17 -0
  106. data/spec/unit/substation/processor/transformer/outgoing_spec.rb +17 -0
  107. data/spec/unit/substation/processor/wrapper/incoming_spec.rb +15 -0
  108. data/spec/unit/substation/processor/wrapper/outgoing_spec.rb +15 -0
  109. data/spec/unit/substation/processor/wrapper_spec.rb +24 -0
  110. data/spec/unit/substation/processor_spec.rb +68 -0
  111. data/spec/unit/substation/request_spec.rb +70 -0
  112. data/spec/unit/substation/response/api_spec.rb +22 -0
  113. data/spec/unit/substation/response/exception/output_spec.rb +46 -0
  114. data/spec/unit/substation/response/exception_spec.rb +25 -0
  115. data/spec/unit/substation/response/failure_spec.rb +25 -0
  116. data/spec/unit/substation/response/success_spec.rb +24 -0
  117. data/spec/unit/substation/response_spec.rb +73 -0
  118. data/substation.gemspec +7 -6
  119. metadata +157 -67
  120. checksums.yaml +0 -7
  121. data/TODO +0 -0
  122. data/lib/substation/observer.rb +0 -66
  123. data/lib/substation/processor/pivot.rb +0 -25
  124. data/lib/substation/utils.rb +0 -68
  125. data/spec/integration/substation/dispatcher/call_spec.rb +0 -260
  126. data/spec/unit/substation/chain/call_spec.rb +0 -63
  127. data/spec/unit/substation/chain/dsl/builder/class_methods/call_spec.rb +0 -19
  128. data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +0 -21
  129. data/spec/unit/substation/chain/dsl/builder/failure_chain_spec.rb +0 -30
  130. data/spec/unit/substation/chain/dsl/chain_spec.rb +0 -15
  131. data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -24
  132. data/spec/unit/substation/chain/dsl/initialize_spec.rb +0 -19
  133. data/spec/unit/substation/chain/dsl/processors_spec.rb +0 -42
  134. data/spec/unit/substation/chain/dsl/use_spec.rb +0 -14
  135. data/spec/unit/substation/chain/each_spec.rb +0 -46
  136. data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
  137. data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
  138. data/spec/unit/substation/chain/outgoing/result_spec.rb +0 -21
  139. data/spec/unit/substation/dispatcher/action/call_spec.rb +0 -23
  140. data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +0 -61
  141. data/spec/unit/substation/dispatcher/action_names_spec.rb +0 -14
  142. data/spec/unit/substation/dispatcher/call_spec.rb +0 -47
  143. data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +0 -20
  144. data/spec/unit/substation/environment/chain_spec.rb +0 -50
  145. data/spec/unit/substation/environment/class_methods/build_spec.rb +0 -11
  146. data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +0 -18
  147. data/spec/unit/substation/environment/dsl/register_spec.rb +0 -14
  148. data/spec/unit/substation/environment/dsl/registry_spec.rb +0 -19
  149. data/spec/unit/substation/observer/chain/call_spec.rb +0 -26
  150. data/spec/unit/substation/observer/class_methods/coerce_spec.rb +0 -33
  151. data/spec/unit/substation/observer/null/call_spec.rb +0 -12
  152. data/spec/unit/substation/processor/evaluator/call_spec.rb +0 -49
  153. data/spec/unit/substation/processor/pivot/call_spec.rb +0 -17
  154. data/spec/unit/substation/processor/wrapper/call_spec.rb +0 -20
  155. data/spec/unit/substation/request/env_spec.rb +0 -14
  156. data/spec/unit/substation/request/error_spec.rb +0 -15
  157. data/spec/unit/substation/request/input_spec.rb +0 -14
  158. data/spec/unit/substation/request/success_spec.rb +0 -15
  159. data/spec/unit/substation/response/env_spec.rb +0 -16
  160. data/spec/unit/substation/response/failure/success_predicate_spec.rb +0 -15
  161. data/spec/unit/substation/response/input_spec.rb +0 -16
  162. data/spec/unit/substation/response/output_spec.rb +0 -16
  163. data/spec/unit/substation/response/request_spec.rb +0 -16
  164. data/spec/unit/substation/response/success/success_predicate_spec.rb +0 -15
  165. data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +0 -46
  166. data/spec/unit/substation/utils/class_methods/const_get_spec.rb +0 -46
  167. data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +0 -20
@@ -0,0 +1,303 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Chain do
6
+ describe '#call' do
7
+
8
+ subject { object.call(request) }
9
+
10
+ include_context 'Request#initialize'
11
+
12
+ let(:object) { described_class.new(processors, exception_chain) }
13
+ let(:exception_chain) { double(:call => response) }
14
+ let(:processors) { [ processor_1, processor_2, processor_3 ] }
15
+
16
+ let(:processor_1_name) { double }
17
+ let(:processor_2_name) { double }
18
+ let(:processor_3_name) { double }
19
+
20
+ let(:processor_config) { Processor::Config.new(handler, failure_chain, executor) }
21
+ let(:handler) { double('handler') }
22
+ let(:failure_chain) { double(:call => failure_response) }
23
+ let(:failure_response) { double }
24
+ let(:executor) { Processor::Executor::NULL }
25
+
26
+ context 'when all processors are successful' do
27
+ let(:processor_1) {
28
+ Class.new {
29
+ include Substation::Processor::Incoming
30
+ def call(request)
31
+ request.success(:success_1)
32
+ end
33
+ }.new(processor_1_name, handler, processor_config)
34
+ }
35
+
36
+ let(:processor_2) {
37
+ Class.new {
38
+ include Substation::Processor::Pivot
39
+ def call(request)
40
+ request.success(:success_2)
41
+ end
42
+ }.new(processor_2_name, handler, processor_config)
43
+ }
44
+
45
+ let(:processor_3) {
46
+ Class.new {
47
+ include Substation::Processor::Outgoing
48
+ def call(response)
49
+ respond_with(response, :success_3)
50
+ end
51
+ }.new(processor_3_name, handler, processor_config)
52
+ }
53
+
54
+ let(:response) { Response::Success.new(current_request, :success_3) }
55
+ let(:current_request) { Request.new(name, env, :success_1) }
56
+
57
+ it { should eql(response) }
58
+ end
59
+
60
+ context 'when an incoming processor is not successful' do
61
+
62
+ let(:processor_2) {
63
+ Class.new {
64
+ include Substation::Processor::Pivot
65
+ def call(request)
66
+ request.success(:success_1)
67
+ end
68
+ }.new(processor_2_name, handler, processor_config)
69
+ }
70
+
71
+ let(:processor_3) {
72
+ Class.new {
73
+ include Substation::Processor::Outgoing
74
+ def call(response)
75
+ respond_with(response, :success_3)
76
+ end
77
+ }.new(processor_3_name, handler, processor_config)
78
+ }
79
+
80
+ let(:response_class) { Response::Failure }
81
+
82
+ context 'because it returned a failure response' do
83
+ let(:processor_1) {
84
+ Class.new {
85
+ include Substation::Processor::Incoming
86
+ def call(request)
87
+ request.error(:error_1)
88
+ end
89
+ }.new(processor_1_name, handler, processor_config)
90
+ }
91
+
92
+ let(:response) { Response::Failure.new(request, :error_1) }
93
+
94
+ it { should eql(response) }
95
+ end
96
+
97
+ context 'because it raised an uncaught exception' do
98
+ let(:processor_1) {
99
+ Class.new {
100
+ include Substation::Processor::Incoming
101
+ def call(request)
102
+ raise RuntimeError, 'exception_1'
103
+ end
104
+ }.new(processor_1_name, handler, processor_config)
105
+ }
106
+
107
+ let(:response) { Response::Exception.new(request, data) }
108
+ let(:data) { Response::Exception::Output.new(input, RuntimeError.new('exception_1')) }
109
+
110
+ it { should eql(response) }
111
+
112
+ it 'wraps the original exception instance' do
113
+ expect(subject.output.exception.message).to eql('exception_1')
114
+ end
115
+
116
+ it 'calls the failure chain' do
117
+ expect(exception_chain).to receive(:call).with(response)
118
+ subject
119
+ end
120
+ end
121
+ end
122
+
123
+ context 'when the pivot processor is not successful' do
124
+ let(:processor_1) {
125
+ Class.new {
126
+ include Substation::Processor::Incoming
127
+ def call(request)
128
+ request.success(:success_1)
129
+ end
130
+ }.new(processor_1_name, handler, processor_config)
131
+ }
132
+
133
+ let(:processor_3) {
134
+ Class.new {
135
+ include Substation::Processor::Outgoing
136
+ def call(response)
137
+ response
138
+ end
139
+ }.new(processor_3_name, handler, processor_config)
140
+ }
141
+
142
+ let(:response_class) { Response::Failure }
143
+
144
+ context 'because it returned a failure response' do
145
+ let(:processor_2) {
146
+ Class.new {
147
+ include Substation::Processor::Pivot
148
+ def call(request)
149
+ request.error(:error_2)
150
+ end
151
+ }.new(processor_2_name, handler, processor_config)
152
+ }
153
+
154
+ let(:response) { Response::Failure.new(current_request, :error_2) }
155
+ let(:current_request) { Request.new(name, env, :success_1) }
156
+
157
+ it { should eql(response) }
158
+ end
159
+
160
+ context 'because it raised an uncaught exception' do
161
+ let(:processor_2) {
162
+ Class.new {
163
+ include Substation::Processor::Pivot
164
+ def call(request)
165
+ raise RuntimeError, 'exception_2'
166
+ end
167
+ }.new(processor_2_name, handler, processor_config)
168
+ }
169
+
170
+ let(:response) { Response::Exception.new(request.to_request(:success_1), data) }
171
+ let(:data) { Response::Exception::Output.new(:success_1, RuntimeError.new('exception_2')) }
172
+ let(:current_request) { Request.new(name, env, :success_1) }
173
+
174
+ it { should eql(response) }
175
+
176
+ it 'wraps the original exception instance' do
177
+ expect(subject.output.exception.message).to eql('exception_2')
178
+ end
179
+
180
+ it 'calls the failure chain' do
181
+ expect(exception_chain).to receive(:call).with(response)
182
+ subject
183
+ end
184
+ end
185
+ end
186
+
187
+ context 'when an outgoing processor is not successful' do
188
+ let(:processor_1) {
189
+ Class.new {
190
+ include Substation::Processor::Incoming
191
+ def call(request)
192
+ request.success(:success_1)
193
+ end
194
+ }.new(processor_1_name, handler, processor_config)
195
+ }
196
+
197
+ let(:processor_2) {
198
+ Class.new {
199
+ include Substation::Processor::Pivot
200
+ def call(response)
201
+ response.success(:success_2)
202
+ end
203
+ }.new(processor_2_name, handler, processor_config)
204
+ }
205
+
206
+ let(:response_class) { Response::Failure }
207
+
208
+ context 'because it raised an uncaught exception' do
209
+ let(:processor_3) {
210
+ Class.new {
211
+ include Substation::Processor::Outgoing
212
+ def call(response)
213
+ raise RuntimeError, 'exception_3'
214
+ end
215
+ }.new(processor_3_name, handler, processor_config)
216
+ }
217
+
218
+ let(:response) { Response::Exception.new(request.to_request(:success_2), data) }
219
+ let(:data) { Response::Exception::Output.new(:success_2, RuntimeError.new('exception_3')) }
220
+ let(:current_request) { Request.new(name, env, :success_1) }
221
+ let(:current_response) { Response::Success.new(current_request, :success_2) }
222
+
223
+ it { should eql(response) }
224
+
225
+ it 'wraps the original exception instance' do
226
+ expect(subject.output.exception.message).to eql('exception_3')
227
+ end
228
+
229
+ it 'calls the failure chain' do
230
+ expect(exception_chain).to receive(:call).with(response)
231
+ subject
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ describe '#each' do
238
+ subject { object.each { |tuple| yields << processor } }
239
+
240
+ let(:object) { described_class.new(processors, described_class::EMPTY) }
241
+ let(:processors) { [ processor ] }
242
+ let(:processor) { Spec::FAKE_PROCESSOR }
243
+ let(:yields) { [] }
244
+
245
+ before do
246
+ expect(object).to be_instance_of(described_class)
247
+ end
248
+
249
+ it_should_behave_like 'an #each method'
250
+
251
+ it 'yields only processors' do
252
+ subject
253
+ yields.each { |processor| expect(processor).to be_instance_of(Spec::Processor) }
254
+ end
255
+
256
+ it 'yields only processors with the expected handler' do
257
+ expect { subject }.to change { yields.dup }
258
+ .from([])
259
+ .to([ processor ])
260
+ end
261
+ end
262
+
263
+ describe Chain do
264
+ subject { described_class.new(processors, described_class::EMPTY) }
265
+
266
+ let(:processors) { [ processor ] }
267
+ let(:processor) { Spec::FAKE_PROCESSOR }
268
+
269
+ before do
270
+ expect(subject).to be_instance_of(described_class)
271
+ end
272
+
273
+ it { should be_kind_of(Enumerable) }
274
+ end
275
+
276
+ describe '.exception_response' do
277
+ subject { described_class.exception_response(state, data, exception) }
278
+
279
+ include_context 'Request#initialize'
280
+
281
+ let(:data) { double('data') }
282
+ let(:exception) { double('exception') }
283
+ let(:exception_data) { Response::Exception::Output.new(data, exception) }
284
+
285
+ let(:expected) { Response::Exception.new(expected_request, exception_data) }
286
+
287
+ context 'when a request is passed as current state' do
288
+ let(:state) { request }
289
+ let(:expected_request) { request.to_request(data) }
290
+
291
+ it { should eql(expected) }
292
+ end
293
+
294
+ context 'when a response is passed as current state' do
295
+ let(:state) { response }
296
+ let(:expected_request) { response.to_request(data) }
297
+ let(:response) { Response::Failure.new(request, output) }
298
+ let(:output) { double('output') }
299
+
300
+ it { should eql(expected) }
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Dispatcher do
6
+ describe '#call' do
7
+
8
+ subject { object.call(name, input) }
9
+
10
+ include_context 'Request#initialize'
11
+
12
+ let(:object) { described_class.new(config, env) }
13
+ let(:config) { DSL::Registry.new(guard, :test => Spec::Action::Success) }
14
+ let(:guard) { double('guard') }
15
+
16
+ let(:expected_response) do
17
+ Spec::Action::Success.call(request)
18
+ end
19
+
20
+ context 'when the action is registered' do
21
+ let(:name) { :test }
22
+
23
+ it { should eql(expected_response) }
24
+ end
25
+
26
+ context 'when the action is not registered' do
27
+ let(:name) { :unknown }
28
+
29
+ specify do
30
+ expect { subject }.to raise_error(described_class::UnknownActionError)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '.new_registry' do
36
+ subject { described_class.new_registry }
37
+
38
+ let(:expected) { DSL::Registry.new(described_class::GUARD) }
39
+
40
+ it { should eql(expected) }
41
+ end
42
+
43
+ describe '#include?' do
44
+
45
+ subject { object.include?(name) }
46
+
47
+ let(:object) { described_class.new(actions, env) }
48
+ let(:actions) { Hash[action => double('action')] }
49
+ let(:action) { double('action') }
50
+ let(:env) { double('env') }
51
+
52
+ context 'when the action is registered' do
53
+ let(:name) { action }
54
+
55
+ it 'returns true' do
56
+ expect(subject).to be(true)
57
+ end
58
+ end
59
+
60
+ context 'when the action is not registered' do
61
+ let(:name) { :unknown }
62
+
63
+ it 'returns false' do
64
+ expect(subject).to be(false)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe DSL::Guard do
6
+ describe '#call' do
7
+ subject { object.call(name, registry) }
8
+
9
+ let(:name) { double('name') }
10
+ let(:registry) { double('registry') }
11
+
12
+ context 'when no reserved names are given' do
13
+ let(:object) { described_class.new }
14
+
15
+ context 'when the given name is valid' do
16
+ let(:name) { :test }
17
+
18
+ before do
19
+ expect(registry).to receive(:include?).with(name).and_return(false)
20
+ end
21
+
22
+ it 'raises no error' do
23
+ expect { subject }.to_not raise_error
24
+ end
25
+ end
26
+
27
+ context 'when the given name is already registered' do
28
+ let(:name) { :test }
29
+ let(:msg) { described_class::ALREADY_REGISTERED_MSG % name.inspect }
30
+
31
+ before do
32
+ expect(registry).to receive(:include?).with(name).and_return(true)
33
+ end
34
+
35
+ it 'raises ReservedNameError' do
36
+ expect { subject }.to raise_error(AlreadyRegisteredError, msg)
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'when reserved names are given' do
42
+ let(:object) { described_class.new(reserved_names) }
43
+ let(:reserved_names) { double('reserved_names') }
44
+
45
+ before do
46
+ expect(registry).to receive(:include?).with(name).and_return(false)
47
+ end
48
+
49
+ context 'when the given name is not reserved' do
50
+ before do
51
+ expect(reserved_names).to receive(:include?).with(name).and_return(false)
52
+ end
53
+
54
+ it 'raises no error' do
55
+ expect { subject }.to_not raise_error
56
+ end
57
+ end
58
+
59
+ context 'when the given name is reserved' do
60
+ let(:msg) { described_class::RESERVED_NAME_MSG % name.inspect }
61
+
62
+ before do
63
+ expect(reserved_names).to receive(:include?).with(name).and_return(true)
64
+ end
65
+
66
+ it 'raises ReservedNameError' do
67
+ expect { subject }.to raise_error(ReservedNameError, msg)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,181 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe DSL::Registry do
6
+ describe '#each' do
7
+ subject { object.each(&block) }
8
+
9
+ let(:object) { described_class.new(guard, entries) }
10
+ let(:guard) { double('guard') }
11
+ let(:entries) { { name => entry } }
12
+ let(:name) { double('name') }
13
+ let(:entry) { double('entry') }
14
+ let(:block) { ->(_) { } }
15
+
16
+ it_should_behave_like 'an #each method'
17
+
18
+ it { should be_kind_of(Enumerable) }
19
+
20
+ it 'yields all entries' do
21
+ expect { |block| object.each(&block) }.to yield_successive_args([name, entry])
22
+ end
23
+ end
24
+
25
+ describe '#[]=' do
26
+ subject { object[name] = expected }
27
+
28
+ let(:object) { described_class.new(guard) }
29
+ let(:guard) { DSL::Guard.new(reserved_names) }
30
+ let(:reserved_names) { EMPTY_ARRAY }
31
+ let(:expected) { double('expected') }
32
+ let(:name) { double('name', :to_sym => coerced_name) }
33
+ let(:coerced_name) { double('coerced_name') }
34
+
35
+ context 'when name is not yet registered' do
36
+ it 'returns registered object' do
37
+ should be(expected)
38
+ end
39
+
40
+ it 'registers given object' do
41
+ subject
42
+ expect(object.fetch(name)).to be(expected)
43
+ end
44
+ end
45
+
46
+ context 'when name is already registered' do
47
+ let(:msg) { DSL::Guard::ALREADY_REGISTERED_MSG % coerced_name.inspect }
48
+
49
+ before { object[name] = expected }
50
+
51
+ specify { expect { subject }.to raise_error(AlreadyRegisteredError, msg) }
52
+ end
53
+
54
+ context 'when name is reserved' do
55
+ let(:msg) { DSL::Guard::RESERVED_NAME_MSG % coerced_name.inspect }
56
+ let(:reserved_names) { [coerced_name] }
57
+
58
+ specify { expect { subject }.to raise_error(ReservedNameError, msg) }
59
+ end
60
+ end
61
+
62
+ describe '#fetch' do
63
+
64
+ let(:object) { described_class.new(guard, entries) }
65
+ let(:guard) { double('guard') }
66
+ let(:name) { double('name') }
67
+ let(:coerced_name) { double('coerced_name') }
68
+
69
+ before do
70
+ expect(name).to receive(:to_sym).and_return(coerced_name)
71
+ end
72
+
73
+ context 'when name is not yet registered' do
74
+ let(:entries) { {} }
75
+
76
+ context 'and a block is given' do
77
+ subject { object.fetch(name, &block) }
78
+
79
+ let(:block) { ->(_) { block_return } }
80
+ let(:block_return) { double('block_return') }
81
+
82
+ it { should be(block_return) }
83
+ end
84
+
85
+ context 'and no block is given' do
86
+ subject { object.fetch(name) }
87
+
88
+ it 'should behave like Hash#fetch' do
89
+ expect { subject }.to raise_error(KeyError)
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'when name is already registered' do
95
+ subject { object.fetch(name) }
96
+
97
+ let(:entries) { { coerced_name => expected } }
98
+ let(:expected) { double('expected') }
99
+
100
+ it { should be(expected) }
101
+ end
102
+ end
103
+
104
+ describe '#include?' do
105
+ subject { object.include?(name) }
106
+
107
+ let(:object) { described_class.new(guard, entries) }
108
+ let(:guard) { double('guard') }
109
+ let(:name) { double('name') }
110
+ let(:coerced_name) { double('coerced_name') }
111
+
112
+ before do
113
+ expect(name).to receive(:to_sym).with(no_args).and_return(coerced_name)
114
+ end
115
+
116
+ context 'when name is included' do
117
+ let(:entries) { { coerced_name => data } }
118
+ let(:data) { double('data') }
119
+
120
+ it { should be(true) }
121
+ end
122
+
123
+ context 'when name is not included' do
124
+ let(:entries) { {} }
125
+
126
+ it { should be(false) }
127
+ end
128
+ end
129
+
130
+ describe '#keys' do
131
+ subject { object.keys }
132
+
133
+ let(:object) { described_class.new(guard, entries) }
134
+ let(:guard) { double('guard') }
135
+ let(:entries) { { key => value } }
136
+ let(:key) { double('key') }
137
+ let(:value) { double('value') }
138
+
139
+ it { should eql([key]) }
140
+ end
141
+
142
+ describe '#merge' do
143
+ subject { object.merge(other) }
144
+
145
+ let(:other) { described_class.new(guard, entries) }
146
+ let(:entries) { { coerced_name => data } }
147
+ let(:data) { double('data') }
148
+
149
+ let(:guard) { double('guard') }
150
+ let(:name) { double('name') }
151
+ let(:coerced_name) { double('coerced_name') }
152
+
153
+ before do
154
+ expect(coerced_name).to receive(:to_sym).with(no_args).and_return(coerced_name)
155
+ end
156
+
157
+ context 'when other contains equally named objects' do
158
+ before do
159
+ expect(guard).to receive(:call).with(coerced_name, entries).and_raise(RuntimeError)
160
+ end
161
+
162
+ let(:object) { described_class.new(guard, entries) }
163
+
164
+ it 'raises an error' do
165
+ expect { subject }.to raise_error(RuntimeError)
166
+ end
167
+ end
168
+
169
+ context 'when other only contains new names as keys' do
170
+ before do
171
+ expect(guard).to receive(:call).with(coerced_name, {})
172
+ end
173
+
174
+ let(:object) { described_class.new(guard) }
175
+ let(:expected) { described_class.new(guard, entries) }
176
+
177
+ it { should eql(expected) }
178
+ it { should_not be(object) }
179
+ end
180
+ end
181
+ end