interaktor 0.1.5 → 0.2.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.
@@ -62,7 +62,7 @@ class Interaktor::Context < OpenStruct
62
62
  # @return [void]
63
63
  def success!(context = {})
64
64
  context.each { |key, value| self[key.to_sym] = value }
65
- throw :early_return, self
65
+ early_return!
66
66
  end
67
67
 
68
68
  # Roll back the Interaktor::Context. Any interaktors to which this context
@@ -98,4 +98,19 @@ class Interaktor::Context < OpenStruct
98
98
  def _called
99
99
  @called ||= []
100
100
  end
101
+
102
+ # Trigger an early return throw.
103
+ #
104
+ # @return [void]
105
+ def early_return!
106
+ @early_return = true
107
+ throw :early_return, self
108
+ end
109
+
110
+ # Whether or not the context has been returned from early.
111
+ #
112
+ # @return [Boolean]
113
+ def early_return?
114
+ (@early_return == true) || false
115
+ end
101
116
  end
@@ -0,0 +1,16 @@
1
+ class Interaktor::Error::AttributeError < Interaktor::Error::Base
2
+ # @return [Array<Symbol>]
3
+ attr_reader :attributes
4
+
5
+ # @param interaktor [Class]
6
+ # @param attributes [Array<Symbol>]
7
+ def initialize(interaktor, attributes)
8
+ super(interaktor)
9
+
10
+ @attributes = attributes
11
+ end
12
+
13
+ def message
14
+ raise NotImplementedError
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class Interaktor::Error::Base < StandardError
2
+ # @return [Class]
3
+ attr_reader :interaktor
4
+
5
+ # @param interaktor [Class]
6
+ def initialize(interaktor)
7
+ @interaktor = interaktor
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Interaktor::Error::DisallowedAttributeAssignmentError < Interaktor::Error::AttributeError
2
+ def message
3
+ <<~MESSAGE.strip.tr("\n", "")
4
+ Attempted a disallowed assignment to the '#{attributes.first}'
5
+ attribute which was not included when the #{interaktor} interaktor was
6
+ originally called.
7
+ MESSAGE
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class Interaktor::Error::MissingAttributeError < Interaktor::Error::AttributeError
2
+ def message
3
+ "Missing attribute(s) in call to #{interaktor}: #{attributes.join(", ")}"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ class Interaktor::Error::OptionError < Interaktor::Error::Base
2
+ # @return [Hash{Symbol=>Object}]
3
+ attr_reader :options
4
+
5
+ # @param interaktor [Class]
6
+ # @param options [Hash{Symbol=>Object}]
7
+ def initialize(interaktor, options)
8
+ super(interaktor)
9
+
10
+ @options = options
11
+ end
12
+
13
+ def message
14
+ raise NotImplementedError
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class Interaktor::Error::UnknownAttributeError < Interaktor::Error::AttributeError
2
+ def message
3
+ "Unknown attribute(s) in call to #{interaktor}: #{attributes.join(", ")}"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Interaktor::Error::UnknownOptionError < Interaktor::Error::OptionError
2
+ def message
3
+ "Unknown option(s) in #{interaktor} interaktor: #{options.keys.join(", ")}"
4
+ end
5
+ end
@@ -40,28 +40,15 @@ module Interaktor::Organizer
40
40
  # @return [void]
41
41
  def call
42
42
  self.class.organized.each do |interaktor|
43
+ # Take the context that is being passed to each interaktor and remove
44
+ # any attributes from it that are not required by the interactor.
45
+ @context.to_h
46
+ .keys
47
+ .reject { |attr| interaktor.input_attributes.include?(attr) }
48
+ .each { |attr| @context.delete_field(attr) }
49
+
43
50
  catch(:early_return) { interaktor.call!(@context) }
44
51
  end
45
52
  end
46
-
47
- private
48
-
49
- # A list of attributes required to invoke the organized Interaktors.
50
- # Obtained by compiling a list of all required attributes of all organized
51
- # interaktors. Duplicates are removed.
52
- #
53
- # @return [Array<Symbol>]
54
- def required_attributes
55
- self.class.organized.map(&:required_attributes).flatten.uniq
56
- end
57
-
58
- # A list of optional attributes allowed to be included when invoking the
59
- # organized Interaktors. Obtained by compiling a list of all optional
60
- # attributes of all organized interaktors. Duplicates are removed.
61
- #
62
- # @return [Array<Symbol>]
63
- def optional_attributes
64
- self.class.organized.map(&:optional_attributes).flatten.uniq
65
- end
66
53
  end
