stealth 1.1.5 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +27 -11
  3. data/CHANGELOG.md +77 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +53 -49
  6. data/LICENSE +4 -17
  7. data/README.md +9 -17
  8. data/VERSION +1 -1
  9. data/lib/stealth/base.rb +72 -21
  10. data/lib/stealth/cli.rb +1 -2
  11. data/lib/stealth/commands/console.rb +1 -1
  12. data/lib/stealth/configuration.rb +6 -3
  13. data/lib/stealth/controller/callbacks.rb +1 -1
  14. data/lib/stealth/controller/catch_all.rb +27 -4
  15. data/lib/stealth/controller/controller.rb +168 -49
  16. data/lib/stealth/controller/dev_jumps.rb +41 -0
  17. data/lib/stealth/controller/dynamic_delay.rb +4 -6
  18. data/lib/stealth/controller/interrupt_detect.rb +100 -0
  19. data/lib/stealth/controller/messages.rb +283 -0
  20. data/lib/stealth/controller/nlp.rb +50 -0
  21. data/lib/stealth/controller/replies.rb +183 -40
  22. data/lib/stealth/controller/unrecognized_message.rb +62 -0
  23. data/lib/stealth/{flow/core_ext.rb → core_ext/numeric.rb} +0 -1
  24. data/lib/stealth/core_ext/string.rb +18 -0
  25. data/lib/stealth/core_ext.rb +5 -0
  26. data/lib/stealth/dispatcher.rb +21 -0
  27. data/lib/stealth/errors.rb +12 -0
  28. data/lib/stealth/flow/base.rb +1 -2
  29. data/lib/stealth/flow/specification.rb +3 -2
  30. data/lib/stealth/flow/state.rb +3 -3
  31. data/lib/stealth/generators/builder/Gemfile +4 -3
  32. data/lib/stealth/generators/builder/bot/controllers/bot_controller.rb +42 -0
  33. data/lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb +2 -0
  34. data/lib/stealth/generators/builder/bot/controllers/goodbyes_controller.rb +2 -0
  35. data/lib/stealth/generators/builder/bot/controllers/hellos_controller.rb +2 -0
  36. data/lib/stealth/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  37. data/lib/stealth/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  38. data/lib/stealth/generators/builder/config/flow_map.rb +8 -0
  39. data/lib/stealth/generators/builder/config/initializers/autoload.rb +8 -0
  40. data/lib/stealth/generators/builder/config/initializers/inflections.rb +16 -0
  41. data/lib/stealth/generators/builder/config/puma.rb +15 -0
  42. data/lib/stealth/helpers/redis.rb +40 -0
  43. data/lib/stealth/lock.rb +83 -0
  44. data/lib/stealth/logger.rb +27 -18
  45. data/lib/stealth/nlp/client.rb +22 -0
  46. data/lib/stealth/nlp/result.rb +57 -0
  47. data/lib/stealth/reloader.rb +90 -0
  48. data/lib/stealth/reply.rb +17 -0
  49. data/lib/stealth/scheduled_reply.rb +3 -3
  50. data/lib/stealth/server.rb +8 -3
  51. data/lib/stealth/service_message.rb +3 -2
  52. data/lib/stealth/service_reply.rb +5 -1
  53. data/lib/stealth/services/base_reply_handler.rb +10 -2
  54. data/lib/stealth/session.rb +106 -53
  55. data/spec/configuration_spec.rb +42 -2
  56. data/spec/controller/callbacks_spec.rb +23 -28
  57. data/spec/controller/catch_all_spec.rb +87 -29
  58. data/spec/controller/controller_spec.rb +444 -43
  59. data/spec/controller/dynamic_delay_spec.rb +16 -18
  60. data/spec/controller/helpers_spec.rb +1 -2
  61. data/spec/controller/interrupt_detect_spec.rb +171 -0
  62. data/spec/controller/messages_spec.rb +744 -0
  63. data/spec/controller/nlp_spec.rb +93 -0
  64. data/spec/controller/replies_spec.rb +446 -11
  65. data/spec/controller/unrecognized_message_spec.rb +168 -0
  66. data/spec/dispatcher_spec.rb +79 -0
  67. data/spec/flow/flow_spec.rb +1 -2
  68. data/spec/flow/state_spec.rb +14 -3
  69. data/spec/helpers/redis_spec.rb +77 -0
  70. data/spec/lock_spec.rb +100 -0
  71. data/spec/nlp/client_spec.rb +23 -0
  72. data/spec/nlp/result_spec.rb +57 -0
  73. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  74. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  75. data/spec/replies/messages/say_randomize_text.yml +10 -0
  76. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  77. data/spec/reply_spec.rb +61 -0
  78. data/spec/scheduled_reply_spec.rb +23 -0
  79. data/spec/service_reply_spec.rb +1 -2
  80. data/spec/session_spec.rb +251 -12
  81. data/spec/spec_helper.rb +21 -0
  82. data/spec/support/controllers/vaders_controller.rb +24 -0
  83. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  84. data/spec/support/nlp_clients/luis.rb +9 -0
  85. data/spec/support/nlp_results/luis_result.rb +163 -0
  86. data/spec/version_spec.rb +1 -2
  87. data/stealth.gemspec +6 -6
  88. metadata +83 -38
  89. data/docs/00-introduction.md +0 -37
  90. data/docs/01-getting-started.md +0 -21
  91. data/docs/02-local-development.md +0 -40
  92. data/docs/03-basics.md +0 -171
  93. data/docs/04-sessions.md +0 -29
  94. data/docs/05-controllers.md +0 -179
  95. data/docs/06-models.md +0 -39
  96. data/docs/07-replies.md +0 -114
  97. data/docs/08-catchalls.md +0 -49
  98. data/docs/09-messaging-integrations.md +0 -80
  99. data/docs/10-nlp-integrations.md +0 -13
  100. data/docs/11-analytics.md +0 -13
  101. data/docs/12-commands.md +0 -62
  102. data/docs/13-deployment.md +0 -50
  103. data/lib/stealth/generators/builder/config/initializers/.keep +0 -0
