substation 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/.travis.yml +4 -0
  2. data/Changelog.md +92 -2
  3. data/Gemfile.devtools +19 -17
  4. data/README.md +116 -46
  5. data/config/flay.yml +2 -2
  6. data/config/mutant.yml +1 -0
  7. data/config/reek.yml +11 -5
  8. data/config/rubocop.yml +35 -0
  9. data/lib/substation.rb +10 -1
  10. data/lib/substation/chain.rb +108 -64
  11. data/lib/substation/chain/dsl.rb +62 -37
  12. data/lib/substation/dispatcher.rb +3 -1
  13. data/lib/substation/environment.rb +9 -6
  14. data/lib/substation/environment/dsl.rb +4 -3
  15. data/lib/substation/observer.rb +2 -0
  16. data/lib/substation/processor.rb +106 -7
  17. data/lib/substation/processor/evaluator.rb +98 -7
  18. data/lib/substation/processor/transformer.rb +26 -0
  19. data/lib/substation/processor/wrapper.rb +5 -3
  20. data/lib/substation/request.rb +12 -1
  21. data/lib/substation/response.rb +13 -0
  22. data/lib/substation/utils.rb +3 -1
  23. data/lib/substation/version.rb +3 -1
  24. data/spec/integration/substation/dispatcher/call_spec.rb +12 -12
  25. data/spec/spec_helper.rb +39 -32
  26. data/spec/unit/substation/chain/call_spec.rb +205 -29
  27. data/spec/unit/substation/chain/class_methods/failure_response_spec.rb +16 -0
  28. data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +7 -4
  29. data/spec/unit/substation/chain/dsl/class_methods/build_spec.rb +24 -0
  30. data/spec/unit/substation/chain/dsl/failure_chain_spec.rb +35 -0
  31. data/spec/unit/substation/chain/dsl/processors_spec.rb +8 -6
  32. data/spec/unit/substation/chain/dsl/use_spec.rb +1 -1
  33. data/spec/unit/substation/chain/each_spec.rb +5 -9
  34. data/spec/unit/substation/chain/failure_data/equalizer_spec.rb +46 -0
  35. data/spec/unit/substation/chain/failure_data/hash_spec.rb +13 -0
  36. data/spec/unit/substation/dispatcher/action/call_spec.rb +2 -1
  37. data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +7 -5
  38. data/spec/unit/substation/dispatcher/call_spec.rb +2 -2
  39. data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +6 -6
  40. data/spec/unit/substation/environment/chain_spec.rb +22 -27
  41. data/spec/unit/substation/environment/class_methods/build_spec.rb +11 -4
  42. data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +5 -3
  43. data/spec/unit/substation/environment/dsl/register_spec.rb +8 -3
  44. data/spec/unit/substation/environment/dsl/registry_spec.rb +5 -3
  45. data/spec/unit/substation/environment/equalizer_spec.rb +25 -0
  46. data/spec/unit/substation/observer/chain/call_spec.rb +2 -0
  47. data/spec/unit/substation/observer/class_methods/coerce_spec.rb +2 -0
  48. data/spec/unit/substation/observer/null/call_spec.rb +2 -0
  49. data/spec/unit/substation/processor/evaluator/call_spec.rb +20 -10
  50. data/spec/unit/substation/processor/evaluator/class_methods/new_spec.rb +9 -0
  51. data/spec/unit/substation/processor/evaluator/data/call_spec.rb +34 -0
  52. data/spec/unit/substation/processor/evaluator/pivot/call_spec.rb +34 -0
  53. data/spec/unit/substation/processor/evaluator/request/call_spec.rb +34 -0
  54. data/spec/unit/substation/processor/fallible/name_spec.rb +15 -0
  55. data/spec/unit/substation/processor/fallible/with_failure_chain_spec.rb +18 -0
  56. data/spec/unit/substation/processor/incoming/result_spec.rb +25 -0
  57. data/spec/unit/substation/processor/outgoing/call_spec.rb +28 -0
  58. data/spec/unit/substation/processor/outgoing/name_spec.rb +14 -0
  59. data/spec/unit/substation/processor/outgoing/success_predicate_spec.rb +15 -0
  60. data/spec/unit/substation/{chain/outgoing → processor}/result_spec.rb +4 -3
  61. data/spec/unit/substation/processor/success_predicate_spec.rb +22 -0
  62. data/spec/unit/substation/processor/transformer/call_spec.rb +21 -0
  63. data/spec/unit/substation/processor/wrapper/call_spec.rb +9 -7
  64. data/spec/unit/substation/request/env_spec.rb +3 -2
  65. data/spec/unit/substation/request/error_spec.rb +2 -1
  66. data/spec/unit/substation/request/input_spec.rb +3 -2
  67. data/spec/unit/substation/request/name_spec.rb +15 -0
  68. data/spec/unit/substation/request/success_spec.rb +2 -1
  69. data/spec/unit/substation/response/env_spec.rb +3 -2
  70. data/spec/unit/substation/response/failure/success_predicate_spec.rb +2 -1
  71. data/spec/unit/substation/response/input_spec.rb +3 -2
  72. data/spec/unit/substation/response/output_spec.rb +2 -1
  73. data/spec/unit/substation/response/request_spec.rb +3 -2
  74. data/spec/unit/substation/response/success/success_predicate_spec.rb +2 -1
  75. data/spec/unit/substation/response/to_request_spec.rb +19 -0
  76. data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +14 -12
  77. data/spec/unit/substation/utils/class_methods/const_get_spec.rb +6 -6
  78. data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +4 -4
  79. metadata +25 -9
  80. data/lib/substation/processor/pivot.rb +0 -25
  81. data/spec/unit/substation/chain/class_methods/build_spec.rb +0 -31
  82. data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -23
  83. data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
  84. data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
  85. data/spec/unit/substation/processor/pivot/call_spec.rb +0 -16
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
 
