interaktor 0.1.3

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