stealth 1.1.2 → 2.0.0.beta1

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.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +18 -8
  3. data/CHANGELOG.md +100 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +49 -43
  6. data/LICENSE +4 -17
  7. data/README.md +9 -17
  8. data/VERSION +1 -1
  9. data/lib/stealth/base.rb +62 -13
  10. data/lib/stealth/cli.rb +1 -2
  11. data/lib/stealth/commands/console.rb +1 -1
  12. data/lib/stealth/configuration.rb +0 -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 +179 -41
  22. data/lib/stealth/controller/unrecognized_message.rb +62 -0
  23. data/lib/stealth/core_ext.rb +5 -0
  24. data/lib/stealth/{flow/core_ext.rb → core_ext/numeric.rb} +0 -1
  25. data/lib/stealth/core_ext/string.rb +18 -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 +4 -4
  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 +2 -2
  54. data/lib/stealth/session.rb +106 -53
  55. data/spec/configuration_spec.rb +9 -2
  56. data/spec/controller/callbacks_spec.rb +23 -28
  57. data/spec/controller/catch_all_spec.rb +81 -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 -39
  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.dirname(__FILE__) + '/spec_helper')
3
+ require 'spec_helper'
5
4
 
6
5
  describe "Stealth::Configuration" do
7
6
 
@@ -29,6 +28,14 @@ describe "Stealth::Configuration" do
29
28
  it "should handle multiple keys at the root level" do
30
29
  expect(config.twilio_sms.account_sid).to eq parsed_config['twilio_sms']['account_sid']
31
30
  end
31
+
32
+ it "should return nil if the key is not present at the node" do
33
+ expect(config.twilio_sms.api_key).to be nil
34
+ end
35
+
36
+ it "should raise a NoMethodError when accessing multi-levels of missing nodes" do
37
+ expect { config.slack.api_key }.to raise_error(NoMethodError)
38
+ end
32
39
  end
33
40
 
34
41
  describe "config files with ERB" do
@@ -1,57 +1,48 @@
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'
4
+
5
+ $history = []
5
6
 
6
7
  class BotController < Stealth::Controller
7
8
  before_action :fetch_user_name
8
9
 
9
- attr_accessor :record
10
-
11
10
  def some_action
12
- @record = []
13
11
  step_to flow: 'flow_tester', state: 'my_action'
14
12
  end
15
13
 
16
14
  def other_action
17
- @record = []
18
15
  step_to flow: 'other_flow_tester', state: 'other_action'
19
16
  end
20
17
 
21
18
  def halted_action
22
- @record = []
23
19
  step_to flow: 'flow_tester', state: 'my_action2'
24
20
  end
25
21
 
26
22
  def filtered_action
27
- @record = []
28
23
  step_to flow: 'flow_tester', state: 'my_action3'
29
24
  end
30
25
 
31
26
  def some_other_action2
32
- @record = []
33
27
  step_to flow: 'other_flow_tester', state: 'other_action2'
34
28
  end
35
29
 
36
30
  def some_other_action3
37
- @record = []
38
31
  step_to flow: 'other_flow_tester', state: 'other_action3'
39
32
  end
40
33
 
41
34
  def some_other_action4
42
- @record = []
43
35
  step_to flow: 'other_flow_tester', state: 'other_action4'
44
36
  end
45
37
 
46
38
  def some_other_action5
47
- @record = []
48
39
  step_to flow: 'other_flow_tester', state: 'other_action5'
49
40
  end
50
41
 
51
42
  private
52
43
 
53
44
  def fetch_user_name
54
- @record << "fetched user name"
45
+ $history << "fetched user name"
55
46
  end
56
47
  end
57
48
 
@@ -77,7 +68,7 @@ class FlowTestersController < BotController
77
68
  protected
78
69
 
79
70
  def test_action
80
- @record << "tested action"
71
+ $history << "tested action"
81
72
  end
82
73
 
83
74
  def test_before_halting
@@ -85,11 +76,11 @@ class FlowTestersController < BotController
85
76
  end
86
77
 
87
78
  def test_filtering
88
- @record << "filtered"
79
+ $history << "filtered"
89
80
  end
90
81
 
91
82
  def test_after_halting
92
- @record << "after action ran"
83
+ $history << "after action ran"
93
84
  end
94
85
  end
95
86
 
@@ -125,11 +116,11 @@ class OtherFlowTestersController < BotController
125
116
  private
126
117
 
127
118
  def after_action1
128
- @record << "after action 1"
119
+ $history << "after action 1"
129
120
  end
130
121
 
131
122
  def after_action2
132
- @record << "after action 2"
123
+ $history << "after action 2"
133
124
  end
134
125
 
135
126
  def run_halt
@@ -137,9 +128,9 @@ class OtherFlowTestersController < BotController
137
128
  end
138
129
 
139
130
  def run_around_filter
140
- @record << "around before"
131
+ $history << "around before"
141
132
  yield
