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.
@@ -1,5 +1,5 @@
1
1
  module Interaktor
2
- describe Context do
2
+ RSpec.describe Context do
3
3
  describe ".build" do
4
4
  it "converts the given hash to a context" do
5
5
  context = described_class.build(foo: "bar")
@@ -1,7 +1,7 @@
1
1
  # rubocop:disable RSpec/ScatteredSetup
2
2
 
3
3
  module Interaktor
4
- describe Hooks do
4
+ RSpec.describe Hooks do
5
5
  describe "#with_hooks" do
6
6
  def build_hooked(&block)
7
7
  hooked = Class.new.send(:include, Interaktor::Hooks)
@@ -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
@@ -1,3 +1,3 @@
1
- describe Interaktor do
2
- include_examples "lint"
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 "fails when an unknown attribute is provided" do
16
- expect {
17
- interaktor.call(baz: "wadus")
18
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownAttributeError).and having_attributes(attributes: [:baz]))
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 "fails when an unknown attribute is provided" do
40
- expect {
41
- interaktor.call!(baz: "wadus")
42
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownAttributeError).and having_attributes(attributes: [:baz]))
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 "required attributes" do
125
- let(:instance) { instance_double(interaktor) }
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
- it "initializes successfully when the attribute is provided" do
128
- interaktor.class_eval { required :bar }
146
+ expect(interaktor.required_input_attributes).to contain_exactly(:foo, :bar)
147
+ end
129
148
 
130
- expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
149
+ it "returns empty array when not defined" do
150
+ interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
131
151
 
132
- result = interaktor.call(bar: "baz")
152
+ expect(interaktor.required_input_attributes).to be_empty
153
+ end
154
+ end
133
155
 
134
- expect(result.success?).to be true
135
- expect(result.bar).to eq "baz"
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 "creates a value setter" do
139
- interaktor.class_eval { required :bar }
169
+ it "returns empty array when not defined" do
170
+ interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
140
171
 
141
- expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
172
+ expect(interaktor.optional_input_attributes).to be_empty
173
+ end
174
+ end
142
175
 
143
- interaktor.define_method(:call) do
144
- self.bar = "wadus"
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
- result = interaktor.call(bar: "baz")
186
+ expect(interaktor.input_attributes).to contain_exactly(:foo, :bar, :baz)
187
+ end
188
+ end
148
189
 
149
- expect(result.success?).to be true
150
- expect(result.bar).to eq "wadus"
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 "raises an exception when the attribute is not provided" do
154
- interaktor.class_eval { required :bar }
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
- describe "options" do
162
- it "raises an exception when an unknown option is provided" do
163
- expect {
164
- interaktor.class_eval { required :bar, unknown: true }
165
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownOptionError).and having_attributes(options: { unknown: true }))
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 "optional attributes" do
171
- let(:instance) { instance_double(interaktor) }
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
- it "initializes successfully when the attribute is provided" do
174
- interaktor.class_eval { optional :bar }
240
+ expect(interaktor.success_attributes).to contain_exactly(:foo, :bar, :baz)
241
+ end
242
+ end
175
243
 
176
- expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
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
- result = interaktor.call(bar: "baz")
254
+ expect(interaktor.required_failure_attributes).to contain_exactly(:foo, :bar)
255
+ end
179
256
 
180
- expect(result.success?).to be true
181
- expect(result.bar).to eq "baz"
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
- it "initializes successfully when the attribute is not provided" do
185
- interaktor.class_eval { optional :bar }
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 receive(:new).once.with({}).and_call_original
274
+ expect(interaktor.optional_failure_attributes).to contain_exactly(:baz)
275
+ end
188
276
 
189
- result = interaktor.call
277
+ it "returns empty array when not defined" do
278
+ interaktor = FakeInteraktor.build_interaktor(type: interaktor_class)
190
279
 
191
- expect(result.success?).to be true
192
- expect(result.bar).to be_nil
280
+ expect(interaktor.optional_failure_attributes).to be_empty
193
281
  end
282
+ end
194
283
 
195
- it "creates a value setter" do
196
- interaktor.class_eval { optional :bar }
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 receive(:new).once.with(bar: "baz").and_call_original
294
+ expect(interaktor.failure_attributes).to contain_exactly(:foo, :bar, :baz)
295
+ end
296
+ end
199
297
 
200
- interaktor.define_method(:call) do
201
- self.bar = "wadus"
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 "wadus"
314
+ expect(result.bar).to eq "baz"
208
315
  end
209
316
 