67
54
  end
@@ -5,30 +5,33 @@
5
5
 
6
6
  describe "Integration" do
7
7
  def build_interaktor(&block)
8
- interaktor = Class.new.send(:include, Interaktor)
9
- interaktor.class_eval(&block) if block
10
- interaktor.class_eval do
11
- optional :steps
8
+ Class.new.tap do |interaktor|
9
+ interaktor.send(:include, Interaktor)
10
+ interaktor.class_eval(&block) if block
11
+ interaktor.class_eval do
12
+ optional :steps
12
13
 
13
- def unexpected_error!
14
- raise "foo"
14
+ def unexpected_error!
15
+ raise "foo"
16
+ end
15
17
  end
16
18
  end
17
- interaktor
18
19
  end
19
20
 
20
21
  def build_organizer(options = {}, &block)
21
- organizer = Class.new.send(:include, Interaktor::Organizer)
22
- organizer.organize(options[:organize]) if options[:organize]
23
- organizer.class_eval(&block) if block
24
- organizer.class_eval do
25
- optional :steps
22
+ Class.new.tap do |organizer|
23
+ organizer.send(:include, Interaktor::Organizer)
24
+
25
+ organizer.organize(options[:organize]) if options[:organize]
26
+ organizer.class_eval(&block) if block
27
+ organizer.class_eval do
28
+ optional :steps
26
29
 
27
- def unexpected_error!
28
- raise "foo"
30
+ def unexpected_error!
31
+ raise "foo"
32
+ end
29
33
  end
30
34
  end
31
- organizer
32
35
  end
33
36
 
34
37
  # rubocop:disable Style/AsciiComments
@@ -36,24 +36,92 @@ module Interaktor
36
36
  end
37
37
 
38
38
  describe "#call" do
39
- it "calls each interaktor in order with the context" do
40
- instance = organizer.new
41
- context = instance_double(Interaktor::Context)
42
- interaktor2 = class_double(Class.new.include(Interaktor))
43
- interaktor3 = class_double(Class.new.include(Interaktor))
44
- interaktor4 = class_double(Class.new.include(Interaktor))
45
-
46
- allow(instance).to receive(:context).and_return(context)
47
- instance.instance_variable_set(:@context, context)
39
+ it "calls each interaktor in order" do
40
+ organizer.class_eval { required :foo }
41
+
42
+ interaktor2 = Class.new.include(Interaktor)
43
+ interaktor2.class_eval { required :foo }
44
+ interaktor2.define_method(:call) { self.foo = "bar" }
45
+
46
+ interaktor3 = Class.new.include(Interaktor)
47
+ interaktor3.class_eval { required :foo }
48
+ interaktor3.define_method(:call) { self.foo = "baz" }
49
+
50
+ interaktor4 = Class.new.include(Interaktor)
51
+ interaktor4.class_eval { required :foo }
52
+ interaktor4.define_method(:call) { self.foo = "wadus" }
53
+
48
54
  allow(organizer).to receive(:organized) {
49
55
  [interaktor2, interaktor3, interaktor4]
50
56
  }
51
57
 
52
- expect(interaktor2).to receive(:call!).once.with(context).ordered
53
- expect(interaktor3).to receive(:call!).once.with(context).ordered
54
- expect(interaktor4).to receive(:call!).once.with(context).ordered
58
+ expect(interaktor2).to receive(:call!).once.ordered.and_call_original
59
+ expect(interaktor3).to receive(:call!).once.ordered.and_call_original
60
+ expect(interaktor4).to receive(:call!).once.ordered.and_call_original
61
+
62
+ result = organizer.call(foo: "asdf")
63
+
64
+ expect(result.foo).to eq "wadus"
65
+ end
66
+
67
+ it "calls each interaktor in order and passes success attributes" do
68
+ organizer.class_eval { required :foo }
69
+
70
+ interaktor2 = Class.new.include(Interaktor)
71
+ interaktor2.class_eval do
72
+ required :foo
73
+ success :bar
74
+ end
75
+ interaktor2.define_method(:call) { success!(bar: "baz") }
76
+
77
+ interaktor3 = Class.new.include(Interaktor)
78
+ interaktor3.class_eval { required :bar }
79
+ interaktor3.define_method(:call) { self.bar = "wadus" }
80
+
81
+ allow(organizer).to receive(:organized) {
82
+ [interaktor2, interaktor3]
83
+ }
84
+
85
+ expect(interaktor2).to receive(:call!).once.ordered.and_call_original
86
+ expect(interaktor3).to receive(:call!).once.ordered.and_call_original
87
+
88
+ result = organizer.call(foo: "asdf")
55
89
 