142
- @record << "around after"
133
+ $history << "around after"
143
134
  end
144
135
  end
145
136
 
@@ -165,29 +156,33 @@ describe "Stealth::Controller callbacks" do
165
156
 
166
157
  let(:facebook_message) { SampleMessage.new(service: 'facebook') }
167
158
 
159
+ before(:each) do
160
+ $history = []
161
+ end
162
+
168
163
  describe "before_action" do
169
164
  it "should fire the callback on the parent class" do
170
165
  controller = BotController.new(service_message: facebook_message.message_with_text)
171
166
  controller.other_action
172
- expect(controller.record).to eq ["fetched user name"]
167
+ expect($history).to eq ["fetched user name"]
173
168
  end
174
169
 
175
170
  it "should fire the callback on a child class" do
176
171
  controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
177
172
  controller.some_action
178
- expect(controller.record).to eq ["fetched user name", "tested action"]
173
+ expect($history).to eq ["fetched user name", "tested action"]
179
174
  end
180
175
 
181
176
  it "should halt the callback chain when :abort is thrown" do
182
177
  controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
183
178
  controller.halted_action
184
- expect(controller.record).to eq ["fetched user name"]
179
+ expect($history).to eq ["fetched user name"]
185
180
  end
186
181
 
187
182
  it "should respect 'unless' filter" do
188
183
  controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
189
184
  controller.filtered_action
190
- expect(controller.record).to eq ["fetched user name", "tested action", "filtered"]
185
+ expect($history).to eq ["fetched user name", "tested action", "filtered"]
191
186
  end
192
187
  end
193
188
 
@@ -195,13 +190,13 @@ describe "Stealth::Controller callbacks" do
195
190
  it "should fire the after callbacks in reverse order" do
196
191
  controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
197
192
  controller.some_other_action2
198
- expect(controller.record).to eq ["fetched user name", "after action 2", "after action 1"]
193
+ expect($history).to eq ["fetched user name", "after action 2", "after action 1"]
199
194
  end
200
195
 
201
196
  it "should not fire after callbacks if a before callback throws an :abort" do
202
197
  controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
203
198
  controller.some_other_action3
204
- expect(controller.record).to eq ["fetched user name"]
199
+ expect($history).to eq ["fetched user name"]
205
200
  end
206
201
  end
207
202
 
@@ -209,13 +204,13 @@ describe "Stealth::Controller callbacks" do
209
204
  it "should fire the around callback before and after" do
210
205
  controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
211
206
  controller.some_other_action4
212
- expect(controller.record).to eq ["fetched user name", "around before", "around after"]
207
+ expect($history).to eq ["fetched user name", "around before", "around after"]
213
208
  end
214
209
 
215
210
  it "should not fire the around callback if a before callback throws abort" do
216
211
  controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
217
212
  controller.some_other_action5
218
- expect(controller.record).to eq ["fetched user name"]
213
+ expect($history).to eq ["fetched user name"]
219
214
  end
220
215
  end
221
216
 
@@ -1,35 +1,22 @@
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::CatchAll" do
7
-
8
- class VadersController < Stealth::Controller
9
- def my_action
10
- raise "oops"
11
- end
12
-
13
- def my_action2
14
-
15
- end
16
-
17
- def my_action3
18
-
19
- end
20
- end
6
+ $msg = nil
21
7
 
22
8
  class StubbedCatchAllsController < Stealth::Controller
23
9
  def level1
24
-
10
+ $msg = current_message
11
+ do_nothing
25
12
  end
26
13
 
27
14
  def level2
28
-
15
+ do_nothing
29
16
  end
30
17
 
31
18
  def level3
32
-
19
+ do_nothing
33
20
  end
34
21
  end
35
22
 
@@ -40,6 +27,8 @@ describe "Stealth::Controller::CatchAll" do
40
27
  state :my_action
41
28
  state :my_action2
42
29
  state :my_action3
30
+ state :action_with_unrecognized_msg
31
+ state :action_with_unrecognized_match
43
32
  end
44
33
 
45
34
  flow :catch_all do
@@ -64,30 +53,26 @@ describe "Stealth::Controller::CatchAll" do
64
53
  it "should step_to catch_all->level1 when a StandardError is raised" do
65
54
  controller.current_session.session = Stealth::Session.canonical_session_slug(flow: 'vader', state: 'my_action')
66
55
  controller.action(action: :my_action)
67
- expect(controller.current_session.flow_string).to eq("catch_all")
68
- expect(controller.current_session.state_string).to eq("level1")
56
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level1')
69
57
  end
70
58
 
71
59
  it "should step_to catch_all->level1 when an action doesn't progress the flow" do
72
60
  controller.current_session.session = Stealth::Session.canonical_session_slug(flow: 'vader', state: 'my_action2')
73
61
  controller.action(action: :my_action2)
