interaktor 0.2.0 → 0.3.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/.github/workflows/publish.yml +0 -1
- data/.github/workflows/tests.yml +5 -5
- data/.gitignore +3 -0
- data/Gemfile +1 -1
- data/Gemfile.ci +6 -0
- data/README.md +65 -29
- data/interaktor.gemspec +3 -2
- data/lib/interaktor.rb +11 -16
- data/lib/interaktor/callable.rb +235 -108
- data/lib/interaktor/error/attribute_error.rb +3 -1
- data/lib/interaktor/error/attribute_schema_validation_error.rb +54 -0
- data/lib/interaktor/error/missing_explicit_success_error.rb +5 -0
- data/lib/interaktor/error/option_error.rb +3 -1
- data/lib/interaktor/error/organizer_missing_passed_attribute_error.rb +21 -0
- data/lib/interaktor/error/organizer_success_attribute_missing_error.rb +20 -0
- data/lib/interaktor/organizer.rb +33 -7
- data/spec/integration_spec.rb +142 -71
- data/spec/{interactor → interaktor}/context_spec.rb +1 -1
- data/spec/{interactor → interaktor}/hooks_spec.rb +1 -1
- data/spec/interaktor/organizer_spec.rb +249 -0
- data/spec/interaktor_spec.rb +2 -2
- data/spec/spec_helper.rb +20 -0
- data/spec/support/helpers.rb +14 -0
- data/spec/support/lint.rb +403 -166
- metadata +31 -12
- data/.travis.yml +0 -14
- data/lib/interaktor/error/missing_attribute_error.rb +0 -5
- data/spec/interactor/organizer_spec.rb +0 -128
@@ -0,0 +1,249 @@
|
|
1
|
+
RSpec.describe Interaktor::Organizer do
|
2
|
+
it_behaves_like "lint", described_class
|
3
|
+
|
4
|
+
describe ".organize" do
|
5
|
+
let(:interaktor2) { instance_double(Interaktor) }
|
6
|
+
let(:interaktor3) { instance_double(Interaktor) }
|
7
|
+
|
8
|
+
it "sets interaktors given class arguments" do
|
9
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
10
|
+
|
11
|
+
expect {
|
12
|
+
organizer.organize(interaktor2, interaktor3)
|
13
|
+
}.to change(organizer, :organized)
|
14
|
+
.from([])
|
15
|
+
.to([interaktor2, interaktor3])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "sets interaktors given an array of classes" do
|
19
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
20
|
+
|
21
|
+
expect {
|
22
|
+
organizer.organize([interaktor2, interaktor3])
|
23
|
+
}.to change(organizer, :organized)
|
24
|
+
.from([])
|
25
|
+
.to([interaktor2, interaktor3])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "allows multiple organize calls" do
|
29
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
30
|
+
interaktor4 = instance_double(Interaktor)
|
31
|
+
|
32
|
+
expect {
|
33
|
+
organizer.organize(interaktor2, interaktor3)
|
34
|
+
organizer.organize(interaktor4)
|
35
|
+
}.to change(organizer, :organized)
|
36
|
+
.from([])
|
37
|
+
.to([interaktor2, interaktor3, interaktor4])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe ".organized" do
|
42
|
+
it "is empty by default" do
|
43
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
44
|
+
|
45
|
+
expect(organizer.organized).to eq([])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#call" do
|
50
|
+
it "calls each interaktor in order" do
|
51
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class) do
|
52
|
+
input { required(:foo) }
|
53
|
+
success { required(:foo) }
|
54
|
+
end
|
55
|
+
|
56
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1") do
|
57
|
+
input { required(:foo) }
|
58
|
+
success { required(:foo) }
|
59
|
+
|
60
|
+
def call
|
61
|
+
success!(foo: "bar")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor2") do
|
66
|
+
input { required(:foo) }
|
67
|
+
success { required(:foo) }
|
68
|
+
|
69
|
+
def call
|
70
|
+
success!(foo: "baz")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
interaktor3 = FakeInteraktor.build_interaktor("Interaktor3") do
|
75
|
+
input { required(:foo) }
|
76
|
+
success { required(:foo) }
|
77
|
+
|
78
|
+
def call
|
79
|
+
success!(foo: "wadus")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
allow(organizer).to receive(:organized) {
|
84
|
+
[interaktor1, interaktor2, interaktor3]
|
85
|
+
}
|
86
|
+
|
87
|
+
expect(interaktor1).to receive(:call!).once.ordered.and_call_original
|
88
|
+
expect(interaktor2).to receive(:call!).once.ordered.and_call_original
|
89
|
+
expect(interaktor3).to receive(:call!).once.ordered.and_call_original
|
90
|
+
|
91
|
+
result = organizer.call(foo: "asdf")
|
92
|
+
|
93
|
+
expect(result.foo).to eq "wadus"
|
94
|
+
end
|
95
|
+
|
96
|
+
it "calls each interaktor in order and passes success attributes" do
|
97
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class) { input { required(:foo) } }
|
98
|
+
|
99
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1") do
|
100
|
+
input { required(:foo) }
|
101
|
+
success { required(:bar) }
|
102
|
+
|
103
|
+
def call
|
104
|
+
success!(bar: "baz")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor1") do
|
109
|
+
input { required(:bar) }
|
110
|
+
|
111
|
+
def call
|
112
|
+
self.bar = "wadus"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
allow(organizer).to receive(:organized) {
|
117
|
+
[interaktor1, interaktor2]
|
118
|
+
}
|
119
|
+
|
120
|
+
expect(interaktor1).to receive(:call!).once.ordered.and_call_original
|
121
|
+
expect(interaktor2).to receive(:call!).once.ordered.and_call_original
|
122
|
+
|
123
|
+
result = organizer.call(foo: "asdf")
|
124
|
+
|
125
|
+
expect(result.bar).to eq "wadus"
|
126
|
+
end
|
127
|
+
|
128
|
+
it "allows an interaktor to accept required attributes from previous success attributes" do
|
129
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
130
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1") do
|
131
|
+
success { required(:foo) }
|
132
|
+
|
133
|
+
def call
|
134
|
+
success!(foo: "whatever")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor2") do
|
138
|
+
success { required(:bar) }
|
139
|
+
|
140
|
+
def call
|
141
|
+
success!(bar: "whatever")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
interaktor3 = FakeInteraktor.build_interaktor("Interaktor3") do
|
145
|
+
input do
|
146
|
+
required(:foo)
|
147
|
+
required(:bar)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
allow(organizer).to receive(:organized).and_return([interaktor1, interaktor2, interaktor3])
|
152
|
+
|
153
|
+
expect(interaktor1).to receive(:call!).once.ordered.and_call_original
|
154
|
+
expect(interaktor2).to receive(:call!).once.ordered.and_call_original
|
155
|
+
expect(interaktor3).to receive(:call!).once.ordered.and_call_original
|
156
|
+
|
157
|
+
organizer.call
|
158
|
+
end
|
159
|
+
|
160
|
+
it "allows an interaktor to accept required attributes from the original organizer" do
|
161
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class) do
|
162
|
+
input do
|
163
|
+
required(:foo)
|
164
|
+
required(:bar)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1")
|
168
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor2") { input { required(:foo) } }
|
169
|
+
interaktor3 = FakeInteraktor.build_interaktor("Interaktor3") { input { required(:bar) } }
|
170
|
+
|
171
|
+
allow(organizer).to receive(:organized).and_return([interaktor1, interaktor2, interaktor3])
|
172
|
+
|
173
|
+
expect(interaktor1).to receive(:call!).once.ordered.and_call_original
|
174
|
+
expect(interaktor2).to receive(:call!).once.ordered.and_call_original
|
175
|
+
expect(interaktor3).to receive(:call!).once.ordered.and_call_original
|
176
|
+
|
177
|
+
organizer.call(foo: "whatever", bar: "baz")
|
178
|
+
end
|
179
|
+
|
180
|
+
it "allows an interaktor to accept required attributes from the original organizer AND previous success attributes" do
|
181
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class) { input { required(:foo) } }
|
182
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1") do
|
183
|
+
success { required(:bar) }
|
184
|
+
|
185
|
+
def call
|
186
|
+
success!(bar: "whatever")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor2") do
|
190
|
+
success { required(:baz) }
|
191
|
+
|
192
|
+
def call
|
193
|
+
success!(baz: "whatever")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
interaktor3 = FakeInteraktor.build_interaktor("Interaktor3") do
|
197
|
+
input do
|
198
|
+
required(:foo)
|
199
|
+
required(:bar)
|
200
|
+
required(:baz)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
allow(organizer).to receive(:organized).and_return([interaktor1, interaktor2, interaktor3])
|
205
|
+
|
206
|
+
expect(interaktor1).to receive(:call!).once.ordered.and_call_original
|
207
|
+
expect(interaktor2).to receive(:call!).once.ordered.and_call_original
|
208
|
+
expect(interaktor3).to receive(:call!).once.ordered.and_call_original
|
209
|
+
|
210
|
+
organizer.call(foo: "whatever")
|
211
|
+
end
|
212
|
+
|
213
|
+
it "raises an exception if an interaktor requires an input attribute not provided by any previous interaktor" do
|
214
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class)
|
215
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1")
|
216
|
+
interaktor2 = FakeInteraktor.build_interaktor("Interaktor2") { input { required(:foo) } }
|
217
|
+
|
218
|
+
allow(organizer).to receive(:organized).and_return([interaktor1, interaktor2])
|
219
|
+
|
220
|
+
expect(interaktor1).not_to receive(:call!)
|
221
|
+
|
222
|
+
expect {
|
223
|
+
organizer.call
|
224
|
+
}.to raise_error(
|
225
|
+
an_instance_of(Interaktor::Error::OrganizerMissingPassedAttributeError)
|
226
|
+
.and(having_attributes(
|
227
|
+
attribute: :foo,
|
228
|
+
interaktor: interaktor2,
|
229
|
+
))
|
230
|
+
)
|
231
|
+
end
|
232
|
+
|
233
|
+
it "raises an exception if the organizer's last interaktor does not include the organizer's success attributes" do
|
234
|
+
organizer = FakeInteraktor.build_interaktor(type: described_class) { success { required(:final) } }
|
235
|
+
interaktor1 = FakeInteraktor.build_interaktor("Interaktor1")
|
236
|
+
|
237
|
+
allow(organizer).to receive(:organized).and_return([interaktor1])
|
238
|
+
|
239
|
+
expect(interaktor1).not_to receive(:call!)
|
240
|
+
|
241
|
+
expect {
|
242
|
+
organizer.call
|
243
|
+
}.to raise_error(
|
244
|
+
an_instance_of(Interaktor::Error::OrganizerSuccessAttributeMissingError)
|
245
|
+
.and(having_attributes(attribute: :final, interaktor: interaktor1))
|
246
|
+
)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/spec/interaktor_spec.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
describe Interaktor do
|
2
|
-
|
1
|
+
RSpec.describe Interaktor do
|
2
|
+
it_behaves_like "lint", described_class
|
3
3
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
4
|
+
end
|
5
|
+
|
6
|
+
config.mock_with :rspec do |mocks|
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
11
|
+
config.filter_run_when_matching :focus
|
12
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
13
|
+
config.disable_monkey_patching!
|
14
|
+
config.order = :random
|
15
|
+
|
16
|
+
config.default_formatter = "doc" if config.files_to_run.one?
|
17
|
+
|
18
|
+
Kernel.srand config.seed
|
19
|
+
end
|
20
|
+
|
1
21
|
require "simplecov"
|
2
22
|
SimpleCov.start do
|
3
23
|
enable_coverage :branch
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class FakeInteraktor
|
2
|
+
# @param name [String]
|
3
|
+
def self.build_interaktor(name = nil, type: Interaktor, &block)
|
4
|
+
name ||= "MyTest#{type}"
|
5
|
+
|
6
|
+
result = Class.new.include(type)
|
7
|
+
|
8
|
+
result.class_eval(&block) if block
|
9
|
+
result.define_singleton_method(:inspect) { name.to_s }
|
10
|
+
result.define_singleton_method(:to_s) { inspect }
|
11
|
+
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
data/spec/support/lint.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
shared_examples "lint" do
|
2
|
-
let(:interaktor) { Class.new.include(described_class) }
|
3
|
-
|
1
|
+
RSpec.shared_examples "lint" do |interaktor_class|
|
4
2
|
describe ".call" do
|
5
|
-
let(:instance) { instance_double(interaktor) }
|
6
|
-
|
7
3
|
it "calls an instance" do
|
4
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
8
5
|
expect(interaktor).to receive(:new).once.with({}).and_call_original
|
9
6
|
|
10
7
|
result = interaktor.call
|
@@ -12,13 +9,17 @@ shared_examples "lint" do
|
|
12
9
|
expect(result.success?).to be true
|
13
10
|
end
|
14
11
|
|
15
|
-
it "
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
it "removes unknown provided attributes" do
|
13
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
14
|
+
|
15
|
+
result = interaktor.call(baz: "wadus")
|
16
|
+
|
17
|
+
expect(result.baz).to be nil
|
19
18
|
end
|
20
19
|
|
21
20
|
it "fails when a non-hash or non-context argument is passed" do
|
21
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
22
|
+
|
22
23
|
expect {
|
23
24
|
interaktor.call("foo")
|
24
25
|
}.to raise_error(ArgumentError, /Expected a hash argument/)
|
@@ -26,9 +27,8 @@ shared_examples "lint" do
|
|
26
27
|
end
|
27
28
|
|
28
29
|
describe ".call!" do
|
29
|
-
let(:instance) { instance_double(interaktor) }
|
30
|
-
|
31
30
|
it "calls an instance" do
|
31
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
32
32
|
expect(interaktor).to receive(:new).once.with({}).and_call_original
|
33
33
|
|
34
34
|
result = interaktor.call!
|
@@ -36,13 +36,17 @@ shared_examples "lint" do
|
|
36
36
|
expect(result.success?).to be true
|
37
37
|
end
|
38
38
|
|
39
|
-
it "
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
it "removes unknown provided attributes" do
|
40
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
41
|
+
|
42
|
+
result = interaktor.call!(baz: "wadus")
|
43
|
+
|
44
|
+
expect(result.baz).to be nil
|
43
45
|
end
|
44
46
|
|
45
47
|
it "fails when a non-hash or non-context argument is passed" do
|
48
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
49
|
+
|
46
50
|
expect {
|
47
51
|
interaktor.call!("foo")
|
48
52
|
}.to raise_error(ArgumentError, /Expected a hash argument/)
|
@@ -50,15 +54,17 @@ shared_examples "lint" do
|
|
50
54
|
end
|
51
55
|
|
52
56
|
describe "#run" do
|
53
|
-
let(:instance) { interaktor.new }
|
54
|
-
|
55
57
|
it "runs the interaktor" do
|
58
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
59
|
+
|
56
60
|
expect(instance).to receive(:run!).once.with(no_args)
|
57
61
|
|
58
62
|
instance.run
|
59
63
|
end
|
60
64
|
|
61
65
|
it "rescues failure" do
|
66
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
67
|
+
|
62
68
|
expect(instance).to receive(:run!).and_raise(Interaktor::Failure)
|
63
69
|
|
64
70
|
expect {
|
@@ -67,6 +73,8 @@ shared_examples "lint" do
|
|
67
73
|
end
|
68
74
|
|
69
75
|
it "raises other errors" do
|
76
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
77
|
+
|
70
78
|
expect(instance).to receive(:run!).and_raise("foo")
|
71
79
|
|
72
80
|
expect {
|
@@ -76,15 +84,17 @@ shared_examples "lint" do
|
|
76
84
|
end
|
77
85
|
|
78
86
|
describe "#run!" do
|
79
|
-
let(:instance) { interaktor.new }
|
80
|
-
|
81
87
|
it "calls the interaktor" do
|
88
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
89
|
+
|
82
90
|
expect(instance).to receive(:call).once.with(no_args)
|
83
91
|
|
84
92
|
instance.run!
|
85
93
|
end
|
86
94
|
|
87
95
|
it "raises failure" do
|
96
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
97
|
+
|
88
98
|
expect(instance).to receive(:run!).and_raise(Interaktor::Failure)
|
89
99
|
|
90
100
|
expect {
|
@@ -93,6 +103,8 @@ shared_examples "lint" do
|
|
93
103
|
end
|
94
104
|
|
95
105
|
it "raises other errors" do
|
106
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
107
|
+
|
96
108
|
expect(instance).to receive(:run!).and_raise("foo")
|
97
109
|
|
98
110
|
expect {
|
@@ -102,9 +114,9 @@ shared_examples "lint" do
|
|
102
114
|
end
|
103
115
|
|
104
116
|
describe "#call" do
|
105
|
-
let(:instance) { interaktor.new }
|
106
|
-
|
107
117
|
it "exists" do
|
118
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
119
|
+
|
108
120
|
expect(instance).to respond_to(:call)
|
109
121
|
expect { instance.call }.not_to raise_error
|
110
122
|
expect { instance.method(:call) }.not_to raise_error
|
@@ -112,275 +124,500 @@ shared_examples "lint" do
|
|
112
124
|
end
|
113
125
|
|
114
126
|
describe "#rollback" do
|
115
|
-
let(:instance) { interaktor.new }
|
116
|
-
|
117
127
|
it "exists" do
|
128
|
+
instance = FakeInteraktor.build_interaktor(type: interaktor_class).new
|
129
|
+
|
118
130
|
expect(instance).to respond_to(:rollback)
|
119
131
|
expect { instance.rollback }.not_to raise_error
|
120
132
|
expect { instance.method(:rollback) }.not_to raise_error
|
121
133
|
end
|
122
134
|
end
|
123
135
|
|
124
|
-
describe "
|
125
|
-
|
136
|
+
describe "#required_input_attributes" do
|
137
|
+
it "returns the attributes" do
|
138
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
139
|
+
input do
|
140
|
+
required(:foo)
|
141
|
+
required(:bar)
|
142
|
+
optional(:baz)
|
143
|
+
end
|
144
|
+
end
|
126
145
|
|
127
|
-
|
128
|
-
|
146
|
+
expect(interaktor.required_input_attributes).to contain_exactly(:foo, :bar)
|
147
|
+
end
|
129
148
|
|
130
|
-
|
149
|
+
it "returns empty array when not defined" do
|
150
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
131
151
|
|
132
|
-
|
152
|
+
expect(interaktor.required_input_attributes).to be_empty
|
153
|
+
end
|
154
|
+
end
|
133
155
|
|
134
|
-
|
135
|
-
|
156
|
+
describe "#optional_input_attributes" do
|
157
|
+
it "returns the attributes" do
|
158
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
159
|
+
input do
|
160
|
+
required(:foo)
|
161
|
+
required(:bar)
|
162
|
+
optional(:baz)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
expect(interaktor.optional_input_attributes).to contain_exactly(:baz)
|
136
167
|
end
|
137
168
|
|
138
|
-
it "
|
139
|
-
interaktor
|
169
|
+
it "returns empty array when not defined" do
|
170
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
140
171
|
|
141
|
-
expect(interaktor).to
|
172
|
+
expect(interaktor.optional_input_attributes).to be_empty
|
173
|
+
end
|
174
|
+
end
|
142
175
|
|
143
|
-
|
144
|
-
|
176
|
+
describe "#input_attributes" do
|
177
|
+
it "returns both required and optional attributes" do
|
178
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
179
|
+
input do
|
180
|
+
required(:foo)
|
181
|
+
required(:bar)
|
182
|
+
optional(:baz)
|
183
|
+
end
|
145
184
|
end
|
146
185
|
|
147
|
-
|
186
|
+
expect(interaktor.input_attributes).to contain_exactly(:foo, :bar, :baz)
|
187
|
+
end
|
188
|
+
end
|
148
189
|
|
149
|
-
|
150
|
-
|
190
|
+
describe "#required_success_attributes" do
|
191
|
+
it "returns the attributes" do
|
192
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
193
|
+
success do
|
194
|
+
required(:foo)
|
195
|
+
required(:bar)
|
196
|
+
optional(:baz)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
expect(interaktor.required_success_attributes).to contain_exactly(:foo, :bar)
|
151
201
|
end
|
152
202
|
|
153
|
-
it "
|
154
|
-
interaktor
|
203
|
+
it "returns empty array when not defined" do
|
204
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
155
205
|
|
156
|
-
expect
|
157
|
-
interaktor.call!
|
158
|
-
}.to raise_error(an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar]))
|
206
|
+
expect(interaktor.required_success_attributes).to be_empty
|
159
207
|
end
|
208
|
+
end
|
160
209
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
210
|
+
describe "#optional_success_attributes" do
|
211
|
+
it "returns the attributes" do
|
212
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
213
|
+
success do
|
214
|
+
required(:foo)
|
215
|
+
required(:bar)
|
216
|
+
optional(:baz)
|
217
|
+
end
|
166
218
|
end
|
219
|
+
|
220
|
+
expect(interaktor.optional_success_attributes).to contain_exactly(:baz)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "returns empty array when not defined" do
|
224
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
225
|
+
|
226
|
+
expect(interaktor.optional_success_attributes).to be_empty
|
167
227
|
end
|
168
228
|
end
|
169
229
|
|
170
|
-
describe "
|
171
|
-
|
230
|
+
describe "#success_attributes" do
|
231
|
+
it "returns both required and optional attributes" do
|
232
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
233
|
+
success do
|
234
|
+
required(:foo)
|
235
|
+
required(:bar)
|
236
|
+
optional(:baz)
|
237
|
+
end
|
238
|
+
end
|
172
239
|
|
173
|
-
|
174
|
-
|
240
|
+
expect(interaktor.success_attributes).to contain_exactly(:foo, :bar, :baz)
|
241
|
+
end
|
242
|
+
end
|
175
243
|
|
176
|
-
|
244
|
+
describe "#required_failure_attributes" do
|
245
|
+
it "returns the attributes" do
|
246
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
247
|
+
failure do
|
248
|
+
required(:foo)
|
249
|
+
required(:bar)
|
250
|
+
optional(:baz)
|
251
|
+
end
|
252
|
+
end
|
177
253
|
|
178
|
-
|
254
|
+
expect(interaktor.required_failure_attributes).to contain_exactly(:foo, :bar)
|
255
|
+
end
|
179
256
|
|
180
|
-
|
181
|
-
|
257
|
+
it "returns empty array when not defined" do
|
258
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
259
|
+
|
260
|
+
expect(interaktor.required_failure_attributes).to be_empty
|
182
261
|
end
|
262
|
+
end
|
183
263
|
|
184
|
-
|
185
|
-
|
264
|
+
describe "#optional_failure_attributes" do
|
265
|
+
it "returns the attributes" do
|
266
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
267
|
+
failure do
|
268
|
+
required(:foo)
|
269
|
+
required(:bar)
|
270
|
+
optional(:baz)
|
271
|
+
end
|
272
|
+
end
|
186
273
|
|
187
|
-
expect(interaktor).to
|
274
|
+
expect(interaktor.optional_failure_attributes).to contain_exactly(:baz)
|
275
|
+
end
|
188
276
|
|
189
|
-
|
277
|
+
it "returns empty array when not defined" do
|
278
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
|
190
279
|
|
191
|
-
expect(
|
192
|
-
expect(result.bar).to be_nil
|
280
|
+
expect(interaktor.optional_failure_attributes).to be_empty
|
193
281
|
end
|
282
|
+
end
|
194
283
|
|
195
|
-
|
196
|
-
|
284
|
+
describe "#failure_attributes" do
|
285
|
+
it "returns both required and optional attributes" do
|
286
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
287
|
+
failure do
|
288
|
+
required(:foo)
|
289
|
+
required(:bar)
|
290
|
+
optional(:baz)
|
291
|
+
end
|
292
|
+
end
|
197
293
|
|
198
|
-
expect(interaktor).to
|
294
|
+
expect(interaktor.failure_attributes).to contain_exactly(:foo, :bar, :baz)
|
295
|
+
end
|
296
|
+
end
|
199
297
|
|
200
|
-
|
201
|
-
|
298
|
+
describe "input attributes" do
|
299
|
+
it "accepts a schema object" do
|
300
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
301
|
+
input(Dry::Schema.Params { required(:bar).filled(:string) })
|
202
302
|
end
|
203
303
|
|
304
|
+
expect(interaktor.input_schema).to be_a Dry::Schema::Params
|
305
|
+
expect(interaktor.input_schema.info).to eq(
|
306
|
+
keys: { bar: { required: true, type: "string" } },
|
307
|
+
)
|
308
|
+
|
309
|
+
expect(interaktor.required_input_attributes).to contain_exactly(:bar)
|
310
|
+
|
204
311
|
result = interaktor.call(bar: "baz")
|
205
312
|
|
206
313
|
expect(result.success?).to be true
|
207
|
-
expect(result.bar).to eq "
|
314
|
+
expect(result.bar).to eq "baz"
|
208
315
|
end
|
209
316
|
|
210
|
-
it "
|
211
|
-
interaktor
|
317
|
+
it "accepts a schema definition block" do
|
318
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
319
|
+
input { required(:bar).filled(:string) }
|
320
|
+
end
|
212
321
|
|
213
|
-
expect(interaktor).to
|
322
|
+
expect(interaktor.input_schema).to be_a Dry::Schema::Params
|
323
|
+
expect(interaktor.input_schema.info).to eq(
|
324
|
+
keys: { bar: { required: true, type: "string" } },
|
325
|
+
)
|
326
|
+
|
327
|
+
expect(interaktor.required_input_attributes).to contain_exactly(:bar)
|
328
|
+
|
329
|
+
result = interaktor.call(bar: "baz")
|
214
330
|
|
215
|
-
|
216
|
-
|
331
|
+
expect(result.success?).to be true
|
332
|
+
expect(result.bar).to eq "baz"
|
333
|
+
end
|
334
|
+
|
335
|
+
it "raises an exception when the attribute is required and not provided" do
|
336
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
337
|
+
input { required(:bar).filled(:string) }
|
217
338
|
end
|
218
339
|
|
219
|
-
expect {
|
220
|
-
|
221
|
-
|
222
|
-
|
340
|
+
expect {
|
341
|
+
interaktor.call!
|
342
|
+
}.to raise_error(
|
343
|
+
an_instance_of(Interaktor::Error::AttributeSchemaValidationError).and(
|
344
|
+
having_attributes(
|
345
|
+
interaktor: interaktor,
|
346
|
+
validation_errors: { bar: ["is missing"] },
|
347
|
+
)
|
223
348
|
)
|
224
349
|
)
|
225
350
|
end
|
226
351
|
|
227
|
-
|
228
|
-
|
229
|
-
|
352
|
+
it "removes unknown provided attributes" do
|
353
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
354
|
+
input { required(:bar).filled(:string) }
|
355
|
+
end
|
230
356
|
|
231
|
-
|
357
|
+
result = interaktor.call!(bar: "baz", foo: "unexpected")
|
232
358
|
|
233
|
-
|
359
|
+
expect(result.foo).to be nil
|
360
|
+
end
|
234
361
|
|
235
|
-
|
236
|
-
|
362
|
+
it "allows provided optional attributes" do
|
363
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
364
|
+
input { optional(:bar).filled(:string) }
|
237
365
|
end
|
238
366
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
367
|
+
expect(interaktor.optional_input_attributes).to contain_exactly(:bar)
|
368
|
+
|
369
|
+
result = interaktor.call(bar: "baz")
|
370
|
+
|
371
|
+
expect(result.success?).to be true
|
372
|
+
expect(result.bar).to eq "baz"
|
373
|
+
end
|
374
|
+
|
375
|
+
it "allows missing optional attributes" do
|
376
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
377
|
+
input { optional(:bar).filled(:string) }
|
243
378
|
end
|
379
|
+
|
380
|
+
expect(interaktor.optional_input_attributes).to contain_exactly(:bar)
|
381
|
+
|
382
|
+
result = interaktor.call
|
383
|
+
|
384
|
+
expect(result.success?).to be true
|
385
|
+
expect(result.bar).to be_nil
|
244
386
|
end
|
245
|
-
end
|
246
387
|
|
247
|
-
|
248
|
-
|
249
|
-
|
388
|
+
it "creates attribute getters and setters" do
|
389
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
390
|
+
input do
|
391
|
+
required(:foo).filled(:string)
|
392
|
+
optional(:bar).filled(:string)
|
393
|
+
end
|
250
394
|
|
251
|
-
|
395
|
+
def call
|
396
|
+
foo
|
397
|
+
bar
|
252
398
|
|
253
|
-
|
254
|
-
|
399
|
+
self.foo = "one"
|
400
|
+
self.bar = "two"
|
401
|
+
end
|
255
402
|
end
|
256
403
|
|
257
|
-
result = interaktor.call
|
404
|
+
result = interaktor.call(foo: "bar", bar: "baz")
|
258
405
|
|
259
406
|
expect(result.success?).to be true
|
260
|
-
expect(result.
|
407
|
+
expect(result.foo).to eq "one"
|
408
|
+
expect(result.bar).to eq "two"
|
261
409
|
end
|
410
|
+
end
|
262
411
|
|
263
|
-
|
264
|
-
|
412
|
+
describe "success attributes" do
|
413
|
+
it "accepts a schema object" do
|
414
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
415
|
+
success(Dry::Schema.Params { required(:bar).filled(:string) })
|
265
416
|
|
266
|
-
|
417
|
+
def call
|
418
|
+
success!(bar: "baz")
|
419
|
+
end
|
420
|
+
end
|
267
421
|
|
268
|
-
expect
|
269
|
-
|
270
|
-
|
271
|
-
)
|
422
|
+
expect(interaktor.success_schema).to be_a Dry::Schema::Params
|
423
|
+
expect(interaktor.success_schema.info).to eq(
|
424
|
+
keys: { bar: { required: true, type: "string" } },
|
272
425
|
)
|
273
|
-
end
|
274
426
|
|
275
|
-
|
276
|
-
interaktor.class_eval { success :bar }
|
427
|
+
expect(interaktor.required_success_attributes).to contain_exactly(:bar)
|
277
428
|
|
278
|
-
|
429
|
+
result = interaktor.call
|
430
|
+
|
431
|
+
expect(result.success?).to be true
|
432
|
+
expect(result.bar).to eq "baz"
|
433
|
+
end
|
434
|
+
|
435
|
+
it "accepts a schema definition block" do
|
436
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
437
|
+
success { required(:bar).filled(:string) }
|
279
438
|
|
280
|
-
|
281
|
-
|
439
|
+
def call
|
440
|
+
success!(bar: "baz")
|
441
|
+
end
|
282
442
|
end
|
283
443
|
|
284
|
-
expect
|
285
|
-
|
286
|
-
|
287
|
-
)
|
444
|
+
expect(interaktor.success_schema).to be_a Dry::Schema::Params
|
445
|
+
expect(interaktor.success_schema.info).to eq(
|
446
|
+
keys: { bar: { required: true, type: "string" } },
|
288
447
|
)
|
289
|
-
end
|
290
448
|
|
291
|
-
|
292
|
-
interaktor.class_eval { success :bar }
|
449
|
+
expect(interaktor.required_success_attributes).to contain_exactly(:bar)
|
293
450
|
|
294
|
-
|
451
|
+
result = interaktor.call
|
452
|
+
|
453
|
+
expect(result.success?).to be true
|
454
|
+
expect(result.bar).to eq "baz"
|
455
|
+
end
|
295
456
|
|
296
|
-
|
297
|
-
|
457
|
+
it "raises an exception when the attribute is required and not provided" do
|
458
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
459
|
+
success { required(:bar).filled(:string) }
|
460
|
+
|
461
|
+
def call
|
462
|
+
success!
|
463
|
+
end
|
298
464
|
end
|
299
465
|
|
300
|
-
expect {
|
301
|
-
|
302
|
-
|
466
|
+
expect {
|
467
|
+
result = interaktor.call
|
468
|
+
}.to raise_error(
|
469
|
+
an_instance_of(Interaktor::Error::AttributeSchemaValidationError).and(
|
470
|
+
having_attributes(
|
471
|
+
interaktor: interaktor,
|
472
|
+
validation_errors: { bar: ["is missing"] },
|
473
|
+
)
|
303
474
|
)
|
304
475
|
)
|
305
476
|
end
|
306
477
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
478
|
+
it "removes unknown provided attributes" do
|
479
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
480
|
+
success { required(:bar).filled(:string) }
|
481
|
+
|
482
|
+
def call
|
483
|
+
success!(bar: "baz", foo: "wadus")
|
484
|
+
end
|
312
485
|
end
|
313
|
-
end
|
314
|
-
end
|
315
486
|
|
316
|
-
|
317
|
-
it "fails when the correct attributes are provided" do
|
318
|
-
interaktor.class_eval { failure :bar }
|
487
|
+
result = interaktor.call
|
319
488
|
|
320
|
-
expect(
|
489
|
+
expect(result.foo).to be nil
|
490
|
+
end
|
321
491
|
|
322
|
-
|
323
|
-
|
492
|
+
it "allows missing optional attributes" do
|
493
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
494
|
+
success { optional(:bar).filled(:string) }
|
495
|
+
|
496
|
+
def call
|
497
|
+
success!
|
498
|
+
end
|
324
499
|
end
|
325
500
|
|
501
|
+
expect(interaktor.optional_success_attributes).to contain_exactly(:bar)
|
502
|
+
|
326
503
|
result = interaktor.call
|
327
504
|
|
328
|
-
expect(result.success?).to be
|
329
|
-
expect(result.bar).to
|
505
|
+
expect(result.success?).to be true
|
506
|
+
expect(result.bar).to be nil
|
330
507
|
end
|
331
508
|
|
332
|
-
it "raises an exception when the correct attributes are not provided" do
|
333
|
-
interaktor
|
334
|
-
|
335
|
-
expect(interaktor).to receive(:new).once.with({}).and_call_original
|
509
|
+
it "raises an exception when the correct attributes are not provided because #success! is not called" do
|
510
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
511
|
+
success { required(:bar).filled(:string) }
|
336
512
|
|
337
|
-
|
338
|
-
|
513
|
+
def call
|
514
|
+
# do nothing and succeed implicitly
|
515
|
+
end
|
339
516
|
end
|
340
517
|
|
341
518
|
expect { interaktor.call }.to(
|
342
519
|
raise_error(
|
343
|
-
an_instance_of(Interaktor::Error::
|
520
|
+
an_instance_of(Interaktor::Error::MissingExplicitSuccessError).and having_attributes(attributes: [:bar])
|
344
521
|
)
|
345
522
|
)
|
346
523
|
end
|
524
|
+
end
|
347
525
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
expect(interaktor).to receive(:new).once.with({}).and_call_original
|
526
|
+
describe "failure attributes" do
|
527
|
+
it "accepts a schema object" do
|
528
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
529
|
+
failure(Dry::Schema.Params { required(:bar).filled(:string) })
|
353
530
|
|
354
|
-
|
531
|
+
def call
|
355
532
|
fail!(bar: "baz")
|
356
533
|
end
|
534
|
+
end
|
535
|
+
|
536
|
+
expect(interaktor.failure_schema).to be_a Dry::Schema::Params
|
537
|
+
expect(interaktor.failure_schema.info).to eq(
|
538
|
+
keys: { bar: { required: true, type: "string" } },
|
539
|
+
)
|
540
|
+
|
541
|
+
expect(interaktor.required_failure_attributes).to contain_exactly(:bar)
|
542
|
+
|
543
|
+
result = interaktor.call
|
544
|
+
|
545
|
+
expect(result.failure?).to be true
|
546
|
+
expect(result.bar).to eq "baz"
|
547
|
+
end
|
548
|
+
|
549
|
+
it "accepts a schema definition block" do
|
550
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
551
|
+
failure { required(:bar).filled(:string) }
|
357
552
|
|
358
|
-
|
553
|
+
def call
|
554
|
+
fail!(bar: "baz")
|
555
|
+
end
|
359
556
|
end
|
360
557
|
|
361
|
-
|
362
|
-
|
558
|
+
expect(interaktor.failure_schema).to be_a Dry::Schema::Params
|
559
|
+
expect(interaktor.failure_schema.info).to eq(
|
560
|
+
keys: { bar: { required: true, type: "string" } },
|
561
|
+
)
|
363
562
|
|
364
|
-
|
563
|
+
expect(interaktor.required_failure_attributes).to contain_exactly(:bar)
|
365
564
|
|
366
|
-
|
367
|
-
|
565
|
+
result = interaktor.call
|
566
|
+
|
567
|
+
expect(result.failure?).to be true
|
568
|
+
expect(result.bar).to eq "baz"
|
569
|
+
end
|
570
|
+
|
571
|
+
it "raises an exception when the attribute is required and not provided" do
|
572
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
573
|
+
failure { required(:bar).filled(:string) }
|
574
|
+
|
575
|
+
def call
|
576
|
+
fail!
|
368
577
|
end
|
578
|
+
end
|
369
579
|
|
370
|
-
|
371
|
-
|
372
|
-
|
580
|
+
expect {
|
581
|
+
result = interaktor.call
|
582
|
+
}.to raise_error(
|
583
|
+
an_instance_of(Interaktor::Error::AttributeSchemaValidationError).and(
|
584
|
+
having_attributes(
|
585
|
+
interaktor: interaktor,
|
586
|
+
validation_errors: { bar: ["is missing"] },
|
373
587
|
)
|
374
588
|
)
|
589
|
+
)
|
590
|
+
end
|
591
|
+
|
592
|
+
it "removes unknown provided attributes" do
|
593
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
594
|
+
failure { required(:bar).filled(:string) }
|
595
|
+
|
596
|
+
def call
|
597
|
+
fail!(bar: "baz", foo: "wadus")
|
598
|
+
end
|
375
599
|
end
|
600
|
+
|
601
|
+
result = interaktor.call
|
602
|
+
|
603
|
+
expect(result.foo).to be nil
|
376
604
|
end
|
377
605
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
606
|
+
it "allows missing optional attributes" do
|
607
|
+
interaktor = FakeInteraktor.build_interaktor(type: interaktor_class) do
|
608
|
+
failure { optional(:bar).filled(:string) }
|
609
|
+
|
610
|
+
def call
|
611
|
+
fail!
|
612
|
+
end
|
383
613
|
end
|
614
|
+
|
615
|
+
expect(interaktor.optional_failure_attributes).to contain_exactly(:bar)
|
616
|
+
|
617
|
+
result = interaktor.call
|
618
|
+
|
619
|
+
expect(result.failure?).to be true
|
620
|
+
expect(result.bar).to be nil
|
384
621
|
end
|
385
622
|
end
|
386
623
|
end
|