56
- instance.call
90
+ expect(result.bar).to eq "wadus"
91
+ end
92
+
93
+ it "raises an exception if the organizer attributes do not satisfy the first interaktor" do
94
+ organizer.class_eval { required :foo }
95
+
96
+ interaktor2 = Class.new.include(Interaktor)
97
+ interaktor2.class_eval { required :bar }
98
+
99
+ allow(organizer).to receive(:organized).and_return([interaktor2])
100
+
101
+ expect(interaktor2).to receive(:call!).once.ordered.and_call_original
102
+
103
+ expect {
104
+ organizer.call(foo: "bar")
105
+ }.to raise_error(an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar]))
106
+ end
107
+
108
+ it "raises an exception if the organizer attributes do not satisfy a non-first interaktor" do
109
+ organizer.class_eval { required :foo }
110
+
111
+ interaktor2 = Class.new.include(Interaktor)
112
+ interaktor2.class_eval { required :foo }
113
+
114
+ interaktor3 = Class.new.include(Interaktor)
115
+ interaktor3.class_eval { required :bar }
116
+
117
+ allow(organizer).to receive(:organized).and_return([interaktor2, interaktor3])
118
+
119
+ expect(interaktor2).to receive(:call!).once.ordered.and_call_original
120
+ expect(interaktor3).to receive(:call!).once.ordered.and_call_original
121
+
122
+ expect {
123
+ organizer.call(foo: "bar")
124
+ }.to raise_error(an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar]))
57
125
  end
58
126
  end
59
127
  end
@@ -1,8 +1,13 @@
1
- if ENV["CODECLIMATE_REPO_TOKEN"]
2
- require "simplecov"
3
- SimpleCov.start
1
+ require "simplecov"
2
+ SimpleCov.start do
3
+ enable_coverage :branch
4
+
5
+ add_filter "/spec/"
6
+ add_filter "/vendor/"
7
+ add_filter "/lib/interaktor/error"
4
8
  end
5
9
 
10
+ Bundler.require(:default, :test)
6
11
  require "interaktor"
7
12
 
8
13
  Dir[File.expand_path("support/*.rb", __dir__)].sort.each { |f| require f }
@@ -1,82 +1,51 @@
1
1
  shared_examples "lint" do
2
- let(:interaktor) do
3
- klass = Class.new.include(described_class)
4
- klass.class_eval do
5
- optional :foo
6
- end
7
-
8
- klass
9
- end
2
+ let(:interaktor) { Class.new.include(described_class) }
10
3
 
11
4
  describe ".call" do
12
- let(:context) { instance_double(Interaktor::Context) }
13
5
  let(:instance) { instance_double(interaktor) }
14
6
 
15
- it "calls an instance with the given context" do
16
- instance.instance_variable_set(:@context, context)
17
- expect(interaktor).to receive(:new).once.with(foo: "bar").and_return(instance)
18
- expect(instance).to receive(:run).once.with(no_args)
7
+ it "calls an instance" do
8
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
19
9
 
20
- resulting_context = interaktor.call(foo: "bar")
10
+ result = interaktor.call
21
11
 
22
- expect(resulting_context).to eq(context)
12
+ expect(result.success?).to be true
23
13
  end
24
14
 
25
- it "provides a blank context if none is given" do
26
- instance.instance_variable_set(:@context, context)
27
- expect(interaktor).to receive(:new).once.with({}).and_return(instance)
28
- expect(instance).to receive(:run).once.with(no_args)
29
-
30
- resulting_context = interaktor.call({})
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]))
19
+ end
31
20
 
32
- expect(resulting_context).to eq(context)
21
+ it "fails when a non-hash or non-context argument is passed" do
22
+ expect {
23
+ interaktor.call("foo")
24
+ }.to raise_error(ArgumentError, /Expected a hash argument/)
33
25
  end
34
26
  end
35
27
 
36
28
  describe ".call!" do