74
- expect(controller.current_session.flow_string).to eq("catch_all")
75
- expect(controller.current_session.state_string).to eq("level1")
62
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level1')
76
63
  end
77
64
 
78
65
  it "should step_to catch_all->level2 when an action raises back to back" do
79
66
  controller.step_to flow: :vader, state: :my_action
80
67
  controller.step_to flow: :vader, state: :my_action
81
- expect(controller.current_session.flow_string).to eq("catch_all")
82
- expect(controller.current_session.state_string).to eq("level2")
68
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level2')
83
69
  end
84
70
 
85
71
  it "should step_to catch_all->level3 when an action raises back to back to back" do
86
72
  controller.step_to flow: :vader, state: :my_action
87
73
  controller.step_to flow: :vader, state: :my_action
88
74
  controller.step_to flow: :vader, state: :my_action
89
- expect(controller.current_session.flow_string).to eq("catch_all")
90
- expect(controller.current_session.state_string).to eq("level3")
75
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level3')
91
76
  end
92
77
 
93
78
  it "should just stop after the maximum number of catch_all levels have been reached" do
@@ -95,8 +80,75 @@ describe "Stealth::Controller::CatchAll" do
95
80
  controller.step_to flow: :vader, state: :my_action
96
81
  controller.step_to flow: :vader, state: :my_action
97
82
  controller.step_to flow: :vader, state: :my_action
98
- expect(controller.current_session.flow_string).to eq("vader")
99
- expect(controller.current_session.state_string).to eq("my_action")
83
+ expect($redis.get(controller.current_session.session_key)).to eq('vader->my_action')
84
+ end
85
+
86
+ it "should NOT run the catch_all if do_nothing is called" do
87
+ controller.current_session.set_session(new_flow: 'vader', new_state: 'my_action3')
88
+ controller.action(action: :my_action3)
89
+ expect($redis.get(controller.current_session.session_key)).to eq('vader->my_action3')
90
+ end
91
+
92
+ describe "catch_alls from within catch_all flow" do
93
+ let(:e) {
94
+ e = OpenStruct.new
95
+ e.class = RuntimeError
96
+ e.message = 'oops'
97
+ e.backtrace = [
98
+ '/stealth/lib/stealth/controller/controller.rb',
99
+ '/stealth/lib/stealth/controller/catch_all.rb',
100
+ ]
101
+ e
102
+ }
103
+
104
+ before(:each) do
105
+ controller.current_session.session = Stealth::Session.canonical_session_slug(flow: 'catch_all', state: 'level1')
106
+ end
107
+
108
+ it "should not step_to to catch_all" do
109
+ expect(controller).to_not receive(:step_to)
110
+ controller.run_catch_all(err: e)
111
+ end
112
+
113
+ it "should return false" do
114
+ expect(controller.run_catch_all(err: e)).to be false
115
+ end
116
+
117
+ it "should log the error message" do
118
+ expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "[Level 1] for user #{facebook_message.sender_id} OpenStruct\noops\n/stealth/lib/stealth/controller/controller.rb\n/stealth/lib/stealth/controller/catch_all.rb")
119
+ expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "CatchAll triggered for user #{facebook_message.sender_id} from within CatchAll; ignoring.")
120
+ controller.run_catch_all(err: e)
121
+ end
122
+ end
123
+
124
+ describe "catch_all_reason" do
125
+ before(:each) do
126
+ @session = Stealth::Session.new(id: controller.current_session_id)
127
+ @session.set_session(new_flow: 'vader', new_state: 'my_action2')
128
+ end
129
+
130
+ after(:each) do
131
+ $msg = nil
132
+ end
133
+
134
+ it 'should have access to the error raised in current_message.catch_all_reason' do
135
+ controller.action(action: :my_action)
136
+ expect($msg.catch_all_reason).to be_a(Hash)
137
+ expect($msg.catch_all_reason[:err]).to eq(RuntimeError)
138
+ expect($msg.catch_all_reason[:err_msg]).to eq('oops')
139
+ end
140
+
141
+ it 'should have the correct error when handle_message fails to recognize a message' do
142
+ controller.action(action: :action_with_unrecognized_msg)
143
+ expect($msg.catch_all_reason[:err]).to eq(Stealth::Errors::UnrecognizedMessage)
144
+ expect($msg.catch_all_reason[:err_msg]).to eq("The reply '#{facebook_message.message_with_text.message}' was not recognized.")
145
+ end
146
+
147
+ it 'should have the correct error when get_match fails to recognize a message' do
148
+ controller.action(action: :action_with_unrecognized_match)
149
+ expect($msg.catch_all_reason[:err]).to eq(Stealth::Errors::UnrecognizedMessage)
150
+ expect($msg.catch_all_reason[:err_msg]).to eq("The reply '#{facebook_message.message_with_text.message}' was not recognized.")
151
+ end
100
152
  end
101
153
  end
102
154
  end
@@ -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