3
5
  # A collection of utility methods
@@ -12,7 +14,7 @@ module Substation
12
14
  #
13
15
  # @api private
14
16
  def self.const_get(name)
15
- list = name.to_s.split("::")
17
+ list = name.to_s.split('::')
16
18
  list.shift if list.first.empty?
17
19
  obj = Object
18
20
  list.each do |const|
@@ -1,4 +1,6 @@
1
+ # encoding: utf-8
2
+
1
3
  module Substation
2
4
  # Gem version
3
- VERSION = '0.0.8'.freeze
5
+ VERSION = '0.0.9'.freeze
4
6
  end
@@ -181,8 +181,8 @@ module App
181
181
  end # module Actions
182
182
 
183
183
  module Observers
184
- LogEvent = Proc.new { |response| response }
185
- SendEmail = Proc.new { |response| response }
184
+ LOG_EVENT = ->(response) { response }
185
+ SEND_EMAIL = ->(response) { response }
186
186
  end
187
187
 
188
188
  DB = Database.new({
@@ -198,8 +198,8 @@ module App
198
198
  :create_person => {
199
199
  :action => Actions::CreatePerson,
200
200
  :observer => [
201
- Observers::LogEvent,
202
- Observers::SendEmail
201
+ Observers::LOG_EVENT,
202
+ Observers::SEND_EMAIL
203
203
  ]
204
204
  }
205
205
  }
@@ -213,11 +213,11 @@ end
213
213
 
214
214
  describe App::APP, '#call' do
215
215
 
216
- context "when dispatching an action" do
216
+ context 'when dispatching an action' do
217
217
  subject { object.call(action, input) }
218
218
 
219
219
  let(:object) { described_class }
220
- let(:request) { Substation::Request.new(env, input) }
220
+ let(:request) { Substation::Request.new(action, env, input) }
221
221
  let(:env) { App::Environment.new(storage) }
222
222
  let(:storage) { App::Storage.new(App::DB) }
223
223
  let(:response) { Substation::Response::Success.new(request, output) }
@@ -225,7 +225,7 @@ describe App::APP, '#call' do
225
225
  let(:john) { App::Models::Person.new(:id => 1, :name => 'John') }
226
226
  let(:jane) { App::Models::Person.new(:id => 2, :name => 'Jane') }
227
227
 
228
- context "with no input data" do
228
+ context 'with no input data' do
229
229
  let(:action) { :list_people }
230
230
  let(:input) { nil }
231
231
  let(:output) { [ john ] }
@@ -233,8 +233,8 @@ describe App::APP, '#call' do
233
233
  it { should eql(response) }
234
234
  end
235
235
 
236
- context "with input data" do
237
- context "and no observer" do
236
+ context 'with input data' do
237
+ context 'and no observer' do
238
238
  let(:action) { :load_person }
239
239
  let(:input) { 1 }
240
240
  let(:output) { john }
@@ -242,14 +242,14 @@ describe App::APP, '#call' do
242
242
  it { should eq(response) }
243
243
  end
244
244
 
245
- context "and observers" do
245
+ context 'and observers' do
246
246
  let(:action) { :create_person }
247
247
  let(:input) { jane }
248
248
  let(:output) { [ john, jane ] }
249
249
 
250
250
  before do
251
- App::Observers::LogEvent.should_receive(:call).with(response).ordered
252
- App::Observers::SendEmail.should_receive(:call).with(response).ordered
251
+ App::Observers::LOG_EVENT.should_receive(:call).with(response).ordered
252
+ App::Observers::SEND_EMAIL.should_receive(:call).with(response).ordered
253
253
  end
254
254
 
255
255
  it { should eql(response) }
@@ -1,7 +1,28 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'devtools/spec_helper'
2
4
 
3
5
  require 'concord' # makes spec setup easier
4
6
 
7
+ if ENV['COVERAGE'] == 'true'
8
+ require 'simplecov'
9
+ require 'coveralls'
10
+
11
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
12
+ SimpleCov::Formatter::HTMLFormatter,
13
+ Coveralls::SimpleCov::Formatter
14
+ ]
15
+
16
+ SimpleCov.start do
17
+ command_name 'spec:unit'
18
+ add_filter 'config'
19
+ add_filter 'spec'
20
+ minimum_coverage 100
21
+ end
22
+ end
23
+
24
+ require 'substation'
25
+
5
26
  module Spec
