teckel 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -7
- data/lib/teckel/chain/config.rb +20 -19
- data/lib/teckel/chain/result.rb +5 -9
- data/lib/teckel/chain/runner.rb +8 -6
- data/lib/teckel/chain.rb +15 -11
- data/lib/teckel/config.rb +15 -9
- data/lib/teckel/contracts.rb +3 -3
- data/lib/teckel/operation/config.rb +31 -28
- data/lib/teckel/operation/result.rb +8 -8
- data/lib/teckel/operation/runner.rb +1 -1
- data/lib/teckel/operation.rb +5 -5
- data/lib/teckel/result.rb +2 -2
- data/lib/teckel/version.rb +2 -1
- data/lib/teckel.rb +4 -1
- metadata +19 -54
- data/spec/chain/around_hook_spec.rb +0 -100
- data/spec/chain/default_settings_spec.rb +0 -39
- data/spec/chain/inheritance_spec.rb +0 -116
- data/spec/chain/none_input_spec.rb +0 -36
- data/spec/chain/results_spec.rb +0 -53
- data/spec/chain_spec.rb +0 -255
- data/spec/config_spec.rb +0 -26
- data/spec/doctest_helper.rb +0 -8
- data/spec/operation/config_spec.rb +0 -227
- data/spec/operation/contract_trace_spec.rb +0 -118
- data/spec/operation/default_settings_spec.rb +0 -120
- data/spec/operation/fail_on_input_spec.rb +0 -103
- data/spec/operation/inheritance_spec.rb +0 -94
- data/spec/operation/result_spec.rb +0 -49
- data/spec/operation/results_spec.rb +0 -117
- data/spec/operation_spec.rb +0 -531
- data/spec/rb27/pattern_matching_spec.rb +0 -193
- data/spec/result_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -35
- data/spec/support/dry_base.rb +0 -8
- data/spec/support/fake_db.rb +0 -12
- data/spec/support/fake_models.rb +0 -20
- data/spec/teckel_spec.rb +0 -7
data/spec/chain_spec.rb
DELETED
@@ -1,255 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'support/dry_base'
|
4
|
-
require 'support/fake_models'
|
5
|
-
|
6
|
-
module TeckelChainTest
|
7
|
-
class CreateUser
|
8
|
-
include ::Teckel::Operation
|
9
|
-
result!
|
10
|
-
|
11
|
-
input Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
|
12
|
-
output Types.Instance(User)
|
13
|
-
error Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
|
14
|
-
|
15
|
-
def call(input)
|
16
|
-
user = User.new(name: input[:name], age: input[:age])
|
17
|
-
if user.save
|
18
|
-
success!(user)
|
19
|
-
else
|
20
|
-
fail!(message: "Could not save User", errors: user.errors)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class LogUser
|
26
|
-
include ::Teckel::Operation
|
27
|
-
|
28
|
-
result!
|
29
|
-
|
30
|
-
input Types.Instance(User)
|
31
|
-
error none
|
32
|
-
output input
|
33
|
-
|
34
|
-
def call(usr)
|
35
|
-
Logger.new(File::NULL).info("User #{usr.name} created")
|
36
|
-
success! usr
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class AddFriend
|
41
|
-
include ::Teckel::Operation
|
42
|
-
|
43
|
-
result!
|
44
|
-
|
45
|
-
settings Struct.new(:fail_befriend)
|
46
|
-
|
47
|
-
input Types.Instance(User)
|
48
|
-
output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
|
49
|
-
error Types::Hash.schema(message: Types::String)
|
50
|
-
|
51
|
-
def call(user)
|
52
|
-
if settings&.fail_befriend
|
53
|
-
fail!(message: "Did not find a friend.")
|
54
|
-
else
|
55
|
-
success! user: user, friend: User.new(name: "A friend", age: 42)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
class Chain
|
61
|
-
include Teckel::Chain
|
62
|
-
|
63
|
-
step :create, CreateUser
|
64
|
-
step :log, LogUser
|
65
|
-
step :befriend, AddFriend
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
RSpec.describe Teckel::Chain do
|
70
|
-
let(:frozen_error) do
|
71
|
-
# different ruby versions raise different errors
|
72
|
-
defined?(FrozenError) ? FrozenError : RuntimeError
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'Chain input points to first step input' do
|
76
|
-
expect(TeckelChainTest::Chain.input).to eq(TeckelChainTest::CreateUser.input)
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'Chain output points to last steps output' do
|
80
|
-
expect(TeckelChainTest::Chain.output).to eq(TeckelChainTest::AddFriend.output)
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'Chain errors maps all step errors' do
|
84
|
-
expect(TeckelChainTest::Chain.errors).to eq([
|
85
|
-
TeckelChainTest::CreateUser.error,
|
86
|
-
Teckel::Contracts::None,
|
87
|
-
TeckelChainTest::AddFriend.error
|
88
|
-
])
|
89
|
-
end
|
90
|
-
|
91
|
-
context "success" do
|
92
|
-
it "result matches" do
|
93
|
-
result = TeckelChainTest::Chain.
|
94
|
-
with(befriend: nil).
|
95
|
-
call(name: "Bob", age: 23)
|
96
|
-
|
97
|
-
expect(result.success).to include(user: kind_of(User), friend: kind_of(User))
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
context "failure" do
|
102
|
-
it "returns a Result for invalid input" do
|
103
|
-
result = TeckelChainTest::Chain.
|
104
|
-
with(befriend: :fail).
|
105
|
-
call(name: "Bob", age: 0)
|
106
|
-
|
107
|
-
expect(result).to be_a(Teckel::Chain::Result)
|
108
|
-
expect(result).to be_failure
|
109
|
-
expect(result.step).to eq(:create)
|
110
|
-
expect(result.value).to eq(errors: [{ age: "underage" }], message: "Could not save User")
|
111
|
-
end
|
112
|
-
|
113
|
-
it "returns a Result for failed step" do
|
114
|
-
result = TeckelChainTest::Chain.
|
115
|
-
with(befriend: :fail).
|
116
|
-
call(name: "Bob", age: 23)
|
117
|
-
|
118
|
-
expect(result).to be_a(Teckel::Chain::Result)
|
119
|
-
expect(result).to be_failure
|
120
|
-
expect(result.step).to eq(:befriend)
|
121
|
-
expect(result.value).to eq(message: "Did not find a friend.")
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
describe "#finalize!" do
|
126
|
-
subject { TeckelChainTest::Chain.dup }
|
127
|
-
|
128
|
-
it "freezes the Chain class and operation classes" do
|
129
|
-
subject.finalize!
|
130
|
-
|
131
|
-
steps = subject.steps
|
132
|
-
expect(steps).to be_frozen
|
133
|
-
expect(steps).to all be_frozen
|
134
|
-
end
|
135
|
-
|
136
|
-
it "disallows adding new steps" do
|
137
|
-
subject.class_eval do
|
138
|
-
step :other, TeckelChainTest::AddFriend
|
139
|
-
end
|
140
|
-
|
141
|
-
subject.finalize!
|
142
|
-
|
143
|
-
expect {
|
144
|
-
subject.class_eval do
|
145
|
-
step :yet_other, TeckelChainTest::AddFriend
|
146
|
-
end
|
147
|
-
}.to raise_error(frozen_error)
|
148
|
-
end
|
149
|
-
|
150
|
-
it "disallows changing around hook" do
|
151
|
-
subject.class_eval do
|
152
|
-
around ->{}
|
153
|
-
end
|
154
|
-
|
155
|
-
chain2 = TeckelChainTest::Chain.dup.finalize!
|
156
|
-
expect {
|
157
|
-
chain2.class_eval do
|
158
|
-
around ->{}
|
159
|
-
end
|
160
|
-
}.to raise_error(frozen_error)
|
161
|
-
end
|
162
|
-
|
163
|
-
it "runs" do
|
164
|
-
subject.finalize!
|
165
|
-
|
166
|
-
result = subject.call(name: "Bob", age: 23)
|
167
|
-
expect(result.success).to include(user: kind_of(User), friend: kind_of(User))
|
168
|
-
end
|
169
|
-
|
170
|
-
it "accepts mocks" do
|
171
|
-
subject.finalize!
|
172
|
-
|
173
|
-
allow(subject).to receive(:call) { :mocked }
|
174
|
-
expect(subject.call).to eq(:mocked)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
describe "#clone" do
|
179
|
-
subject { TeckelChainTest::Chain.dup }
|
180
|
-
let(:klone) { subject.clone }
|
181
|
-
|
182
|
-
it 'clones' do
|
183
|
-
expect(klone.object_id).not_to be_eql(subject.object_id)
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'clones config' do
|
187
|
-
orig_config = subject.instance_variable_get(:@config)
|
188
|
-
klone_config = klone.instance_variable_get(:@config)
|
189
|
-
expect(klone_config.object_id).not_to be_eql(orig_config.object_id)
|
190
|
-
end
|
191
|
-
|
192
|
-
it 'clones steps' do
|
193
|
-
orig_settings = subject.instance_variable_get(:@config).instance_variable_get(:@config)[:steps]
|
194
|
-
klone_settings = klone.instance_variable_get(:@config).instance_variable_get(:@config)[:steps]
|
195
|
-
|
196
|
-
expect(orig_settings).to be_a(Array)
|
197
|
-
expect(klone_settings).to be_a(Array)
|
198
|
-
expect(klone_settings.object_id).not_to be_eql(orig_settings.object_id)
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
describe "frozen" do
|
203
|
-
subject { TeckelChainTest::Chain.dup }
|
204
|
-
|
205
|
-
it "also freezes the config" do
|
206
|
-
expect { subject.freeze }.to change {
|
207
|
-
[
|
208
|
-
subject.frozen?,
|
209
|
-
subject.instance_variable_get(:@config).frozen?
|
210
|
-
]
|
211
|
-
}.from([false, false]).to([true, true])
|
212
|
-
end
|
213
|
-
|
214
|
-
it "prevents changes to steps" do
|
215
|
-
subject.freeze
|
216
|
-
expect {
|
217
|
-
subject.class_eval do
|
218
|
-
step :yet_other, TeckelChainTest::AddFriend
|
219
|
-
end
|
220
|
-
}.to raise_error(frozen_error)
|
221
|
-
end
|
222
|
-
|
223
|
-
it "prevents changes to config" do
|
224
|
-
subject.freeze
|
225
|
-
expect {
|
226
|
-
subject.class_eval do
|
227
|
-
default_settings!(a: { say: "Chain Default" })
|
228
|
-
end
|
229
|
-
}.to raise_error(frozen_error)
|
230
|
-
end
|
231
|
-
|
232
|
-
describe '#clone' do
|
233
|
-
subject { TeckelChainTest::Chain.dup }
|
234
|
-
|
235
|
-
it 'clones the class' do
|
236
|
-
subject.freeze
|
237
|
-
klone = subject.clone
|
238
|
-
|
239
|
-
expect(klone).to be_frozen
|
240
|
-
expect(klone.object_id).not_to be_eql(subject.object_id)
|
241
|
-
end
|
242
|
-
|
243
|
-
it 'cloned class uses the same, frozen config' do
|
244
|
-
subject.freeze
|
245
|
-
klone = subject.clone
|
246
|
-
|
247
|
-
orig_config = subject.instance_variable_get(:@config)
|
248
|
-
klone_config = klone.instance_variable_get(:@config)
|
249
|
-
|
250
|
-
expect(klone_config).to be_frozen
|
251
|
-
expect(klone_config.object_id).to be_eql(orig_config.object_id)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
data/spec/config_spec.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'support/dry_base'
|
4
|
-
require 'support/fake_models'
|
5
|
-
|
6
|
-
RSpec.describe Teckel::Config do
|
7
|
-
let(:sample_config) do
|
8
|
-
Teckel::Config.new
|
9
|
-
end
|
10
|
-
|
11
|
-
it "set and retrieve key" do
|
12
|
-
sample_config.for(:some_key, "some_value")
|
13
|
-
expect(sample_config.for(:some_key)).to eq("some_value")
|
14
|
-
end
|
15
|
-
|
16
|
-
it "allow default value via block" do
|
17
|
-
expect(sample_config.for(:some_key) { "default" }).to eq("default")
|
18
|
-
# and sets the block value
|
19
|
-
expect(sample_config.for(:some_key)).to eq("default")
|
20
|
-
end
|
21
|
-
|
22
|
-
it "raises FrozenConfigError when setting a key twice" do
|
23
|
-
sample_config.for(:some_key, "some_value")
|
24
|
-
expect { sample_config.for(:some_key, "other_value") }.to raise_error(Teckel::FrozenConfigError)
|
25
|
-
end
|
26
|
-
end
|
data/spec/doctest_helper.rb
DELETED
@@ -1,227 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ostruct"
|
4
|
-
|
5
|
-
RSpec.describe Teckel::Operation do
|
6
|
-
let(:operation) do
|
7
|
-
Class.new do
|
8
|
-
include Teckel::Operation
|
9
|
-
input none
|
10
|
-
output ->(o) { o }
|
11
|
-
error none
|
12
|
-
|
13
|
-
def call(_)
|
14
|
-
success! settings
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
let(:blank_operation) do
|
20
|
-
Class.new do
|
21
|
-
include Teckel::Operation
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
describe ".settings" do
|
26
|
-
specify "no settings" do
|
27
|
-
expect(operation.settings).to eq(Teckel::Contracts::None)
|
28
|
-
expect(operation.settings_constructor).to eq(Teckel::Contracts::None.method(:new))
|
29
|
-
end
|
30
|
-
|
31
|
-
specify "with settings klass" do
|
32
|
-
settings_klass = Struct.new(:name)
|
33
|
-
operation.settings(settings_klass)
|
34
|
-
expect(operation.settings).to eq(settings_klass)
|
35
|
-
end
|
36
|
-
|
37
|
-
specify "without settings class, with settings constructor as proc" do
|
38
|
-
settings_const = if RUBY_VERSION < '2.6.0'
|
39
|
-
->(sets) { sets.map { |k, v| [k.to_s, v.to_i] }.to_h }
|
40
|
-
else
|
41
|
-
->(sets) { sets.to_h { |k, v| [k.to_s, v.to_i] } }
|
42
|
-
end
|
43
|
-
|
44
|
-
operation.settings_constructor(settings_const)
|
45
|
-
|
46
|
-
expect(operation.settings).to eq(Teckel::Contracts::None)
|
47
|
-
expect(operation.settings_constructor).to eq(settings_const)
|
48
|
-
|
49
|
-
runner = operation.with(key: "1")
|
50
|
-
expect(runner).to be_a(Teckel::Operation::Runner)
|
51
|
-
expect(runner.settings).to eq({ "key" => 1 })
|
52
|
-
end
|
53
|
-
|
54
|
-
specify "with settings class, with settings constructor as symbol" do
|
55
|
-
settings_klass = Struct.new(:name) do
|
56
|
-
def self.make_one(opts)
|
57
|
-
new(opts[:name])
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
operation.settings(settings_klass)
|
62
|
-
operation.settings_constructor(:make_one)
|
63
|
-
|
64
|
-
expect(operation.settings).to eq(settings_klass)
|
65
|
-
expect(operation.settings_constructor).to eq(settings_klass.method(:make_one))
|
66
|
-
|
67
|
-
runner = operation.with(name: "value")
|
68
|
-
expect(runner).to be_a(Teckel::Operation::Runner)
|
69
|
-
expect(runner.settings).to be_a(settings_klass)
|
70
|
-
expect(runner.settings.name).to eq("value")
|
71
|
-
end
|
72
|
-
|
73
|
-
specify "with settings class as constant" do
|
74
|
-
settings_klass = Struct.new(:name)
|
75
|
-
operation.const_set(:Settings, settings_klass)
|
76
|
-
|
77
|
-
expect(operation.settings).to eq(settings_klass)
|
78
|
-
expect(operation.settings_constructor).to eq(settings_klass.method(:[]))
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
describe ".default_settings" do
|
83
|
-
specify "no default_settings" do
|
84
|
-
expect(operation.default_settings).to be_nil
|
85
|
-
expect(operation.runner).to receive(:new).with(operation).and_call_original
|
86
|
-
|
87
|
-
operation.call
|
88
|
-
end
|
89
|
-
|
90
|
-
specify "default_settings!() with no default_settings" do
|
91
|
-
operation.default_settings!
|
92
|
-
expect(operation.default_settings).to be_a(Proc)
|
93
|
-
|
94
|
-
expect(operation.default_settings).to receive(:call).with(no_args).and_wrap_original do |original_method, *args, &block|
|
95
|
-
settings = original_method.call(*args, &block)
|
96
|
-
expect(settings).to be_nil
|
97
|
-
expect(operation.runner).to receive(:new).with(operation, settings).and_call_original
|
98
|
-
settings
|
99
|
-
end
|
100
|
-
|
101
|
-
operation.call
|
102
|
-
end
|
103
|
-
|
104
|
-
specify "default_settings!() with default_settings" do
|
105
|
-
settings_klass = Struct.new(:name)
|
106
|
-
|
107
|
-
operation.settings(settings_klass)
|
108
|
-
operation.default_settings!
|
109
|
-
|
110
|
-
expect(operation.default_settings).to be_a(Proc)
|
111
|
-
|
112
|
-
expect(operation.default_settings).to receive(:call).with(no_args).and_wrap_original do |original_method, *args, &block|
|
113
|
-
settings = original_method.call(*args, &block)
|
114
|
-
expect(settings).to be_a(settings_klass).and have_attributes(name: nil)
|
115
|
-
expect(operation.runner).to receive(:new).with(operation, settings).and_call_original
|
116
|
-
settings
|
117
|
-
end
|
118
|
-
|
119
|
-
operation.call
|
120
|
-
end
|
121
|
-
|
122
|
-
specify "default_settings!(arg) with default_settings" do
|
123
|
-
settings_klass = Struct.new(:name)
|
124
|
-
|
125
|
-
operation.settings(settings_klass)
|
126
|
-
operation.default_settings!("Bob")
|
127
|
-
|
128
|
-
expect(operation.default_settings).to be_a(Proc)
|
129
|
-
|
130
|
-
expect(operation.default_settings).to receive(:call).with(no_args).and_wrap_original do |original_method, *args, &block|
|
131
|
-
settings = original_method.call(*args, &block)
|
132
|
-
expect(settings).to be_a(settings_klass).and have_attributes(name: "Bob")
|
133
|
-
expect(operation.runner).to receive(:new).with(operation, settings).and_call_original
|
134
|
-
settings
|
135
|
-
end
|
136
|
-
|
137
|
-
expect(operation.call).to be_a(Struct).and have_attributes(name: "Bob")
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
%i[input output error].each do |meth|
|
142
|
-
describe ".#{meth}" do
|
143
|
-
specify "missing .#{meth} config raises MissingConfigError" do
|
144
|
-
expect {
|
145
|
-
blank_operation.public_send(meth)
|
146
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing #{meth} config for #{blank_operation}")
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
describe ".#{meth}_constructor" do
|
151
|
-
specify "missing .#{meth}_constructor config raises MissingConfigError for missing #{meth}" do
|
152
|
-
expect {
|
153
|
-
blank_operation.public_send(:"#{meth}_constructor")
|
154
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing #{meth} config for #{blank_operation}")
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
specify "default settings config" do
|
160
|
-
expect(blank_operation.settings).to eq(Teckel::Contracts::None)
|
161
|
-
end
|
162
|
-
|
163
|
-
specify "default settings_constructor" do
|
164
|
-
expect(blank_operation.settings_constructor).to eq(Teckel::Contracts::None.method(:[]))
|
165
|
-
end
|
166
|
-
|
167
|
-
specify "default settings_constructor with settings config set" do
|
168
|
-
settings_klass = Struct.new(:name)
|
169
|
-
blank_operation.settings(settings_klass)
|
170
|
-
|
171
|
-
expect(blank_operation.settings_constructor).to eq(settings_klass.method(:[]))
|
172
|
-
end
|
173
|
-
|
174
|
-
specify "unsupported constructor method" do
|
175
|
-
blank_operation.settings(Class.new)
|
176
|
-
expect {
|
177
|
-
blank_operation.settings_constructor(:nope)
|
178
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing settings_constructor config for #{blank_operation}")
|
179
|
-
|
180
|
-
expect {
|
181
|
-
blank_operation.settings_constructor
|
182
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing settings_constructor config for #{blank_operation}")
|
183
|
-
end
|
184
|
-
|
185
|
-
describe "result" do
|
186
|
-
specify "default result config" do
|
187
|
-
expect(blank_operation.result).to eq(Teckel::Operation::ValueResult)
|
188
|
-
end
|
189
|
-
|
190
|
-
specify "default result_constructor" do
|
191
|
-
expect(blank_operation.result_constructor).to eq(Teckel::Operation::ValueResult.method(:[]))
|
192
|
-
end
|
193
|
-
|
194
|
-
specify "default result_constructor with settings config set" do
|
195
|
-
result_klass = OpenStruct.new
|
196
|
-
blank_operation.result(result_klass)
|
197
|
-
|
198
|
-
expect(blank_operation.result_constructor).to eq(result_klass.method(:[]))
|
199
|
-
end
|
200
|
-
|
201
|
-
specify "unsupported constructor method" do
|
202
|
-
blank_operation.result(Class.new)
|
203
|
-
expect {
|
204
|
-
blank_operation.result_constructor(:nope)
|
205
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing result_constructor config for #{blank_operation}")
|
206
|
-
|
207
|
-
expect {
|
208
|
-
blank_operation.result_constructor
|
209
|
-
}.to raise_error(Teckel::MissingConfigError, "Missing result_constructor config for #{blank_operation}")
|
210
|
-
end
|
211
|
-
|
212
|
-
specify "with result class as constant" do
|
213
|
-
result_klass = OpenStruct.new
|
214
|
-
blank_operation.const_set(:Result, result_klass)
|
215
|
-
|
216
|
-
expect(blank_operation.result).to eq(result_klass)
|
217
|
-
expect(blank_operation.result_constructor).to eq(result_klass.method(:[]))
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
describe "result!" do
|
222
|
-
specify "default result config" do
|
223
|
-
blank_operation.result!
|
224
|
-
expect(blank_operation.result).to eq(Teckel::Operation::Result)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'support/dry_base'
|
4
|
-
|
5
|
-
module TeckelOperationContractTrace
|
6
|
-
DefaultError = Struct.new(:message, :status_code)
|
7
|
-
Settings = Struct.new(:fail_it)
|
8
|
-
|
9
|
-
class ApplicationOperation
|
10
|
-
include Teckel::Operation
|
11
|
-
|
12
|
-
class Input < Dry::Struct
|
13
|
-
attribute :input_data, Types::String
|
14
|
-
end
|
15
|
-
|
16
|
-
class Output < Dry::Struct
|
17
|
-
attribute :output_data, Types::String
|
18
|
-
end
|
19
|
-
|
20
|
-
class Error < Dry::Struct
|
21
|
-
attribute :error_data, Types::String
|
22
|
-
end
|
23
|
-
|
24
|
-
# Freeze the base class to make sure it's inheritable configuration is not altered
|
25
|
-
freeze
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# rubocop:disable Style/EvalWithLocation
|
30
|
-
# Hack to get reliable stack traces
|
31
|
-
eval <<~RUBY, binding, "operation_success_error.rb"
|
32
|
-
module TeckelOperationContractTrace
|
33
|
-
class OperationSuccessError < ApplicationOperation
|
34
|
-
# Includes a deliberate bug while crating a success output
|
35
|
-
def call(input)
|
36
|
-
success!(incorrect_key: 1)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
RUBY
|
41
|
-
|
42
|
-
eval <<~RUBY, binding, "operation_simple_success_error.rb"
|
43
|
-
module TeckelOperationContractTrace
|
44
|
-
class OperationSimpleSuccessNil < ApplicationOperation
|
45
|
-
# Includes a deliberate bug while crating a success output
|
46
|
-
def call(input)
|
47
|
-
return { incorrect_key: 1 }
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
RUBY
|
52
|
-
|
53
|
-
eval <<~RUBY, binding, "operation_failure_error.rb"
|
54
|
-
module TeckelOperationContractTrace
|
55
|
-
class OperationFailureError < ApplicationOperation
|
56
|
-
# Includes a deliberate bug while crating an error output
|
57
|
-
def call(input)
|
58
|
-
fail!(incorrect_key: 1)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
RUBY
|
63
|
-
|
64
|
-
eval <<~RUBY, binding, "operation_ok.rb"
|
65
|
-
module TeckelOperationContractTrace
|
66
|
-
class OperationOk < ApplicationOperation
|
67
|
-
def call(input)
|
68
|
-
success!(output_data: "all fine")
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
RUBY
|
73
|
-
|
74
|
-
eval <<~RUBY, binding, "operation_input_error.rb"
|
75
|
-
module TeckelOperationContractTrace
|
76
|
-
def self.run_operation(operation)
|
77
|
-
operation.call(error_input_data: "failure")
|
78
|
-
end
|
79
|
-
end
|
80
|
-
RUBY
|
81
|
-
# rubocop:enable Style/EvalWithLocation
|
82
|
-
|
83
|
-
RSpec.describe Teckel::Operation do
|
84
|
-
context "contract errors include meaningful trace" do
|
85
|
-
specify "incorrect success" do
|
86
|
-
expect {
|
87
|
-
TeckelOperationContractTrace::OperationSuccessError.call(input_data: "ok")
|
88
|
-
}.to raise_error(Dry::Struct::Error) { |error|
|
89
|
-
expect(error.backtrace).to include /^#{Regexp.escape("operation_success_error.rb:5:in `call'")}$/
|
90
|
-
}
|
91
|
-
end
|
92
|
-
|
93
|
-
specify "incorrect success via simple return results in +nil+, but no meaningful trace" do
|
94
|
-
expect(
|
95
|
-
TeckelOperationContractTrace::OperationSimpleSuccessNil.call(input_data: "ok")
|
96
|
-
).to be_nil
|
97
|
-
end
|
98
|
-
|
99
|
-
specify "incorrect fail" do
|
100
|
-
expect {
|
101
|
-
TeckelOperationContractTrace::OperationFailureError.call(input_data: "ok")
|
102
|
-
}.to raise_error(Dry::Struct::Error) { |error|
|
103
|
-
expect(error.backtrace).to include /^#{Regexp.escape("operation_failure_error.rb:5:in `call'")}$/
|
104
|
-
}
|
105
|
-
end
|
106
|
-
|
107
|
-
specify "incorrect input" do
|
108
|
-
operation = TeckelOperationContractTrace::OperationOk
|
109
|
-
|
110
|
-
expect(operation.call(input_data: "ok")).to eq(operation.output[output_data: "all fine"])
|
111
|
-
expect {
|
112
|
-
TeckelOperationContractTrace.run_operation(operation)
|
113
|
-
}.to raise_error(Dry::Struct::Error) { |error|
|
114
|
-
expect(error.backtrace).to include /^#{Regexp.escape("operation_input_error.rb:3:in `run_operation'")}$/
|
115
|
-
}
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|