@@ -1,7 +1,6 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '/spec_helper'))
3
+ require 'spec_helper'
5
4
 
6
5
  describe "Stealth::Controller" do
7
6
 
@@ -25,12 +24,24 @@ describe "Stealth::Controller" do
25
24
  end
26
25
 
27
26
  def other_action2
28
-
27
+ step_to state: :other_action4
29
28
  end
30
29
 
31
30
  def other_action3
32
31
 
33
32
  end
33
+
34
+ def other_action4
35
+ do_nothing
36
+ end
37
+
38
+ def broken_action
39
+ raise StandardError
40
+ end
41
+
42
+ def parts_unknown
43
+ step_to flow: :parts, state: :unknown
44
+ end
34
45
  end
35
46
 
36
47
  class FlowMap
@@ -46,27 +57,26 @@ describe "Stealth::Controller" do
46
57
  state :other_action
47
58
  state :other_action2
48
59
  state :other_action3
60
+ state :other_action4
61
+ state :broken_action
62
+ state :part_unknown
49
63
  state :deprecated_action, redirects_to: :other_action
50
64
  state :deprecated_action2, redirects_to: 'mr_robot->my_action'
51
65
  end
52
66
  end
53
67
 
54
68
  let(:facebook_message) { SampleMessage.new(service: 'facebook') }
55
- let(:controller) { MrTronsController.new(service_message: facebook_message.message_with_text) }
69
+ let(:controller) {
70
+ MrTronsController.new(service_message: facebook_message.message_with_text)
71
+ }
56
72
 
57
73
  describe "convenience methods" do
58
74
  it "should make the session ID accessible via current_session_id" do
59
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
75
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
60
76
 
61
77
  expect(controller.current_session_id).to eq(facebook_message.sender_id)
62
78
  end
63
79
 
64
- it "should make the session ID accessible via current_user_id" do
65
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
66
-
67
- expect(controller.current_user_id).to eq(facebook_message.sender_id)
68
- end
69
-
70
80
  it "should make the message available in current_message.message" do
71
81
  expect(controller.current_message.message).to eq(facebook_message.message)
72
82
  end
@@ -126,9 +136,9 @@ describe "Stealth::Controller" do
126
136
 
127
137
  it "should step_to the specified redirect flow and state when a session is specified" do
128
138
  controller.current_session.session = Stealth::Session.canonical_session_slug(flow: 'mr_tron', state: 'deprecated_action2')