6
27
 
7
28
  def self.response_data
@@ -28,7 +49,18 @@ module Spec
28
49
  end
29
50
 
30
51
  class Processor
31
- include Concord.new(:handler)
52
+ include Substation::Processor::Fallible
53
+ attr_reader :name
54
+ end
55
+
56
+ class Presenter
57
+ include Concord.new(:data)
58
+ end
59
+
60
+ class Transformer
61
+ def self.call(response)
62
+ :transformed
63
+ end
32
64
  end
33
65
 
34
66
  module Handler
@@ -51,6 +83,10 @@ module Spec
51
83
  end
52
84
  end
53
85
 
86
+ def self.call(data)
87
+ new.call(data)
88
+ end
89
+
54
90
  def call(data)
55
91
  if data == :success
56
92
  Result::Success.new(data)
@@ -66,43 +102,14 @@ module Spec
66
102
  end
67
103
  end
68
104
 
69
- class Wrapper
70
- class Presenter
71
- include Concord.new(:data)
72
- end
73
-
74
- include Concord.new(:data)
75
-
76
- def call(data)
77
- Presenter.new(data)
78
- end
79
- end
80
105
  end
81
106
 
82
107
  FAKE_HANDLER = Object.new
83
- FAKE_PROCESSOR = Processor.new(FAKE_HANDLER)
84
-
85
- end
86
-
87
- if ENV['COVERAGE'] == 'true'
88
- require 'simplecov'
89
- require 'coveralls'
108
+ FAKE_ENV = Object.new
109
+ FAKE_PROCESSOR = Processor.new(:test, FAKE_HANDLER, [])
90
110
 
91
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
92
- SimpleCov::Formatter::HTMLFormatter,
93
- Coveralls::SimpleCov::Formatter
94
- ]
95
-
96
- SimpleCov.start do
97
- command_name 'spec:unit'
98
- add_filter 'config'
99
- add_filter 'spec'
100
- minimum_coverage 100
101
- end
102
111
  end
103
112
 
104
- require 'substation'
105
-
106
113
  include Substation
107
114
 
108
115
  RSpec.configure do |config|
@@ -6,52 +6,228 @@ describe Chain, '#call' do
6
6
 
7
7
  subject { object.call(request) }
8
8
 