37
- let(:context) { instance_double(Interaktor::Context) }
38
29
  let(:instance) { instance_double(interaktor) }
39
30
 
40
- it "calls an instance with the given context" do
41
- instance.instance_variable_set(:@context, context)
42
- expect(interaktor).to receive(:new).once.with(foo: "bar").and_return(instance)
43
- expect(instance).to receive(:run!).once.with(no_args)
31
+ it "calls an instance" do
32
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
44
33
 
45
- resulting_context = interaktor.call!(foo: "bar")
34
+ result = interaktor.call!
46
35
 
47
- expect(resulting_context).to eq(context)
36
+ expect(result.success?).to be true
48
37
  end
49
38
 
50
- it "provides a blank context if none is given" do
51
- instance.instance_variable_set(:@context, context)
52
- expect(interaktor).to receive(:new).once.with({}).and_return(instance)
53
- expect(instance).to receive(:run!).once.with(no_args)
54
-
55
- resulting_context = interaktor.call!({})
56
-
57
- expect(resulting_context).to eq(context)
58
- end
59
- end
60
-
61
- describe ".new" do
62
- let(:context) { instance_double(Interaktor::Context) }
63
-
64
- it "initializes a context" do
65
- expect(Interaktor::Context).to receive(:build).once.with(foo: "bar").and_return(context)
66
-
67
- instance = interaktor.new(foo: "bar")
68
-
69
- expect(instance).to be_an(interaktor)
70
- expect(instance.instance_variable_get(:@context)).to eq(context)
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]))
71
43
  end
72
44
 
73
- it "initializes a blank context if none is given" do
74
- expect(Interaktor::Context).to receive(:build).once.with({}).and_return(context)
75
-
76
- instance = interaktor.new
77
-
78
- expect(instance).to be_a(interaktor)
79
- expect(instance.instance_variable_get(:@context)).to eq(context)
45
+ it "fails when a non-hash or non-context argument is passed" do
46
+ expect {
47
+ interaktor.call!("foo")
48
+ }.to raise_error(ArgumentError, /Expected a hash argument/)
80
49
  end
81
50
  end
82
51
 
@@ -151,4 +120,267 @@ shared_examples "lint" do
151
120
  expect { instance.method(:rollback) }.not_to raise_error
152
121
  end
153
122
  end
