interaktor 0.2.0 → 0.4.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 +5 -4
- data/.github/workflows/tests.yml +5 -5
- data/.gitignore +3 -0
- data/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.ci +6 -0
- data/README.md +65 -29
- data/interaktor.gemspec +3 -2
- 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/organizer_missing_passed_attribute_error.rb +21 -0
- data/lib/interaktor/error/organizer_success_attribute_missing_error.rb +20 -0
- data/lib/interaktor/hooks.rb +16 -0
- data/lib/interaktor/organizer.rb +33 -7
- data/lib/interaktor.rb +11 -16
- data/spec/integration_spec.rb +142 -71
- data/spec/{interactor → interaktor}/context_spec.rb +1 -1
- data/spec/{interactor → interaktor}/hooks_spec.rb +100 -2
- 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 -17
- data/.travis.yml +0 -14
- data/lib/interaktor/error/disallowed_attribute_assignment_error.rb +0 -9
- data/lib/interaktor/error/missing_attribute_error.rb +0 -5
- data/lib/interaktor/error/option_error.rb +0 -16
- data/lib/interaktor/error/unknown_attribute_error.rb +0 -5
- data/lib/interaktor/error/unknown_option_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
|