9
- let(:object) { described_class.new(handlers) }
10
- let(:handlers) { [ handler_1, handler_2 ] }
11
- let(:request) { Request.new(env, input) }
12
- let(:env) { mock }
13
- let(:input) { mock }
14
-
15
- let(:handler_2) {
16
- Class.new {
17
- include Substation::Chain::Outgoing
18
- def call(request)
19
- request.success(request.input)
20
- end
21
- }.new
22
- }
9
+ let(:object) { described_class.new(processors, exception_chain) }
10
+ let(:exception_chain) { mock(:call => response) }
11
+ let(:processors) { [ processor_1, processor_2, processor_3 ] }
12
+ let(:request) { Request.new(name, env, input) }
13
+ let(:name) { mock }
14
+ let(:env) { mock }
15
+ let(:input) { mock }
16
+
17
+ let(:failure_chain) { mock }
18
+ let(:handler) { mock }
19
+
20
+ let(:processor_1_name) { mock }
21
+ let(:processor_2_name) { mock }
22
+ let(:processor_3_name) { mock }
23
23
 
24
- context "when all handlers are successful" do
25
- let(:handler_1) {
24
+ context 'when all processors are successful' do
25
+ let(:processor_1) {
26
26
  Class.new {
27
- include Substation::Chain::Incoming
27
+ include Substation::Processor::Incoming
28
28
  def call(request)
29
- request.success(request.input)
29
+ request.success(:success_1)
30
+ end
31
+ }.new(processor_1_name, handler, failure_chain)
32
+ }
33
+
34
+ let(:processor_2) {
35
+ Class.new {
36
+ include Substation::Processor::Pivot
37
+ def call(request)
38
+ request.success(:success_2)
39
+ end
40
+ }.new(processor_2_name, handler, failure_chain)
41
+ }
42
+
43
+ let(:processor_3) {
44
+ Class.new {
45
+ include Substation::Processor::Outgoing
46
+ def call(response)
47
+ respond_with(response, :success_3)
30
48
  end
31
- }.new
49
+ }.new(processor_3_name, handler)
32
50
  }
33
51
 
34
- let(:response) { Response::Success.new(request, request.input) }
52
+ let(:response) { Response::Success.new(current_request, :success_3) }
53
+ let(:current_request) { Request.new(name, env, :success_1) }
35
54
 
36
55
  it { should eql(response) }
37
56
  end
38
57
 
39
- context "when an intermediate handler is not successful" do
40
- let(:handler_1) {
58
+ context 'when an incoming processor is not successful' do
59
+
60
+ let(:processor_2) {
41
61
  Class.new {
42
- include Substation::Chain::Incoming
62
+ include Substation::Processor::Pivot
43
63
  def call(request)
44
- request.error(request.input)
64
+ request.success(:success_1)
45
65
  end
46
- }.new
66
+ }.new(processor_2_name, handler, failure_chain)
47
67
  }
48
68
 
49
- let(:response) { Response::Failure.new(request, request.input) }
69
+ let(:processor_3) {
70
+ Class.new {
71
+ include Substation::Processor::Outgoing
72
+ def call(response)
73
+ respond_with(response, :success_3)
74
+ end
75
+ }.new(processor_3_name, handler)
76
+ }
77
+
78
+ let(:response_class) { Response::Failure }
79
+
80
+ context 'because it returned a failure response' do
81
+ let(:processor_1) {
82
+ Class.new {
83
+ include Substation::Processor::Incoming
84
+ def call(request)
85
+ request.error(:error_1)
86
+ end
87
+ }.new(processor_1_name, handler, failure_chain)
88
+ }
50
89
 
51
- before do
52
- handler_2.should_not_receive(:call)
90
+ let(:response) { Response::Failure.new(request, :error_1) }
91
+
92
+ it { should eql(response) }
53
93
  end
54
94
 
