interaktor 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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