129
- mr_robot_controller = MrTronsController.new(service_message: facebook_message.message_with_text)
139
+ mr_robot_controller = MrRobotsController.new(service_message: facebook_message.message_with_text)
130
140
 
131
- expect(MrRobotsController).to receive(:new).and_return(mr_robot_controller)
141
+ allow(MrRobotsController).to receive(:new).and_return(mr_robot_controller)
132
142
  expect(mr_robot_controller).to receive(:my_action)
133
143
  controller.action(action: :deprecated_action2)
134
144
  end
@@ -156,7 +166,7 @@ describe "Stealth::Controller" do
156
166
  it "should call a controller's corresponding action when only a state is provided" do
157
167
  expect_any_instance_of(MrTronsController).to receive(:other_action3)
158
168
 
159
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
169
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
160
170
 
161
171
  controller.step_to state: "other_action3"
162
172
  end
@@ -175,10 +185,44 @@ describe "Stealth::Controller" do
175
185
  controller.step_to session: controller.current_session
176
186
  end
177
187
 
188
+ it "should call a controller's corresponding action when a session slug is provided" do
189
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
190
+ controller.step_to slug: 'mr_robot->my_action3'
191
+ end
192
+
193
+ it "should pass along the service_message" do
194
+ robot_controller_dbl = double('MrRobotsController').as_null_object
195
+ expect(MrRobotsController).to receive(:new).with(service_message: controller.current_message, pos: nil).and_return(robot_controller_dbl)
196
+ controller.step_to flow: :mr_robot, state: :my_action3
197
+ end
198
+
178
199
  it "should accept flow and string specified as symbols" do
179
200
  expect_any_instance_of(MrRobotsController).to receive(:my_action3)
180
201
  controller.step_to flow: :mr_robot, state: :my_action3
181
202
  end
203
+
204
+ it "should check if an interruption occured" do
205
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
206
+ controller.step_to flow: :mr_robot, state: :my_action3
207
+ end
208
+
209
+ it "should call run_interrupt_action if an interruption occured and return" do
210
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
211
+ expect(controller).to receive(:run_interrupt_action)
212
+ expect(controller.step_to(flow: :mr_robot, state: :my_action3)).to eq :interrupted
213
+ end
214
+
215
+ it "should set @pos if it is specified in the arguments" do
216
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
217
+ controller.step_to flow: :mr_robot, state: :my_action3, pos: -1
218
+ expect(controller.pos).to eq -1
219
+ end
220
+
221
+ it "should leave @pos as nil if the pos argument is not specified" do
222
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
223
+ controller.step_to flow: :mr_robot, state: :my_action3
224
+ expect(controller.pos).to be_nil
225
+ end
182
226
  end
183
227
 
184
228
  describe "update_session_to" do
@@ -199,7 +243,7 @@ describe "Stealth::Controller" do
199
243
  it "should update session to controller's corresponding action when only a state is provided" do
200
244
  expect_any_instance_of(MrTronsController).to_not receive(:other_action3)
201
245
 
202
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
246
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
203
247
 
204
248
  controller.update_session_to state: "other_action3"
205
249
  expect(controller.current_session.flow_string).to eq('mr_tron')
@@ -217,14 +261,22 @@ describe "Stealth::Controller" do
217
261
  it "should update session to controller's corresponding action when a session is provided" do
218
262
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
219
263
 
220
- session = Stealth::Session.new(user_id: controller.current_session_id)
221
- session.set(flow: 'mr_robot', state: 'my_action3')
264
+ session = Stealth::Session.new(id: controller.current_session_id)
265
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
222
266
 
223
267
  controller.update_session_to session: session
224
268
  expect(controller.current_session.flow_string).to eq('mr_robot')
225
269
  expect(controller.current_session.state_string).to eq('my_action3')
226
270
  end
227
271
 
272
+ it "should update session to controller's corresponding action when a session slug is provided" do
273
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
274
+ expect(controller.current_session.flow_string).to eq('mr_robot')
275
+ expect(controller.current_session.state_string).to eq('my_action3')
276
+
277
+ controller.update_session_to slug: 'mr_robot->my_action3'
278
+ end
279
+
228
280
  it "should accept flow and string specified as symbols" do
229
281
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
230
282
 