55
- it { should eql(response) }
95
+ context 'because it raised an uncaught exception' do
96
+ let(:processor_1) {
97
+ Class.new {
98
+ include Substation::Processor::Incoming
99
+ def call(request)
100
+ raise RuntimeError, 'exception_1'
101
+ end
102
+ }.new(processor_1_name, handler, failure_chain)
103
+ }
104
+
105
+ let(:response) { Response::Failure.new(request, data) }
106
+ let(:data) { Chain::FailureData.new(request, RuntimeError.new('exception_1')) }
107
+
108
+ it { should eql(response) }
109
+
110
+ it 'wraps the original exception instance' do
111
+ expect(subject.output.exception.message).to eql('exception_1')
112
+ end
113
+
114
+ it 'calls the failure chain' do
115
+ exception_chain.should_receive(:call).with(response)
116
+ subject
117
+ end
118
+ end
119
+ end
120
+
121
+ context 'when the pivot processor is not successful' do
122
+ let(:processor_1) {
123
+ Class.new {
124
+ include Substation::Processor::Incoming
125
+ def call(request)
126
+ request.success(:success_1)
127
+ end
128
+ }.new(processor_1_name, handler, failure_chain)
129
+ }
130
+
131
+ let(:processor_3) {
132
+ Class.new {
133
+ include Substation::Processor::Outgoing
134
+ def call(response)
135
+ response
136
+ end
137
+ }.new(processor_3_name, handler)
138
+ }
139
+
140
+ let(:response_class) { Response::Failure }
141
+
142
+ context 'because it returned a failure response' do
143
+ let(:processor_2) {
144
+ Class.new {
145
+ include Substation::Processor::Pivot
146
+ def call(request)
147
+ request.error(:error_2)
148
+ end
149
+ }.new(processor_2_name, handler, failure_chain)
150
+ }
151
+
152
+ let(:response) { Response::Failure.new(current_request, :error_2) }
153
+ let(:current_request) { Request.new(name, env, :success_1) }
154
+
155
+ it { should eql(response) }
156
+ end
157
+
158
+ context 'because it raised an uncaught exception' do
159
+ let(:processor_2) {
160
+ Class.new {
161
+ include Substation::Processor::Pivot
162
+ def call(request)
163
+ raise RuntimeError, 'exception_2'
164
+ end
165
+ }.new(processor_2_name, handler, failure_chain)
166
+ }
167
+
168
+ let(:response) { Response::Failure.new(request, data) }
169
+ let(:data) { Chain::FailureData.new(current_request, RuntimeError.new('exception_2')) }
170
+ let(:current_request) { Request.new(name, env, :success_1) }
171
+
172
+ it { should eql(response) }
173
+
174
+ it 'wraps the original exception instance' do
175
+ expect(subject.output.exception.message).to eql('exception_2')
176
+ end
177
+
178
+ it 'calls the failure chain' do
179
+ exception_chain.should_receive(:call).with(response)
180
+ subject
181
+ end
182
+ end
183
+ end
184
+
185
+ context 'when an outgoing processor is not successful' do
186
+ let(:processor_1) {
187
+ Class.new {
188
+ include Substation::Processor::Incoming
189
+ def call(request)
190
+ request.success(:success_1)
191
+ end
192
+ }.new(processor_1_name, handler, failure_chain)
193
+ }
194
+
195
+ let(:processor_2) {
196
+ Class.new {
197
+ include Substation::Processor::Pivot
198
+ def call(response)
199
+ response.success(:success_2)
200
+ end
201
+ }.new(processor_2_name, handler, failure_chain)
202
+ }
203
+
204
+ let(:response_class) { Response::Failure }
205
+
206
+ context 'because it raised an uncaught exception' do
207
+ let(:processor_3) {
208
+ Class.new {
209
+ include Substation::Processor::Outgoing
210
+ def call(response)
211
+ raise RuntimeError, 'exception_3'
212
+ end
213
+ }.new(processor_3_name, handler)
214
+ }
215
+
216
+ let(:response) { Response::Failure.new(request, data) }
217
+ let(:data) { Chain::FailureData.new(current_response, RuntimeError.new('exception_3')) }
218
+ let(:current_request) { Request.new(name, env, :success_1) }
219
+ let(:current_response) { Response::Success.new(current_request, :success_2) }
220
+
221
+ it { should eql(response) }
222
+
223
+ it 'wraps the original exception instance' do
224
+ expect(subject.output.exception.message).to eql('exception_3')
225
+ end
226
+
227
+ it 'calls the failure chain' do
228
+ exception_chain.should_receive(:call).with(response)
229
+ subject
230
+ end
231
+ end
56
232
  end
57
233
  end