substation 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Changelog.md +92 -2
- data/Gemfile.devtools +19 -17
- data/README.md +116 -46
- data/config/flay.yml +2 -2
- data/config/mutant.yml +1 -0
- data/config/reek.yml +11 -5
- data/config/rubocop.yml +35 -0
- data/lib/substation.rb +10 -1
- data/lib/substation/chain.rb +108 -64
- data/lib/substation/chain/dsl.rb +62 -37
- data/lib/substation/dispatcher.rb +3 -1
- data/lib/substation/environment.rb +9 -6
- data/lib/substation/environment/dsl.rb +4 -3
- data/lib/substation/observer.rb +2 -0
- data/lib/substation/processor.rb +106 -7
- data/lib/substation/processor/evaluator.rb +98 -7
- data/lib/substation/processor/transformer.rb +26 -0
- data/lib/substation/processor/wrapper.rb +5 -3
- data/lib/substation/request.rb +12 -1
- data/lib/substation/response.rb +13 -0
- data/lib/substation/utils.rb +3 -1
- data/lib/substation/version.rb +3 -1
- data/spec/integration/substation/dispatcher/call_spec.rb +12 -12
- data/spec/spec_helper.rb +39 -32
- data/spec/unit/substation/chain/call_spec.rb +205 -29
- data/spec/unit/substation/chain/class_methods/failure_response_spec.rb +16 -0
- data/spec/unit/substation/chain/dsl/builder/dsl_spec.rb +7 -4
- data/spec/unit/substation/chain/dsl/class_methods/build_spec.rb +24 -0
- data/spec/unit/substation/chain/dsl/failure_chain_spec.rb +35 -0
- data/spec/unit/substation/chain/dsl/processors_spec.rb +8 -6
- data/spec/unit/substation/chain/dsl/use_spec.rb +1 -1
- data/spec/unit/substation/chain/each_spec.rb +5 -9
- data/spec/unit/substation/chain/failure_data/equalizer_spec.rb +46 -0
- data/spec/unit/substation/chain/failure_data/hash_spec.rb +13 -0
- data/spec/unit/substation/dispatcher/action/call_spec.rb +2 -1
- data/spec/unit/substation/dispatcher/action/class_methods/coerce_spec.rb +7 -5
- data/spec/unit/substation/dispatcher/call_spec.rb +2 -2
- data/spec/unit/substation/dispatcher/class_methods/coerce_spec.rb +6 -6
- data/spec/unit/substation/environment/chain_spec.rb +22 -27
- data/spec/unit/substation/environment/class_methods/build_spec.rb +11 -4
- data/spec/unit/substation/environment/dsl/class_methods/registry_spec.rb +5 -3
- data/spec/unit/substation/environment/dsl/register_spec.rb +8 -3
- data/spec/unit/substation/environment/dsl/registry_spec.rb +5 -3
- data/spec/unit/substation/environment/equalizer_spec.rb +25 -0
- data/spec/unit/substation/observer/chain/call_spec.rb +2 -0
- data/spec/unit/substation/observer/class_methods/coerce_spec.rb +2 -0
- data/spec/unit/substation/observer/null/call_spec.rb +2 -0
- data/spec/unit/substation/processor/evaluator/call_spec.rb +20 -10
- data/spec/unit/substation/processor/evaluator/class_methods/new_spec.rb +9 -0
- data/spec/unit/substation/processor/evaluator/data/call_spec.rb +34 -0
- data/spec/unit/substation/processor/evaluator/pivot/call_spec.rb +34 -0
- data/spec/unit/substation/processor/evaluator/request/call_spec.rb +34 -0
- data/spec/unit/substation/processor/fallible/name_spec.rb +15 -0
- data/spec/unit/substation/processor/fallible/with_failure_chain_spec.rb +18 -0
- data/spec/unit/substation/processor/incoming/result_spec.rb +25 -0
- data/spec/unit/substation/processor/outgoing/call_spec.rb +28 -0
- data/spec/unit/substation/processor/outgoing/name_spec.rb +14 -0
- data/spec/unit/substation/processor/outgoing/success_predicate_spec.rb +15 -0
- data/spec/unit/substation/{chain/outgoing → processor}/result_spec.rb +4 -3
- data/spec/unit/substation/processor/success_predicate_spec.rb +22 -0
- data/spec/unit/substation/processor/transformer/call_spec.rb +21 -0
- data/spec/unit/substation/processor/wrapper/call_spec.rb +9 -7
- data/spec/unit/substation/request/env_spec.rb +3 -2
- data/spec/unit/substation/request/error_spec.rb +2 -1
- data/spec/unit/substation/request/input_spec.rb +3 -2
- data/spec/unit/substation/request/name_spec.rb +15 -0
- data/spec/unit/substation/request/success_spec.rb +2 -1
- data/spec/unit/substation/response/env_spec.rb +3 -2
- data/spec/unit/substation/response/failure/success_predicate_spec.rb +2 -1
- data/spec/unit/substation/response/input_spec.rb +3 -2
- data/spec/unit/substation/response/output_spec.rb +2 -1
- data/spec/unit/substation/response/request_spec.rb +3 -2
- data/spec/unit/substation/response/success/success_predicate_spec.rb +2 -1
- data/spec/unit/substation/response/to_request_spec.rb +19 -0
- data/spec/unit/substation/utils/class_methods/coerce_callable_spec.rb +14 -12
- data/spec/unit/substation/utils/class_methods/const_get_spec.rb +6 -6
- data/spec/unit/substation/utils/class_methods/symbolize_keys_spec.rb +4 -4
- metadata +25 -9
- data/lib/substation/processor/pivot.rb +0 -25
- data/spec/unit/substation/chain/class_methods/build_spec.rb +0 -31
- data/spec/unit/substation/chain/dsl/class_methods/processors_spec.rb +0 -23
- data/spec/unit/substation/chain/incoming/result_spec.rb +0 -21
- data/spec/unit/substation/chain/outgoing/call_spec.rb +0 -25
- data/spec/unit/substation/processor/pivot/call_spec.rb +0 -16
data/lib/substation/utils.rb
CHANGED
@@ -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|
|
data/lib/substation/version.rb
CHANGED
@@ -181,8 +181,8 @@ module App
|
|
181
181
|
end # module Actions
|
182
182
|
|
183
183
|
module Observers
|
184
|
-
|
185
|
-
|
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::
|
202
|
-
Observers::
|
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
|
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
|
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
|
237
|
-
context
|
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
|
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::
|
252
|
-
App::Observers::
|
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) }
|
data/spec/spec_helper.rb
CHANGED
@@ -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
|
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
|
-
|
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)
|
10
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:
|
13
|
-
let(:
|
14
|
-
|
15
|
-
let(:
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
25
|
-
let(:
|
24
|
+
context 'when all processors are successful' do
|
25
|
+
let(:processor_1) {
|
26
26
|
Class.new {
|
27
|
-
include Substation::
|
27
|
+
include Substation::Processor::Incoming
|
28
28
|
def call(request)
|
29
|
-
request.success(
|
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)
|
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
|
40
|
-
|
58
|
+
context 'when an incoming processor is not successful' do
|
59
|
+
|
60
|
+
let(:processor_2) {
|
41
61
|
Class.new {
|
42
|
-
include Substation::
|
62
|
+
include Substation::Processor::Pivot
|
43
63
|
def call(request)
|
44
|
-
request.
|
64
|
+
request.success(:success_1)
|
45
65
|
end
|
46
|
-
}.new
|
66
|
+
}.new(processor_2_name, handler, failure_chain)
|
47
67
|
}
|
48
68
|
|
49
|
-
let(:
|
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
|
-
|
52
|
-
|
90
|
+
let(:response) { Response::Failure.new(request, :error_1) }
|
91
|
+
|
92
|
+
it { should eql(response) }
|
53
93
|
end
|
54
94
|
|
55
|
-
it
|
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
|