123
+
124
+ describe "required attributes" do
125
+ let(:instance) { instance_double(interaktor) }
126
+
127
+ it "initializes successfully when the attribute is provided" do
128
+ interaktor.class_eval { required :bar }
129
+
130
+ expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
131
+
132
+ result = interaktor.call(bar: "baz")
133
+
134
+ expect(result.success?).to be true
135
+ expect(result.bar).to eq "baz"
136
+ end
137
+
138
+ it "creates a value setter" do
139
+ interaktor.class_eval { required :bar }
140
+
141
+ expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
142
+
143
+ interaktor.define_method(:call) do
144
+ self.bar = "wadus"
145
+ end
146
+
147
+ result = interaktor.call(bar: "baz")
148
+
149
+ expect(result.success?).to be true
150
+ expect(result.bar).to eq "wadus"
151
+ end
152
+
153
+ it "raises an exception when the attribute is not provided" do
154
+ interaktor.class_eval { required :bar }
155
+
156
+ expect {
157
+ interaktor.call!
158
+ }.to raise_error(an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar]))
159
+ end
160
+
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 }))
166
+ end
167
+ end
168
+ end
169
+
170
+ describe "optional attributes" do
171
+ let(:instance) { instance_double(interaktor) }
172
+
173
+ it "initializes successfully when the attribute is provided" do
174
+ interaktor.class_eval { optional :bar }
175
+
176
+ expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
177
+
178
+ result = interaktor.call(bar: "baz")
179
+
180
+ expect(result.success?).to be true
181
+ expect(result.bar).to eq "baz"
182
+ end
183
+
184
+ it "initializes successfully when the attribute is not provided" do
185
+ interaktor.class_eval { optional :bar }
186
+
187
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
188
+
189
+ result = interaktor.call
190
+
191
+ expect(result.success?).to be true
192
+ expect(result.bar).to be_nil
193
+ end
194
+
195
+ it "creates a value setter" do
196
+ interaktor.class_eval { optional :bar }
197
+
198
+ expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
199
+
200
+ interaktor.define_method(:call) do
201
+ self.bar = "wadus"
202
+ end
203
+
204
+ result = interaktor.call(bar: "baz")
205
+
206
+ expect(result.success?).to be true
207
+ expect(result.bar).to eq "wadus"
208
+ end
209
+
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 }
212
+
213
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
214
+
215
+ interaktor.define_method(:call) do
216
+ self.bar = "baz"
217
+ end
218
+
219
+ expect { interaktor.call }.to(
220
+ raise_error(
221
+ an_instance_of(Interaktor::Error::DisallowedAttributeAssignmentError)
222
+ .and(having_attributes(attributes: [:bar]))
223
+ )
224
+ )
225
+ end
226
+
227
+ describe "options" do
228
+ it "accepts a default value for the attribute" do
229
+ interaktor.class_eval { optional :bar, default: "baz" }
230
+
231
+ expect(interaktor).to receive(:new).once.with(bar: "baz").and_call_original
232
+
233
+ result = interaktor.call
234
+
235
+ expect(result.success?).to be true
236
+ expect(result.bar).to eq "baz"
237
+ end
238
+
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 }))
243
+ end
244
+ end
245
+ end
246
+
247
+ describe "success attributes" do
248
+ it "succeeds when the correct attributes are provided" do
249
+ interaktor.class_eval { success :bar }
250
+
251
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
252
+
253
+ interaktor.define_method(:call) do
254
+ success!(bar: "baz")
255
+ end
256
+
257
+ result = interaktor.call
258
+
259
+ expect(result.success?).to be true
260
+ expect(result.bar).to eq "baz"
261
+ end
262
+
263
+ it "raises an exception when the correct attributes are not provided because #success! is not called" do
264
+ interaktor.class_eval { success :bar }
265
+
266
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
267
+
268
+ expect { interaktor.call }.to(
269
+ raise_error(
270
+ an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
271
+ )
272
+ )
273
+ end
274
+
275
+ it "raises an exception when the correct attributes are not provided in the call to #success!" do
276
+ interaktor.class_eval { success :bar }
277
+
278
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
279
+
280
+ interaktor.define_method(:call) do
281
+ success!({})
282
+ end
283
+
284
+ expect { interaktor.call }.to(
285
+ raise_error(
286
+ an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
287
+ )
288
+ )
289
+ end
290
+
291
+ it "raises an exception when unknown attributes are provided" do
292
+ interaktor.class_eval { success :bar }
293
+
294
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
295
+
296
+ interaktor.define_method(:call) do
297
+ success!(bar: "baz", baz: "wadus")
298
+ end
299
+
300
+ expect { interaktor.call }.to(
301
+ raise_error(
302
+ an_instance_of(Interaktor::Error::UnknownAttributeError).and having_attributes(attributes: [:baz])
303
+ )
304
+ )
305
+ end
306
+
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 }))
312
+ end
313
+ end
314
+ end
315
+
316
+ describe "failure attributes" do
317
+ it "fails when the correct attributes are provided" do
318
+ interaktor.class_eval { failure :bar }
319
+
320
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
321
+
322
+ interaktor.define_method(:call) do
323
+ fail!(bar: "baz")
324
+ end
325
+
326
+ result = interaktor.call
327
+
328
+ expect(result.success?).to be false
329
+ expect(result.bar).to eq "baz"
330
+ end
331
+
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
336
+
337
+ interaktor.define_method(:call) do
338
+ fail!({})
339
+ end
340
+
341
+ expect { interaktor.call }.to(
342
+ raise_error(
343
+ an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
344
+ )
345
+ )
346
+ end
347
+
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
353
+
354
+ interaktor.define_method(:call) do
355
+ fail!(bar: "baz")
356
+ end
357
+
358
+ expect { interaktor.call! }.to raise_error(Interaktor::Failure)
359
+ end
360
+
361
+ it "raises an exception when the correct attributes are not provided" do
362
+ interaktor.class_eval { failure :bar }
363
+
364
+ expect(interaktor).to receive(:new).once.with({}).and_call_original
365
+
366
+ interaktor.define_method(:call) do
367
+ fail!({})
368
+ end
369
+
370
+ expect { interaktor.call }.to(
371
+ raise_error(
372
+ an_instance_of(Interaktor::Error::MissingAttributeError).and having_attributes(attributes: [:bar])
373
+ )
374
+ )
375
+ end
376
+ end
377
+
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 }))
383
+ end
384
+ end
385
+ end
154
386
  end