210
- it "raises an exception when assigning a value to an optional parameter which was not originally provided" do
211
- interaktor.class_eval { optional :bar }
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 receive(:new).once.with({}).and_call_original
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
- interaktor.define_method(:call) do
216
- self.bar = "baz"
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 { interaktor.call }.to(
220
- raise_error(
221
- an_instance_of(Interaktor::Error::DisallowedAttributeAssignmentError)
222
- .and(having_attributes(attributes: [:bar]))
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
- describe "options" do
228
- it "accepts a default value for the attribute" do
229
- interaktor.class_eval { optional :bar, default: "baz" }
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
- expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
357
+ result = interaktor.call!(bar: "baz", foo: "unexpected")
232
358
 
233
- result = interaktor.call
359
+ expect(result.foo).to be nil
360
+ end
234
361
 
235
- expect(result.success?).to be true
236
- expect(result.bar).to eq "baz"
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
- it "raises an exception when an unknown option is provided" do
240
- expect {
241
- interaktor.class_eval { optional :bar, unknown: true }
242
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownOptionError).and having_attributes(options: { unknown: true }))
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
- describe "success attributes" do
248
- it "succeeds when the correct attributes are provided" do
249
- interaktor.class_eval { success :bar }
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
- expect(interaktor).to receive(:new).once.with({}).and_call_original
395
+ def call
396
+ foo
397
+ bar
252
398
 
253
- interaktor.define_method(:call) do
254
- success!(bar: "baz")
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.bar).to eq "baz"
407
+ expect(result.foo).to eq "one"
408
+ expect(result.bar).to eq "two"
261
409
  end
410
+ end
262
411
 
263
- it "raises an exception when the correct attributes are not provided because #success! is not called" do
264
- interaktor.class_eval { success :bar }
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
- expect(interaktor).to receive(:new).once.with({}).and_call_original
417
+ def call
418
+ success!(bar: "baz")
419
+ end
420
+ end
267
421
 
268
- expect { interaktor.call }.to(
269
- raise_error(
270
- an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
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
- it "raises an exception when the correct attributes are not provided in the call to #success!" do
276
- interaktor.class_eval { success :bar }
427
+ expect(interaktor.required_success_attributes).to contain_exactly(:bar)
277
428
 
278
- expect(interaktor).to receive(:new).once.with({}).and_call_original
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
- interaktor.define_method(:call) do
281
- success!({})
439
+ def call
440
+ success!(bar: "baz")
441
+ end
282
442
  end
283
443
 
284
- expect { interaktor.call }.to(
285
- raise_error(
286
- an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
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
- it "raises an exception when unknown attributes are provided" do
292
- interaktor.class_eval { success :bar }
449
+ expect(interaktor.required_success_attributes).to contain_exactly(:bar)
293
450
 
294
- expect(interaktor).to receive(:new).once.with({}).and_call_original
451
+ result = interaktor.call
452
+
453
+ expect(result.success?).to be true
454
+ expect(result.bar).to eq "baz"
455
+ end
295
456
 
296
- interaktor.define_method(:call) do
297
- success!(bar: "baz", baz: "wadus")
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 { interaktor.call }.to(
301
- raise_error(
302
- an_instance_of(Interaktor::Error::UnknownAttributeError).and having_attributes(attributes: [:baz])
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
- describe "options" do
308
- it "raises an exception when an unknown option is provided" do
309
- expect {
310
- interaktor.class_eval { success :bar, unknown: true }
311
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownOptionError).and having_attributes(options: { unknown: true }))
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
- describe "failure attributes" do
317
- it "fails when the correct attributes are provided" do
318
- interaktor.class_eval { failure :bar }
487
+ result = interaktor.call
319
488
 
320
- expect(interaktor).to receive(:new).once.with({}).and_call_original
489
+ expect(result.foo).to be nil
490
+ end
321
491
 
322
- interaktor.define_method(:call) do
323
- fail!(bar: "baz")
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 false
329
- expect(result.bar).to eq "baz"
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.class_eval { failure :bar }
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
- interaktor.define_method(:call) do
338
- fail!({})
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::MissingAttributeError).and having_attributes(attributes: [:bar])
520
+ an_instance_of(Interaktor::Error::MissingExplicitSuccessError).and having_attributes(attributes: [:bar])
344
521
  )
345
522
  )
346
523
  end
524
+ end
347
525
 
348
- context "when the interaktor is called with #call!" do
349
- it "raises an exception when the correct attributes are provided" do
350
- interaktor.class_eval { failure :bar }
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
- interaktor.define_method(:call) do
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
- expect { interaktor.call! }.to raise_error(Interaktor::Failure)
553
+ def call
554
+ fail!(bar: "baz")
555
+ end
359
556
  end
360
557
 
361
- it "raises an exception when the correct attributes are not provided" do
362
- interaktor.class_eval { failure :bar }
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
- expect(interaktor).to receive(:new).once.with({}).and_call_original
563
+ expect(interaktor.required_failure_attributes).to contain_exactly(:bar)
365
564
 
366
- interaktor.define_method(:call) do
367
- fail!({})
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
- expect { interaktor.call }.to(
371
- raise_error(
372
- an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
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
- describe "options" do
379
- it "raises an exception when an unknown option is provided" do
380
- expect {
381
- interaktor.class_eval { failure :bar, unknown: true }
382
- }.to raise_error(an_instance_of(Interaktor::Error::UnknownOptionError).and having_attributes(options: { unknown: true }))
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