substation 0.0.8 → 0.0.9

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