@@ -232,6 +284,17 @@ describe "Stealth::Controller" do
232
284
  expect(controller.current_session.flow_string).to eq('mr_robot')
233
285
  expect(controller.current_session.state_string).to eq('my_action3')
234
286
  end
287
+
288
+ it "should check if an interruption occured" do
289
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
290
+ controller.update_session_to flow: :mr_robot, state: :my_action3
291
+ end
292
+
293
+ it "should call run_interrupt_action if an interruption occured and return" do
294
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
295
+ expect(controller).to receive(:run_interrupt_action)
296
+ expect(controller.update_session_to(flow: :mr_robot, state: :my_action3)).to eq :interrupted
297
+ end
235
298
  end
236
299
 
237
300
  describe "step_to_in" do
@@ -255,30 +318,32 @@ describe "Stealth::Controller" do
255
318
  controller.current_service,
256
319
  controller.current_session_id,
257
320
  'mr_robot',
258
- 'my_action'
321
+ 'my_action',
322
+ nil
259
323
  )
260
324
 
261
325
  expect {
262
326
  controller.step_to_in 100.seconds, flow: "mr_robot"
263
- }.to_not change(controller.current_session, :get)
327
+ }.to_not change(controller.current_session, :get_session)
264
328
  end
265
329
 
266
330
  it "should schedule a transition to controller's corresponding action when only a state is provided" do
267
331
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
268
332
 
269
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
333
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
270
334
 
271
335
  expect(Stealth::ScheduledReplyJob).to receive(:perform_in).with(
272
336
  100.seconds,
273
337
  controller.current_service,
274
338
  controller.current_session_id,
275
339
  'mr_tron',
276
- 'other_action3'
340
+ 'other_action3',
341
+ nil
277
342
  )
278
343
 
279
344
  expect {
280
345
  controller.step_to_in 100.seconds, state: "other_action3"
281
- }.to_not change(controller.current_session, :get)
346
+ }.to_not change(controller.current_session, :get_session)
282
347
  end
283
348
 
284
349
  it "should update session to controller's corresponding action when a state and flow is provided" do
@@ -289,31 +354,50 @@ describe "Stealth::Controller" do
289
354
  controller.current_service,
290
355
  controller.current_session_id,
291
356
  'mr_robot',
292
- 'my_action3'
357
+ 'my_action3',
358
+ nil
293
359
  )
294
360
 
295
361
  expect {
296
362
  controller.step_to_in 100.seconds, flow: 'mr_robot', state: "my_action3"
297
- }.to_not change(controller.current_session, :get)
363
+ }.to_not change(controller.current_session, :get_session)
298
364
  end
299
365
 
300
366
  it "should update session to controller's corresponding action when a session is provided" do
301
367
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
302
368
 
303
- session = Stealth::Session.new(user_id: controller.current_session_id)
304
- session.set(flow: 'mr_robot', state: 'my_action3')
369
+ session = Stealth::Session.new(id: controller.current_session_id)
370
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
305
371
 
306
372
  expect(Stealth::ScheduledReplyJob).to receive(:perform_in).with(
307
373
  100.seconds,
308
374
  controller.current_service,
309
375
  controller.current_session_id,
310
376
  'mr_robot',
311
- 'my_action3'
377
+ 'my_action3',
378
+ nil
312
379
  )
313
380
 
314
381
  expect {
315
382
  controller.step_to_in 100.seconds, session: session
316
- }.to_not change(controller.current_session, :get)
383
+ }.to_not change(controller.current_session, :get_session)
384
+ end
385
+
386
+ it "should update session to controller's corresponding action when a session slug is provided" do
387
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
388
+
389
+ expect(Stealth::ScheduledReplyJob).to receive(:perform_in).with(
390
+ 100.seconds,
391
+ controller.current_service,
392
+ controller.current_session_id,
393
+ 'mr_robot',
394
+ 'my_action3',
395
+ nil
396
+ )
397
+
398
+ expect {
399
+ controller.step_to_in 100.seconds, slug: 'mr_robot->my_action3'
400
+ }.to_not change(controller.current_session, :get_session)
317
401
  end
318
402
 
319
403
  it "should accept flow and string specified as symbols" do
@@ -324,12 +408,38 @@ describe "Stealth::Controller" do
324
408
  controller.current_service,
