interaktor 0.1.3

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.
@@ -0,0 +1,187 @@
1
+ module Interaktor
2
+ describe Context do
3
+ describe ".build" do
4
+ it "converts the given hash to a context" do
5
+ context = Context.build(foo: "bar")
6
+
7
+ expect(context).to be_a(Context)
8
+ expect(context.foo).to eq("bar")
9
+ end
10
+
11
+ it "builds an empty context if no hash is given" do
12
+ context = Context.build
13
+
14
+ expect(context).to be_a(Context)
15
+ expect(context.send(:table)).to eq({})
16
+ end
17
+
18
+ it "doesn't affect the original hash" do
19
+ hash = { foo: "bar" }
20
+ context = Context.build(hash)
21
+
22
+ expect(context).to be_a(Context)
23
+ expect {
24
+ context.foo = "baz"
25
+ }.not_to change {
26
+ hash[:foo]
27
+ }
28
+ end
29
+
30
+ it "preserves an already built context" do
31
+ context1 = Context.build(foo: "bar")
32
+ context2 = Context.build(context1)
33
+
34
+ expect(context2).to be_a(Context)
35
+ expect {
36
+ context2.foo = "baz"
37
+ }.to change {
38
+ context1.foo
39
+ }.from("bar").to("baz")
40
+ end
41
+ end
42
+
43
+ describe "#success?" do
44
+ let(:context) { Context.build }
45
+
46
+ it "is true by default" do
47
+ expect(context.success?).to eq(true)
48
+ end
49
+ end
50
+
51
+ describe "#failure?" do
52
+ let(:context) { Context.build }
53
+
54
+ it "is false by default" do
55
+ expect(context.failure?).to eq(false)
56
+ end
57
+ end
58
+
59
+ describe "#fail!" do
60
+ let(:context) { Context.build(foo: "bar") }
61
+
62
+ it "sets success to false" do
63
+ expect {
64
+ begin
65
+ context.fail!
66
+ rescue StandardError
67
+ nil
68
+ end
69
+ }.to change(context, :success?).from(true).to(false)
70
+ end
71
+
72
+ it "sets failure to true" do
73
+ expect {
74
+ begin
75
+ context.fail!
76
+ rescue StandardError
77
+ nil
78
+ end
79
+ }.to change(context, :failure?).from(false).to(true)
80
+ end
81
+
82
+ it "preserves failure" do
83
+ begin
84
+ context.fail!
85
+ rescue StandardError
86
+ nil
87
+ end
88
+
89
+ expect {
90
+ begin
91
+ context.fail!
92
+ rescue StandardError
93
+ nil
94
+ end
95
+ }.not_to change(context, :failure?)
96
+ end
97
+
98
+ it "preserves the context" do
99
+ expect {
100
+ begin
101
+ context.fail!
102
+ rescue StandardError
103
+ nil
104
+ end
105
+ }.not_to change(context, :foo)
106
+ end
107
+
108
+ it "updates the context" do
109
+ expect {
110
+ begin
111
+ context.fail!(foo: "baz")
112
+ rescue StandardError
113
+ nil
114
+ end
115
+ }.to change(context, :foo).from("bar").to("baz")
116
+ end
117
+
118
+ it "updates the context with a string key" do
119
+ expect {
120
+ begin
121
+ context.fail!("foo" => "baz")
122
+ rescue StandardError
123
+ nil
124
+ end
125
+ }.to change(context, :foo).from("bar").to("baz")
126
+ end
127
+
128
+ it "raises failure" do
129
+ expect {
130
+ context.fail!
131
+ }.to raise_error(Failure)
132
+ end
133
+
134
+ it "makes the context available from the failure" do
135
+ context.fail!
136
+ rescue Failure => e
137
+ expect(e.context).to eq(context)
138
+ end
139
+ end
140
+
141
+ describe "#called!" do
142
+ let(:context) { Context.build }
143
+ let(:instance1) { double(:instance1) }
144
+ let(:instance2) { double(:instance2) }
145
+
146
+ it "appends to the internal list of called instances" do
147
+ expect {
148
+ context.called!(instance1)
149
+ context.called!(instance2)
150
+ }.to change(context, :_called).from([]).to([instance1, instance2])
151
+ end
152
+ end
153
+
154
+ describe "#rollback!" do
155
+ let(:context) { Context.build }
156
+ let(:instance1) { double(:instance1) }
157
+ let(:instance2) { double(:instance2) }
158
+
159
+ before do
160
+ allow(context).to receive(:_called) { [instance1, instance2] }
161
+ end
162
+
163
+ it "rolls back each instance in reverse order" do
164
+ expect(instance2).to receive(:rollback).once.with(no_args).ordered
165
+ expect(instance1).to receive(:rollback).once.with(no_args).ordered
166
+
167
+ context.rollback!
168
+ end
169
+
170
+ it "ignores subsequent attempts" do
171
+ expect(instance2).to receive(:rollback).once
172
+ expect(instance1).to receive(:rollback).once
173
+
174
+ context.rollback!
175
+ context.rollback!
176
+ end
177
+ end
178
+
179
+ describe "#_called" do
180
+ let(:context) { Context.build }
181
+
182
+ it "is empty by default" do
183
+ expect(context._called).to eq([])
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,358 @@
1
+ module Interaktor
2
+ describe Hooks do
3
+ describe "#with_hooks" do
4
+ def build_hooked(&block)
5
+ hooked = Class.new.send(:include, Interaktor::Hooks)
6
+
7
+ hooked.class_eval do
8
+ attr_reader :steps
9
+
10
+ def self.process
11
+ new.tap(&:process).steps
12
+ end
13
+
14
+ def initialize
15
+ @steps = []
16
+ end
17
+
18
+ def process
19
+ with_hooks { steps << :process }
20
+ end
21
+ end
22
+
23
+ hooked.class_eval(&block) if block
24
+ hooked
25
+ end
26
+
27
+ context "with an around hook method" do
28
+ let(:hooked) {
29
+ build_hooked do
30
+ around :add_around_before_and_around_after
31
+
32
+ private
33
+
34
+ def add_around_before_and_around_after(hooked)
35
+ steps << :around_before
36
+ hooked.call
37
+ steps << :around_after
38
+ end
39
+ end
40
+ }
41
+
42
+ it "runs the around hook method" do
43
+ expect(hooked.process).to eq([
44
+ :around_before,
45
+ :process,
46
+ :around_after,
47
+ ])
48
+ end
49
+ end
50
+
51
+ context "with an around hook block" do
52
+ let(:hooked) {
53
+ build_hooked do
54
+ around do |hooked|
55
+ steps << :around_before
56
+ hooked.call
57
+ steps << :around_after
58
+ end
59
+ end
60
+ }
61
+
62
+ it "runs the around hook block" do
63
+ expect(hooked.process).to eq([
64
+ :around_before,
65
+ :process,
66
+ :around_after,
67
+ ])
68
+ end
69
+ end
70
+
71
+ context "with an around hook method and block in one call" do
72
+ let(:hooked) {
73
+ build_hooked do
74
+ around :add_around_before1_and_around_after1 do |hooked|
75
+ steps << :around_before2
76
+ hooked.call
77
+ steps << :around_after2
78
+ end
79
+
80
+ private
81
+
82
+ def add_around_before1_and_around_after1(hooked)
83
+ steps << :around_before1
84
+ hooked.call
85
+ steps << :around_after1
86
+ end
87
+ end
88
+ }
89
+
90
+ it "runs the around hook method and block in order" do
91
+ expect(hooked.process).to eq([
92
+ :around_before1,
93
+ :around_before2,
94
+ :process,
95
+ :around_after2,
96
+ :around_after1,
97
+ ])
98
+ end
99
+ end
100
+
101
+ context "with an around hook method and block in multiple calls" do
102
+ let(:hooked) {
103
+ build_hooked do
104
+ around do |hooked|
105
+ steps << :around_before1
106
+ hooked.call
107
+ steps << :around_after1
108
+ end
109
+
110
+ around :add_around_before2_and_around_after2
111
+
112
+ private
113
+
114
+ def add_around_before2_and_around_after2(hooked)
115
+ steps << :around_before2
116
+ hooked.call
117
+ steps << :around_after2
118
+ end
119
+ end
120
+ }
121
+
122
+ it "runs the around hook block and method in order" do
123
+ expect(hooked.process).to eq([
124
+ :around_before1,
125
+ :around_before2,
126
+ :process,
127
+ :around_after2,
128
+ :around_after1,
129
+ ])
130
+ end
131
+ end
132
+
133
+ context "with a before hook method" do
134
+ let(:hooked) {
135
+ build_hooked do
136
+ before :add_before
137
+
138
+ private
139
+
140
+ def add_before
141
+ steps << :before
142
+ end
143
+ end
144
+ }
145
+
146
+ it "runs the before hook method" do
147
+ expect(hooked.process).to eq([
148
+ :before,
149
+ :process,
150
+ ])
151
+ end
152
+ end
153
+
154
+ context "with a before hook block" do
155
+ let(:hooked) {
156
+ build_hooked do
157
+ before do
158
+ steps << :before
159
+ end
160
+ end
161
+ }
162
+
163
+ it "runs the before hook block" do
164
+ expect(hooked.process).to eq([
165
+ :before,
166
+ :process,
167
+ ])
168
+ end
169
+ end
170
+
171
+ context "with a before hook method and block in one call" do
172
+ let(:hooked) {
173
+ build_hooked do
174
+ before :add_before1 do
175
+ steps << :before2
176
+ end
177
+
178
+ private
179
+
180
+ def add_before1
181
+ steps << :before1
182
+ end
183
+ end
184
+ }
185
+
186
+ it "runs the before hook method and block in order" do
187
+ expect(hooked.process).to eq([
188
+ :before1,
189
+ :before2,
190
+ :process,
191
+ ])
192
+ end
193
+ end
194
+
195
+ context "with a before hook method and block in multiple calls" do
196
+ let(:hooked) {
197
+ build_hooked do
198
+ before do
199
+ steps << :before1
200
+ end
201
+
202
+ before :add_before2
203
+
204
+ private
205
+
206
+ def add_before2
207
+ steps << :before2
208
+ end
209
+ end
210
+ }
211
+
212
+ it "runs the before hook block and method in order" do
213
+ expect(hooked.process).to eq([
214
+ :before1,
215
+ :before2,
216
+ :process,
217
+ ])
218
+ end
219
+ end
220
+
221
+ context "with an after hook method" do
222
+ let(:hooked) {
223
+ build_hooked do
224
+ after :add_after
225
+
226
+ private
227
+
228
+ def add_after
229
+ steps << :after
230
+ end
231
+ end
232
+ }
233
+
234
+ it "runs the after hook method" do
235
+ expect(hooked.process).to eq([
236
+ :process,
237
+ :after,
238
+ ])
239
+ end
240
+ end
241
+
242
+ context "with an after hook block" do
243
+ let(:hooked) {
244
+ build_hooked do
245
+ after do
246
+ steps << :after
247
+ end
248
+ end
249
+ }
250
+
251
+ it "runs the after hook block" do
252
+ expect(hooked.process).to eq([
253
+ :process,
254
+ :after,
255
+ ])
256
+ end
257
+ end
258
+
259
+ context "with an after hook method and block in one call" do
260
+ let(:hooked) {
261
+ build_hooked do
262
+ after :add_after1 do
263
+ steps << :after2
264
+ end
265
+
266
+ private
267
+
268
+ def add_after1
269
+ steps << :after1
270
+ end
271
+ end
272
+ }
273
+
274
+ it "runs the after hook method and block in order" do
275
+ expect(hooked.process).to eq([
276
+ :process,
277
+ :after2,
278
+ :after1,
279
+ ])
280
+ end
281
+ end
282
+
283
+ context "with an after hook method and block in multiple calls" do
284
+ let(:hooked) {
285
+ build_hooked do
286
+ after do
287
+ steps << :after1
288
+ end
289
+
290
+ after :add_after2
291
+
292
+ private
293
+
294
+ def add_after2
295
+ steps << :after2
296
+ end
297
+ end
298
+ }
299
+
300
+ it "runs the after hook block and method in order" do
301
+ expect(hooked.process).to eq([
302
+ :process,
303
+ :after2,
304
+ :after1,
305
+ ])
306
+ end
307
+ end
308
+
309
+ context "with around, before and after hooks" do
310
+ let(:hooked) {
311
+ build_hooked do
312
+ around do |hooked|
313
+ steps << :around_before1
314
+ hooked.call
315
+ steps << :around_after1
316
+ end
317
+
318
+ around do |hooked|
319
+ steps << :around_before2
320
+ hooked.call
321
+ steps << :around_after2
322
+ end
323
+
324
+ before do
325
+ steps << :before1
326
+ end
327
+
328
+ before do
329
+ steps << :before2
330
+ end
331
+
332
+ after do
333
+ steps << :after1
334
+ end
335
+
336
+ after do
337
+ steps << :after2
338
+ end
339
+ end
340
+ }
341
+
342
+ it "runs hooks in the proper order" do
343
+ expect(hooked.process).to eq([
344
+ :around_before1,
345
+ :around_before2,
346
+ :before1,
347
+ :before2,
348
+ :process,
349
+ :after2,
350
+ :after1,
351
+ :around_after2,
352
+ :around_after1,
353
+ ])
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end