325
409
  controller.current_session_id,
326
410
  'mr_robot',
327
- 'my_action3'
411
+ 'my_action3',
412
+ nil
328
413
  )
329
414
 
330
415
  expect {
331
416
  controller.step_to_in 100.seconds, flow: :mr_robot, state: :my_action3
332
- }.to_not change(controller.current_session, :get)
417
+ }.to_not change(controller.current_session, :get_session)
418
+ end
419
+
420
+ it "should pass along the target_id if set on the message" do
421
+ expect(Stealth::ScheduledReplyJob).to receive(:perform_in).with(
422
+ 100.seconds,
423
+ controller.current_service,
424
+ controller.current_session_id,
425
+ 'mr_robot',
426
+ 'my_action3',
427
+ '+18885551212'
428
+ )
429
+
430
+ controller.current_message.target_id = '+18885551212'
431
+ controller.step_to_in 100.seconds, flow: :mr_robot, state: :my_action3
432
+ end
433
+
434
+ it "should check if an interruption occured" do
435
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
436
+ controller.step_to_in 100.seconds, flow: :mr_robot, state: :my_action3
437
+ end
438
+
439
+ it "should call run_interrupt_action if an interruption occured and return" do
440
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
441
+ expect(controller).to receive(:run_interrupt_action)
442
+ expect(controller.step_to_in(100.seconds, flow: :mr_robot, state: :my_action3)).to eq :interrupted
333
443
  end
334
444
  end
335
445
 
@@ -356,30 +466,32 @@ describe "Stealth::Controller" do
356
466
  controller.current_service,
357
467
  controller.current_session_id,
358
468
  'mr_robot',
359
- 'my_action'
469
+ 'my_action',
470
+ nil
360
471
  )
361
472
 
362
473
  expect {
363
474
  controller.step_to_at future_timestamp, flow: "mr_robot"
364
- }.to_not change(controller.current_session, :get)
475
+ }.to_not change(controller.current_session, :get_session)
365
476
  end
366
477
 
367
478
  it "should schedule a transition to controller's corresponding action when only a state is provided" do
368
479
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
369
480
 
370
- controller.current_session.set(flow: 'mr_tron', state: 'other_action')
481
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
371
482
 
372
483
  expect(Stealth::ScheduledReplyJob).to receive(:perform_at).with(
373
484
  future_timestamp,
374
485
  controller.current_service,
375
486
  controller.current_session_id,
376
487
  'mr_tron',
377
- 'other_action3'
488
+ 'other_action3',
489
+ nil
378
490
  )
379
491
 
380
492
  expect {
381
493
  controller.step_to_at future_timestamp, state: "other_action3"
382
- }.to_not change(controller.current_session, :get)
494
+ }.to_not change(controller.current_session, :get_session)
383
495
  end
384
496
 
385
497
  it "should update session to controller's corresponding action when a state and flow is provided" do
@@ -390,31 +502,50 @@ describe "Stealth::Controller" do
390
502
  controller.current_service,
391
503
  controller.current_session_id,
392
504
  'mr_robot',
393
- 'my_action3'
505
+ 'my_action3',
506
+ nil
394
507
  )
395
508
 
396
509
  expect {
397
510
  controller.step_to_at future_timestamp, flow: 'mr_robot', state: "my_action3"
398
- }.to_not change(controller.current_session, :get)
511
+ }.to_not change(controller.current_session, :get_session)
399
512
  end
400
513
 
401
514
  it "should update session to controller's corresponding action when a session is provided" do
402
515
  expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
403
516
 
404
- session = Stealth::Session.new(user_id: controller.current_session_id)
405
- session.set(flow: 'mr_robot', state: 'my_action3')
517
+ session = Stealth::Session.new(id: controller.current_session_id)
518
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
406
519
 
407
520
  expect(Stealth::ScheduledReplyJob).to receive(:perform_at).with(
408
521
  future_timestamp,
409
522
  controller.current_service,
410
523
  controller.current_session_id,
411
524
  'mr_robot',
412
- 'my_action3'
525
+ 'my_action3',
526
+ nil
413
527
  )
414
528
 
415
529
  expect {
416
530
  controller.step_to_at future_timestamp, session: session
417
- }.to_not change(controller.current_session, :get)
531
+ }.to_not change(controller.current_session, :get_session)
532
+ end
533
+
534
+ it "should update session to controller's corresponding action when a session slug is provided" do
535
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
536
+
537
+ expect(Stealth::ScheduledReplyJob).to receive(:perform_at).with(
538
+ future_timestamp,
539
+ controller.current_service,
540
+ controller.current_session_id,
541
+ 'mr_robot',
542
+ 'my_action3',
543
+ nil
544
+ )
545
+
546
+ expect {
547
+ controller.step_to_at future_timestamp, slug: 'mr_robot->my_action3'
548
+ }.to_not change(controller.current_session, :get_session)
418
549
  end
419
550
 
420
551
  it "should accept flow and string specified as symbols" do
@@ -425,12 +556,148 @@ describe "Stealth::Controller" do
425
556
  controller.current_service,
426
557
  controller.current_session_id,
427
558
  'mr_robot',
428
- 'my_action3'
559
+ 'my_action3',
560
+ nil
429
561
  )
430
562
 
431
563
  expect {
432
564
  controller.step_to_at future_timestamp, flow: :mr_robot, state: :my_action3
433
- }.to_not change(controller.current_session, :get)
565
+ }.to_not change(controller.current_session, :get_session)
566
+ end
567
+
568
+ it "should pass along the target_id if set on the message" do
569
+ expect(Stealth::ScheduledReplyJob).to receive(:perform_at).with(
570
+ future_timestamp,
571
+ controller.current_service,
572
+ controller.current_session_id,
573
+ 'mr_robot',
574
+ 'my_action3',
575
+ '+18885551212'
576
+ )
577
+
578
+ controller.current_message.target_id = '+18885551212'
579
+ controller.step_to_at future_timestamp, flow: :mr_robot, state: :my_action3
580
+ end
581
+
582
+ it "should check if an interruption occured" do
583
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
584
+ controller.step_to_at future_timestamp, flow: :mr_robot, state: :my_action3
585
+ end
586
+
587
+ it "should call run_interrupt_action if an interruption occured and return" do
588
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
589
+ expect(controller).to receive(:run_interrupt_action)
590
+ expect(controller.step_to_at(future_timestamp, flow: :mr_robot, state: :my_action3)).to eq :interrupted
591
+ end
592
+ end
593
+
594
+ describe "set_back_to" do
595
+ it "should raise an ArgumentError if a session, flow, or state is not specified" do
596
+ expect {
597
+ controller.set_back_to
598
+ }.to raise_error(ArgumentError)
599
+ end
600
+
601
+ it "should call the flow's first state's controller action when only a flow is provided" do
602
+ expect {
603
+ controller.set_back_to(flow: :mr_robot)
604
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('mr_robot->my_action')
605
+ end
606
+
607
+ it "should call a controller's corresponding action when only a state is provided" do
608
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
609
+
610
+ expect {
611
+ controller.set_back_to(state: :other_action3)
612
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('mr_tron->other_action3')
613
+ end
614
+
615
+ it "should call a controller's corresponding action when a state and flow is provided" do
616
+ expect {
617
+ controller.set_back_to(flow: 'marco', state: 'polo')
618
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('marco->polo')
619
+ end
620
+
621
+ it "should call a controller's corresponding action when a session is provided" do
622
+ allow(controller.current_session).to receive(:flow_string).and_return("mr_robot")
623
+ allow(controller.current_session).to receive(:state_string).and_return("my_action3")
624
+
625
+ expect {
626
+ controller.set_back_to(session: controller.current_session)
627
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('mr_robot->my_action3')
628
+ end
629
+
630
+ it "should call a controller's corresponding action when a session slug is provided" do
631
+ expect {
632
+ controller.set_back_to(slug: 'marco->polo')
633
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('marco->polo')
634
+ end
635
+
636
+ it "should default to the scoped flow if one is not specified" do
637
+ controller.current_session.set_session(new_flow: :mr_tron, new_state: :other_action)
638
+ expect {
639
+ controller.set_back_to(state: 'polo')
640
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.to('mr_tron->polo')
641
+ end
642
+
643
+ it "should overwrite the existing back_to_session if one is already present" do
644
+ $redis.set([controller.current_session_id, 'back_to'].join('-'), 'marco->polo')
645
+ controller.current_session.set_session(new_flow: :mr_tron, new_state: :other_action)
646
+ expect {
647
+ controller.set_back_to(state: 'other_action')
648
+ }.to change{ $redis.get([controller.current_session_id, 'back_to'].join('-')) }.from('marco->polo').to('mr_tron->other_action')
649
+ end
650
+
651
+ it "should check if an interruption occured" do
652
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
653
+ controller.set_back_to flow: :mr_robot, state: :my_action3
654
+ end
655
+
656
+ it "should call run_interrupt_action if an interruption occured and return" do
657
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
658
+ expect(controller).to receive(:run_interrupt_action)
659
+ expect(controller.set_back_to(flow: :mr_robot, state: :my_action3)).to eq :interrupted
660
+ end
661
+ end
662
+
663
+ describe "step_back" do
664
+ let(:back_to_slug) { [controller.current_session_id, 'back_to'].join('-') }
665
+
666
+ it "should raise Stealth::Errors::InvalidStateTransition if back_to_session is not set" do
667
+ $redis.del(back_to_slug)
668
+ expect {
669
+ controller.step_back
670
+ }.to raise_error(Stealth::Errors::InvalidStateTransition)
671
+ end
672
+
673
+ it "should step_to the stored back_to_session" do
674
+ controller.set_back_to(flow: 'marco', state: 'polo')
675
+ back_to_session = Stealth::Session.new(
676
+ id: controller.current_session_id,
677
+ type: :back_to
678
+ )
679
+
680
+ # We need to control the returned session object so the IDs match
681
+ expect(Stealth::Session).to receive(:new).with(
682
+ id: controller.current_session_id,
683
+ type: :back_to
684
+ ).and_return(back_to_session)
685
+ expect(controller).to receive(:step_to).with(session: back_to_session)
686
+
687
+ controller.step_back
688
+ end
689
+
690
+ it "should check if an interruption occured" do
691
+ controller.set_back_to(flow: :mr_robot, state: :my_action3)
692
+ expect(controller).to receive(:interrupt_detected?).and_return(false)
693
+ controller.step_back
694
+ end
695
+
696
+ it "should call run_interrupt_action if an interruption occured and return" do
697
+ controller.set_back_to(flow: :mr_robot, state: :my_action3)
698
+ expect(controller).to receive(:interrupt_detected?).and_return(true)
699
+ expect(controller).to receive(:run_interrupt_action)
700
+ expect(controller.step_back).to eq :interrupted
434
701
  end
435
702
  end
436
703
 
@@ -479,10 +746,144 @@ describe "Stealth::Controller" do
479
746
  end
480
747
 
481
748
  it "should be falsey otherwise" do
749
+ allow(controller).to receive(:flow_controller).and_return(controller)
482
750
  expect(controller.progressed?).to be_falsey
483
751
  controller.action(action: :other_action)
484
752
  expect(controller.progressed?).to be_falsey
485
753
  end
486
754
  end
487
755
 
756
+ describe "do_nothing" do
757
+ it "should set progressed to truthy when called" do
758
+ allow(controller).to receive(:flow_controller).and_return(controller)
759
+ expect(controller.progressed?).to be_falsey
760
+ controller.action(action: :other_action4)
761
+ expect(controller.progressed?).to be_truthy
762
+ end
763
+ end
764
+
765
+ describe "update_session" do
766
+ before(:each) do
767
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
768
+ end
769
+
770
+ it "should set progressed to :updated_session" do
771
+ controller.send(:update_session, flow: :mr_tron, state: :other_action)
772
+ expect(controller.progressed?).to eq :updated_session
773
+ end
774
+
775
+ it "call set_session on the current_session with the new flow and state" do
776
+ controller.send(:update_session, flow: :mr_robot, state: :my_action)
777
+ expect(controller.current_session.flow_string).to eq 'mr_robot'
778
+ expect(controller.current_session.state_string).to eq 'my_action'
779
+ end
780
+
781
+ it "should not call set_session on current_session if the flow and state match" do
782
+ expect_any_instance_of(Stealth::Session).to_not receive(:set_session)
783
+ controller.send(:update_session, flow: :mr_tron, state: :other_action)
784
+ end
785
+ end
786
+
787
+ describe "dev jumps" do
788
+ let!(:dev_env) { ActiveSupport::StringInquirer.new('development') }
789
+
790
+ describe "dev_jump_detected?" do
791
+ it "should return false if the enviornment is not 'development'" do
792
+ expect(Stealth.env).to eq 'test'
793
+ expect(controller.send(:dev_jump_detected?)).to be false
794
+ end
795
+
796
+ it "should return false if the message does not match the jump format" do
797
+ allow(Stealth).to receive(:env).and_return(dev_env)
798
+ controller.current_message.message = 'hello world'
799
+ expect(Stealth.env.development?).to be true
800
+ expect(controller.send(:dev_jump_detected?)).to be false
801
+ end
802
+
803
+ it "should return false if the message looks like an American date" do
804
+ allow(Stealth).to receive(:env).and_return(dev_env)
805
+ controller.current_message.message = '1/23/84'
806
+ expect(Stealth.env.development?).to be true
807
+ expect(controller.send(:dev_jump_detected?)).to be false
808
+ end
809
+
810
+ it "should return false if the message looks like an American date that is zero padded" do
811
+ allow(Stealth).to receive(:env).and_return(dev_env)
812
+ controller.current_message.message = '01/23/1984'
813
+ expect(Stealth.env.development?).to be true
814
+ expect(controller.send(:dev_jump_detected?)).to be false
815
+ end
816
+
817
+ describe "with a dev jump message" do
818
+ before(:each) do
819
+ expect(controller).to receive(:handle_dev_jump).and_return(true)
820
+ expect(Stealth).to receive(:env).and_return(dev_env)
821
+ end
822
+
823
+ it "should return true if the message is in the format /flow/state" do
824
+ controller.current_message.message = '/mr_robot/my_action'
825
+ expect(controller.send(:dev_jump_detected?)).to be true
826
+ end
827
+
828
+ it "should return true if the message is in the format /flow" do
829
+ controller.current_message.message = '/mr_robot'
830
+ expect(controller.send(:dev_jump_detected?)).to be true
831
+ end
832
+
833
+ it "should return true if the message is in the format //state" do
834
+ controller.current_message.message = '//my_action'
835
+ expect(controller.send(:dev_jump_detected?)).to be true
836
+ end
837
+ end
838
+ end
839
+
840
+ describe "handle_dev_jump" do
841
+ it "should handle messages in the format /flow/state" do
842
+ controller.current_message.message = '/mr_robot/my_action'
843
+ expect(controller).to receive(:step_to).with(flow: 'mr_robot', state: 'my_action')
844
+ controller.send(:handle_dev_jump)
845
+ end
846
+
847
+ it "should handle messages in the format /flow" do
848
+ controller.current_message.message = '/mr_robot'
849
+ expect(controller).to receive(:step_to).with(flow: 'mr_robot', state: nil)
850
+ controller.send(:handle_dev_jump)
851
+ end
852
+
853
+ it "should handle messages in the format //state" do
854
+ controller.current_message.message = '//my_action'
855
+ expect(controller).to receive(:step_to).with(flow: nil, state: 'my_action')
856
+ controller.send(:handle_dev_jump)
857
+ end
858
+ end
859
+
860
+ describe "session locking" do
861
+ before(:each) do
862
+ allow(MrTronsController).to receive(:new).and_return(controller)
863
+ end
864
+
865
+ it "should lock and then unlock a session when a do_nothing action is called" do
866
+ expect(controller).to receive(:lock_session!).once
867
+ expect(controller).to receive(:release_lock!).once
868
+ controller.action(action: :other_action4)
869
+ end
870
+
871
+ it "should lock and then unlock a session twice when an action steps to another" do
872
+ expect(controller).to receive(:lock_session!).twice
873
+ expect(controller).to receive(:release_lock!).twice
874
+ controller.action(action: :other_action2)
875
+ end
876
+
877
+ it 'should still release the lock even if an action raises' do
878
+ expect(controller).to receive(:release_lock!).once
879
+ controller.action(action: :broken_action)
880
+ end
881
+
882
+ it 'should still release the lock if an action steps to an unknown flow->state' do
883
+ expect(controller).to receive(:release_lock!).once
884
+ controller.action(action: :parts_unknown)
885
+ end
886
+ end
887
+ end
888
+
488
889
  end