xip 0.0.1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +116 -0
  3. data/.gitignore +12 -0
  4. data/CHANGELOG.md +135 -0
  5. data/Gemfile +4 -1
  6. data/Gemfile.lock +65 -15
  7. data/LICENSE +6 -4
  8. data/README.md +51 -1
  9. data/VERSION +1 -0
  10. data/bin/xip +3 -11
  11. data/lib/xip.rb +1 -3
  12. data/lib/xip/base.rb +189 -0
  13. data/lib/xip/cli.rb +273 -0
  14. data/lib/xip/cli_base.rb +24 -0
  15. data/lib/xip/commands/command.rb +13 -0
  16. data/lib/xip/commands/console.rb +74 -0
  17. data/lib/xip/commands/server.rb +63 -0
  18. data/lib/xip/configuration.rb +56 -0
  19. data/lib/xip/controller/callbacks.rb +63 -0
  20. data/lib/xip/controller/catch_all.rb +84 -0
  21. data/lib/xip/controller/controller.rb +274 -0
  22. data/lib/xip/controller/dev_jumps.rb +40 -0
  23. data/lib/xip/controller/dynamic_delay.rb +61 -0
  24. data/lib/xip/controller/helpers.rb +128 -0
  25. data/lib/xip/controller/interrupt_detect.rb +99 -0
  26. data/lib/xip/controller/messages.rb +283 -0
  27. data/lib/xip/controller/nlp.rb +49 -0
  28. data/lib/xip/controller/replies.rb +281 -0
  29. data/lib/xip/controller/unrecognized_message.rb +61 -0
  30. data/lib/xip/core_ext.rb +5 -0
  31. data/lib/xip/core_ext/numeric.rb +10 -0
  32. data/lib/xip/core_ext/string.rb +18 -0
  33. data/lib/xip/dispatcher.rb +68 -0
  34. data/lib/xip/errors.rb +55 -0
  35. data/lib/xip/flow/base.rb +69 -0
  36. data/lib/xip/flow/specification.rb +56 -0
  37. data/lib/xip/flow/state.rb +82 -0
  38. data/lib/xip/generators/builder.rb +41 -0
  39. data/lib/xip/generators/builder/.gitignore +30 -0
  40. data/lib/xip/generators/builder/Gemfile +19 -0
  41. data/lib/xip/generators/builder/Procfile.dev +2 -0
  42. data/lib/xip/generators/builder/README.md +9 -0
  43. data/lib/xip/generators/builder/Rakefile +2 -0
  44. data/lib/xip/generators/builder/bot/controllers/bot_controller.rb +55 -0
  45. data/lib/xip/generators/builder/bot/controllers/catch_alls_controller.rb +21 -0
  46. data/lib/xip/generators/builder/bot/controllers/concerns/.keep +0 -0
  47. data/lib/xip/generators/builder/bot/controllers/goodbyes_controller.rb +9 -0
  48. data/lib/xip/generators/builder/bot/controllers/hellos_controller.rb +9 -0
  49. data/lib/xip/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  50. data/lib/xip/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  51. data/lib/xip/generators/builder/bot/helpers/bot_helper.rb +2 -0
  52. data/lib/xip/generators/builder/bot/models/bot_record.rb +3 -0
  53. data/lib/xip/generators/builder/bot/models/concerns/.keep +0 -0
  54. data/lib/xip/generators/builder/bot/replies/catch_alls/level1.yml +2 -0
  55. data/lib/xip/generators/builder/bot/replies/goodbyes/say_goodbye.yml +2 -0
  56. data/lib/xip/generators/builder/bot/replies/hellos/say_hello.yml +2 -0
  57. data/lib/xip/generators/builder/config.ru +4 -0
  58. data/lib/xip/generators/builder/config/boot.rb +6 -0
  59. data/lib/xip/generators/builder/config/database.yml +25 -0
  60. data/lib/xip/generators/builder/config/environment.rb +2 -0
  61. data/lib/xip/generators/builder/config/flow_map.rb +25 -0
  62. data/lib/xip/generators/builder/config/initializers/autoload.rb +8 -0
  63. data/lib/xip/generators/builder/config/initializers/inflections.rb +16 -0
  64. data/lib/xip/generators/builder/config/puma.rb +25 -0
  65. data/lib/xip/generators/builder/config/services.yml +35 -0
  66. data/lib/xip/generators/builder/config/sidekiq.yml +3 -0
  67. data/lib/xip/generators/builder/db/seeds.rb +7 -0
  68. data/lib/xip/generators/generate.rb +39 -0
  69. data/lib/xip/generators/generate/flow/controllers/controller.tt +7 -0
  70. data/lib/xip/generators/generate/flow/helpers/helper.tt +3 -0
  71. data/lib/xip/generators/generate/flow/replies/ask_example.tt +9 -0
  72. data/lib/xip/helpers/redis.rb +40 -0
  73. data/lib/xip/jobs.rb +9 -0
  74. data/lib/xip/lock.rb +82 -0
  75. data/lib/xip/logger.rb +9 -3
  76. data/lib/xip/migrations/configurator.rb +73 -0
  77. data/lib/xip/migrations/generators.rb +16 -0
  78. data/lib/xip/migrations/railtie_config.rb +14 -0
  79. data/lib/xip/migrations/tasks.rb +43 -0
  80. data/lib/xip/nlp/client.rb +21 -0
  81. data/lib/xip/nlp/result.rb +56 -0
  82. data/lib/xip/reloader.rb +89 -0
  83. data/lib/xip/reply.rb +36 -0
  84. data/lib/xip/scheduled_reply.rb +18 -0
  85. data/lib/xip/server.rb +63 -0
  86. data/lib/xip/service_message.rb +17 -0
  87. data/lib/xip/service_reply.rb +44 -0
  88. data/lib/xip/services/base_client.rb +24 -0
  89. data/lib/xip/services/base_message_handler.rb +27 -0
  90. data/lib/xip/services/base_reply_handler.rb +72 -0
  91. data/lib/xip/services/jobs/handle_message_job.rb +21 -0
  92. data/lib/xip/session.rb +203 -0
  93. data/lib/xip/version.rb +7 -1
  94. data/logo.svg +17 -0
  95. data/spec/configuration_spec.rb +93 -0
  96. data/spec/controller/callbacks_spec.rb +217 -0
  97. data/spec/controller/catch_all_spec.rb +154 -0
  98. data/spec/controller/controller_spec.rb +889 -0
  99. data/spec/controller/dynamic_delay_spec.rb +70 -0
  100. data/spec/controller/helpers_spec.rb +119 -0
  101. data/spec/controller/interrupt_detect_spec.rb +171 -0
  102. data/spec/controller/messages_spec.rb +744 -0
  103. data/spec/controller/nlp_spec.rb +93 -0
  104. data/spec/controller/replies_spec.rb +694 -0
  105. data/spec/controller/unrecognized_message_spec.rb +168 -0
  106. data/spec/dispatcher_spec.rb +79 -0
  107. data/spec/flow/flow_spec.rb +82 -0
  108. data/spec/flow/state_spec.rb +109 -0
  109. data/spec/helpers/redis_spec.rb +77 -0
  110. data/spec/lock_spec.rb +100 -0
  111. data/spec/nlp/client_spec.rb +23 -0
  112. data/spec/nlp/result_spec.rb +57 -0
  113. data/spec/replies/hello.yml.erb +15 -0
  114. data/spec/replies/messages/say_hola.yml+facebook.erb +6 -0
  115. data/spec/replies/messages/say_hola.yml+twilio.erb +6 -0
  116. data/spec/replies/messages/say_hola.yml.erb +6 -0
  117. data/spec/replies/messages/say_howdy_with_dynamic.yml +79 -0
  118. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  119. data/spec/replies/messages/say_offer.yml +6 -0
  120. data/spec/replies/messages/say_offer_with_dynamic.yml +6 -0
  121. data/spec/replies/messages/say_oi.yml.erb +15 -0
  122. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  123. data/spec/replies/messages/say_randomize_text.yml +10 -0
  124. data/spec/replies/messages/say_yo.yml +6 -0
  125. data/spec/replies/messages/say_yo.yml+twitter +6 -0
  126. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  127. data/spec/reply_spec.rb +61 -0
  128. data/spec/scheduled_reply_spec.rb +23 -0
  129. data/spec/service_reply_spec.rb +92 -0
  130. data/spec/session_spec.rb +366 -0
  131. data/spec/spec_helper.rb +22 -66
  132. data/spec/support/alternate_helpers/foo_helper.rb +5 -0
  133. data/spec/support/controllers/vaders_controller.rb +24 -0
  134. data/spec/support/helpers/fun/games_helper.rb +7 -0
  135. data/spec/support/helpers/fun/pdf_helper.rb +7 -0
  136. data/spec/support/helpers/standalone_helper.rb +5 -0
  137. data/spec/support/helpers_typo/users_helper.rb +2 -0
  138. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  139. data/spec/support/nlp_clients/luis.rb +9 -0
  140. data/spec/support/nlp_results/luis_result.rb +163 -0
  141. data/spec/support/sample_messages.rb +66 -0
  142. data/spec/support/services.yml +31 -0
  143. data/spec/support/services_with_erb.yml +31 -0
  144. data/spec/version_spec.rb +16 -0
  145. data/xip.gemspec +25 -14
  146. metadata +320 -18
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ $history = []
6
+
7
+ class BotController < Xip::Controller
8
+ before_action :fetch_user_name
9
+
10
+ def some_action
11
+ step_to flow: 'flow_tester', state: 'my_action'
12
+ end
13
+
14
+ def other_action
15
+ step_to flow: 'other_flow_tester', state: 'other_action'
16
+ end
17
+
18
+ def halted_action
19
+ step_to flow: 'flow_tester', state: 'my_action2'
20
+ end
21
+
22
+ def filtered_action
23
+ step_to flow: 'flow_tester', state: 'my_action3'
24
+ end
25
+
26
+ def some_other_action2
27
+ step_to flow: 'other_flow_tester', state: 'other_action2'
28
+ end
29
+
30
+ def some_other_action3
31
+ step_to flow: 'other_flow_tester', state: 'other_action3'
32
+ end
33
+
34
+ def some_other_action4
35
+ step_to flow: 'other_flow_tester', state: 'other_action4'
36
+ end
37
+
38
+ def some_other_action5
39
+ step_to flow: 'other_flow_tester', state: 'other_action5'
40
+ end
41
+
42
+ private
43
+
44
+ def fetch_user_name
45
+ $history << "fetched user name"
46
+ end
47
+ end
48
+
49
+ class FlowTestersController < BotController
50
+ before_action :test_before_halting, only: :my_action2
51
+ before_action :test_action
52
+ before_action :test_filtering, except: [:my_action, :my_action2]
53
+
54
+ attr_reader :action_ran
55
+
56
+ def my_action
57
+
58
+ end
59
+
60
+ def my_action2
61
+
62
+ end
63
+
64
+ def my_action3
65
+
66
+ end
67
+
68
+ protected
69
+
70
+ def test_action
71
+ $history << "tested action"
72
+ end
73
+
74
+ def test_before_halting
75
+ throw(:abort)
76
+ end
77
+
78
+ def test_filtering
79
+ $history << "filtered"
80
+ end
81
+
82
+ def test_after_halting
83
+ $history << "after action ran"
84
+ end
85
+ end
86
+
87
+ class OtherFlowTestersController < BotController
88
+ after_action :after_action1, only: :other_action2
89
+ after_action :after_action2, only: :other_action2
90
+
91
+ before_action :run_halt, only: [:other_action3, :other_action5]
92
+ after_action :after_action3, only: :other_action3
93
+
94
+ around_action :run_around_filter, only: [:other_action4, :other_action5]
95
+
96
+ def other_action
97
+
98
+ end
99
+
100
+ def other_action2
101
+
102
+ end
103
+
104
+ def other_action3
105
+
106
+ end
107
+
108
+ def other_action4
109
+
110
+ end
111
+
112
+ def other_action5
113
+
114
+ end
115
+
116
+ private
117
+
118
+ def after_action1
119
+ $history << "after action 1"
120
+ end
121
+
122
+ def after_action2
123
+ $history << "after action 2"
124
+ end
125
+
126
+ def run_halt
127
+ throw(:abort)
128
+ end
129
+
130
+ def run_around_filter
131
+ $history << "around before"
132
+ yield
133
+ $history << "around after"
134
+ end
135
+ end
136
+
137
+ class FlowMap
138
+ include Xip::Flow
139
+
140
+ flow :flow_tester do
141
+ state :my_action
142
+ state :my_action2
143
+ state :my_action3
144
+ end
145
+
146
+ flow :other_flow_tester do
147
+ state :other_action
148
+ state :other_action2
149
+ state :other_action3
150
+ state :other_action4
151
+ state :other_action5
152
+ end
153
+ end
154
+
155
+ describe "Xip::Controller callbacks" do
156
+
157
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
158
+
159
+ before(:each) do
160
+ $history = []
161
+ end
162
+
163
+ describe "before_action" do
164
+ it "should fire the callback on the parent class" do
165
+ controller = BotController.new(service_message: facebook_message.message_with_text)
166
+ controller.other_action
167
+ expect($history).to eq ["fetched user name"]
168
+ end
169
+
170
+ it "should fire the callback on a child class" do
171
+ controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
172
+ controller.some_action
173
+ expect($history).to eq ["fetched user name", "tested action"]
174
+ end
175
+
176
+ it "should halt the callback chain when :abort is thrown" do
177
+ controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
178
+ controller.halted_action
179
+ expect($history).to eq ["fetched user name"]
180
+ end
181
+
182
+ it "should respect 'unless' filter" do
183
+ controller = FlowTestersController.new(service_message: facebook_message.message_with_text)
184
+ controller.filtered_action
185
+ expect($history).to eq ["fetched user name", "tested action", "filtered"]
186
+ end
187
+ end
188
+
189
+ describe "after_action" do
190
+ it "should fire the after callbacks in reverse order" do
191
+ controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
192
+ controller.some_other_action2
193
+ expect($history).to eq ["fetched user name", "after action 2", "after action 1"]
194
+ end
195
+
196
+ it "should not fire after callbacks if a before callback throws an :abort" do
197
+ controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
198
+ controller.some_other_action3
199
+ expect($history).to eq ["fetched user name"]
200
+ end
201
+ end
202
+
203
+ describe "around_action" do
204
+ it "should fire the around callback before and after" do
205
+ controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
206
+ controller.some_other_action4
207
+ expect($history).to eq ["fetched user name", "around before", "around after"]
208
+ end
209
+
210
+ it "should not fire the around callback if a before callback throws abort" do
211
+ controller = OtherFlowTestersController.new(service_message: facebook_message.message_with_text)
212
+ controller.some_other_action5
213
+ expect($history).to eq ["fetched user name"]
214
+ end
215
+ end
216
+
217
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Controller::CatchAll" do
6
+ $msg = nil
7
+
8
+ class StubbedCatchAllsController < Xip::Controller
9
+ def level1
10
+ $msg = current_message
11
+ do_nothing
12
+ end
13
+
14
+ def level2
15
+ do_nothing
16
+ end
17
+
18
+ def level3
19
+ do_nothing
20
+ end
21
+ end
22
+
23
+ class FlowMap
24
+ include Xip::Flow
25
+
26
+ flow :vader do
27
+ state :my_action
28
+ state :my_action2
29
+ state :my_action3
30
+ state :action_with_unrecognized_msg
31
+ state :action_with_unrecognized_match
32
+ end
33
+
34
+ flow :catch_all do
35
+ state :level1
36
+ state :level2
37
+ state :level3
38
+ end
39
+ end
40
+
41
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
42
+ let(:controller) { VadersController.new(service_message: facebook_message.message_with_text) }
43
+
44
+ describe "when a CatchAll flow is defined" do
45
+ before(:each) do
46
+ stub_const("CatchAllsController", StubbedCatchAllsController)
47
+ end
48
+
49
+ after(:each) do
50
+ $redis.flushdb
51
+ end
52
+
53
+ it "should step_to catch_all->level1 when a StandardError is raised" do
54
+ controller.current_session.session = Xip::Session.canonical_session_slug(flow: 'vader', state: 'my_action')
55
+ controller.action(action: :my_action)
56
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level1')
57
+ end
58
+
59
+ it "should step_to catch_all->level1 when an action doesn't progress the flow" do
60
+ controller.current_session.session = Xip::Session.canonical_session_slug(flow: 'vader', state: 'my_action2')
61
+ controller.action(action: :my_action2)
62
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level1')
63
+ end
64
+
65
+ it "should step_to catch_all->level2 when an action raises back to back" do
66
+ controller.step_to flow: :vader, state: :my_action
67
+ controller.step_to flow: :vader, state: :my_action
68
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level2')
69
+ end
70
+
71
+ it "should step_to catch_all->level3 when an action raises back to back to back" do
72
+ controller.step_to flow: :vader, state: :my_action
73
+ controller.step_to flow: :vader, state: :my_action
74
+ controller.step_to flow: :vader, state: :my_action
75
+ expect($redis.get(controller.current_session.session_key)).to eq('catch_all->level3')
76
+ end
77
+
78
+ it "should just stop after the maximum number of catch_all levels have been reached" do
79
+ controller.step_to flow: :vader, state: :my_action
80
+ controller.step_to flow: :vader, state: :my_action
81
+ controller.step_to flow: :vader, state: :my_action
82
+ controller.step_to flow: :vader, state: :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
+ '/xip/lib/xip/controller/controller.rb',
99
+ '/xip/lib/xip/controller/catch_all.rb',
100
+ ]
101
+ e
102
+ }
103
+
104
+ before(:each) do
105
+ controller.current_session.session = Xip::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(Xip::Logger).to receive(:l).with(topic: 'catch_all', message: "[Level 1] for user #{facebook_message.sender_id} OpenStruct\noops\n/xip/lib/xip/controller/controller.rb\n/xip/lib/xip/controller/catch_all.rb")
119
+ expect(Xip::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 = Xip::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(Xip::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(Xip::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
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,889 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Controller" do
6
+
7
+ class MrRobotsController < Xip::Controller
8
+ def my_action
9
+ [:success, :my_action]
10
+ end
11
+
12
+ def my_action2
13
+ [:success, :my_action2]
14
+ end
15
+
16
+ def my_action3
17
+ [:success, :my_action3]
18
+ end
19
+ end
20
+
21
+ class MrTronsController < Xip::Controller
22
+ def other_action
23
+
24
+ end
25
+
26
+ def other_action2
27
+ step_to state: :other_action4
28
+ end
29
+
30
+ def other_action3
31
+
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
45
+ end
46
+
47
+ class FlowMap
48
+ include Xip::Flow
49
+
50
+ flow :mr_robot do
51
+ state :my_action
52
+ state :my_action2
53
+ state :my_action3
54
+ end
55
+
56
+ flow :mr_tron do
57
+ state :other_action
58
+ state :other_action2
59
+ state :other_action3
60
+ state :other_action4
61
+ state :broken_action
62
+ state :part_unknown
63
+ state :deprecated_action, redirects_to: :other_action
64
+ state :deprecated_action2, redirects_to: 'mr_robot->my_action'
65
+ end
66
+ end
67
+
68
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
69
+ let(:controller) {
70
+ MrTronsController.new(service_message: facebook_message.message_with_text)
71
+ }
72
+
73
+ describe "convenience methods" do
74
+ it "should make the session ID accessible via current_session_id" do
75
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
76
+
77
+ expect(controller.current_session_id).to eq(facebook_message.sender_id)
78
+ end
79
+
80
+ it "should make the message available in current_message.message" do
81
+ expect(controller.current_message.message).to eq(facebook_message.message)
82
+ end
83
+
84
+ it "should make the payload available in current_message.payload" do
85
+ message_with_payload = facebook_message.message_with_payload
86
+ expect(controller.current_message.payload).to eq(message_with_payload.payload)
87
+ end
88
+
89
+ describe "current_service" do
90
+ let(:twilio_message) { SampleMessage.new(service: 'twilio') }
91
+ let(:controller_with_twilio_message) { MrTronsController.new(service_message: twilio_message.message_with_text) }
92
+
93
+ it "should detect a Facebook message" do
94
+ expect(controller.current_service).to eq('facebook')
95
+ end
96
+
97
+ it "should detect a Twilio message" do
98
+ expect(controller_with_twilio_message.current_service).to eq('twilio')
99
+ end
100
+ end
101
+
102
+ describe "messages with location" do
103
+ let(:message_with_location) { facebook_message.message_with_location }
104
+ let(:controller_with_location) { MrTronsController.new(service_message: message_with_location) }
105
+
106
+ it "should make the location available in current_message.location" do
107
+ expect(controller_with_location.current_message.location).to eq(message_with_location.location)
108
+ end
109
+
110
+ it "should return true for current_message.has_location?" do
111
+ expect(controller_with_location.has_location?).to be true
112
+ end
113
+ end
114
+
115
+ describe "messages with attachments" do
116
+ let(:message_with_attachments) { facebook_message.message_with_attachments }
117
+ let(:controller_with_attachment) { MrTronsController.new(service_message: message_with_attachments) }
118
+
119
+ it "should make the attachments available in current_message.attachments" do
120
+ expect(controller_with_attachment.current_message.attachments).to eq(message_with_attachments.attachments)
121
+ end
122
+
123
+ it "should return true for current_message.has_attachments?" do
124
+ expect(controller_with_attachment.has_attachments?).to be true
125
+ end
126
+ end
127
+ end
128
+
129
+ describe "states with redirect_to specified" do
130
+ it "should step_to the specified redirect state when only a state is specified" do
131
+ controller.current_session.session = Xip::Session.canonical_session_slug(flow: 'mr_tron', state: 'deprecated_action')
132
+ expect(MrTronsController).to receive(:new).and_return(controller)
133
+ expect(controller).to receive(:other_action)
134
+ controller.action(action: :deprecated_action)
135
+ end
136
+
137
+ it "should step_to the specified redirect flow and state when a session is specified" do
138
+ controller.current_session.session = Xip::Session.canonical_session_slug(flow: 'mr_tron', state: 'deprecated_action2')
139
+ mr_robot_controller = MrRobotsController.new(service_message: facebook_message.message_with_text)
140
+
141
+ allow(MrRobotsController).to receive(:new).and_return(mr_robot_controller)
142
+ expect(mr_robot_controller).to receive(:my_action)
143
+ controller.action(action: :deprecated_action2)
144
+ end
145
+
146
+ it "should NOT call the redirected controller action method" do
147
+ controller.current_session.session = Xip::Session.canonical_session_slug(flow: 'mr_tron', state: 'deprecated_action')
148
+ expect(MrTronsController).to receive(:new).and_return(controller)
149
+ expect(controller).to_not receive(:deprecated_action)
150
+ controller.action(action: :deprecated_action)
151
+ end
152
+ end
153
+
154
+ describe "step_to" do
155
+ it "should raise an ArgumentError if a session, flow, or state is not specified" do
156
+ expect {
157
+ controller.step_to
158
+ }.to raise_error(ArgumentError)
159
+ end
160
+
161
+ it "should call the flow's first state's controller action when only a flow is provided" do
162
+ expect_any_instance_of(MrRobotsController).to receive(:my_action)
163
+ controller.step_to flow: "mr_robot"
164
+ end
165
+
166
+ it "should call a controller's corresponding action when only a state is provided" do
167
+ expect_any_instance_of(MrTronsController).to receive(:other_action3)
168
+
169
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
170
+
171
+ controller.step_to state: "other_action3"
172
+ end
173
+
174
+ it "should call a controller's corresponding action when a state and flow is provided" do
175
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
176
+ controller.step_to flow: "mr_robot", state: "my_action3"
177
+ end
178
+
179
+ it "should call a controller's corresponding action when a session is provided" do
180
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
181
+
182
+ allow(controller.current_session).to receive(:flow_string).and_return("mr_robot")
183
+ allow(controller.current_session).to receive(:state_string).and_return("my_action3")
184
+
185
+ controller.step_to session: controller.current_session
186
+ end
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
+
199
+ it "should accept flow and string specified as symbols" do
200
+ expect_any_instance_of(MrRobotsController).to receive(:my_action3)
201
+ controller.step_to flow: :mr_robot, state: :my_action3
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
226
+ end
227
+
228
+ describe "update_session_to" do
229
+ it "should raise an ArgumentError if a session, flow, or state is not specified" do
230
+ expect {
231
+ controller.update_session_to
232
+ }.to raise_error(ArgumentError)
233
+ end
234
+
235
+ it "should update session to flow's first state's controller action when only a flow is provided" do
236
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
237
+
238
+ controller.update_session_to flow: "mr_robot"
239
+ expect(controller.current_session.flow_string).to eq('mr_robot')
240
+ expect(controller.current_session.state_string).to eq('my_action')
241
+ end
242
+
243
+ it "should update session to controller's corresponding action when only a state is provided" do
244
+ expect_any_instance_of(MrTronsController).to_not receive(:other_action3)
245
+
246
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
247
+
248
+ controller.update_session_to state: "other_action3"
249
+ expect(controller.current_session.flow_string).to eq('mr_tron')
250
+ expect(controller.current_session.state_string).to eq('other_action3')
251
+ end
252
+
253
+ it "should update session to controller's corresponding action when a state and flow is provided" do
254
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
255
+
256
+ controller.update_session_to flow: "mr_robot", state: "my_action3"
257
+ expect(controller.current_session.flow_string).to eq('mr_robot')
258
+ expect(controller.current_session.state_string).to eq('my_action3')
259
+ end
260
+
261
+ it "should update session to controller's corresponding action when a session is provided" do
262
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
263
+
264
+ session = Xip::Session.new(id: controller.current_session_id)
265
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
266
+
267
+ controller.update_session_to session: session
268
+ expect(controller.current_session.flow_string).to eq('mr_robot')
269
+ expect(controller.current_session.state_string).to eq('my_action3')
270
+ end
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
+
280
+ it "should accept flow and string specified as symbols" do
281
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action3)
282
+
283
+ controller.update_session_to flow: :mr_robot, state: :my_action3
284
+ expect(controller.current_session.flow_string).to eq('mr_robot')
285
+ expect(controller.current_session.state_string).to eq('my_action3')
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
298
+ end
299
+
300
+ describe "step_to_in" do
301
+ it "should raise an ArgumentError if a session, flow, or state is not specified" do
302
+ expect {
303
+ controller.step_to_in
304
+ }.to raise_error(ArgumentError)
305
+ end
306
+
307
+ it "should raise an ArgumentError if delay is not specifed as an ActiveSupport::Duration" do
308
+ expect {
309
+ controller.step_to_in DateTime.now, flow: 'mr_robot'
310
+ }.to raise_error(ArgumentError)
311
+ end
312
+
313
+ it "should schedule a transition to flow's first state's controller action when only a flow is provided" do
314
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
315
+
316
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in).with(
317
+ 100.seconds,
318
+ controller.current_service,
319
+ controller.current_session_id,
320
+ 'mr_robot',
321
+ 'my_action',
322
+ nil
323
+ )
324
+
325
+ expect {
326
+ controller.step_to_in 100.seconds, flow: "mr_robot"
327
+ }.to_not change(controller.current_session, :get_session)
328
+ end
329
+
330
+ it "should schedule a transition to controller's corresponding action when only a state is provided" do
331
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
332
+
333
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
334
+
335
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in).with(
336
+ 100.seconds,
337
+ controller.current_service,
338
+ controller.current_session_id,
339
+ 'mr_tron',
340
+ 'other_action3',
341
+ nil
342
+ )
343
+
344
+ expect {
345
+ controller.step_to_in 100.seconds, state: "other_action3"
346
+ }.to_not change(controller.current_session, :get_session)
347
+ end
348
+
349
+ it "should update session to controller's corresponding action when a state and flow is provided" do
350
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
351
+
352
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in).with(
353
+ 100.seconds,
354
+ controller.current_service,
355
+ controller.current_session_id,
356
+ 'mr_robot',
357
+ 'my_action3',
358
+ nil
359
+ )
360
+
361
+ expect {
362
+ controller.step_to_in 100.seconds, flow: 'mr_robot', state: "my_action3"
363
+ }.to_not change(controller.current_session, :get_session)
364
+ end
365
+
366
+ it "should update session to controller's corresponding action when a session is provided" do
367
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
368
+
369
+ session = Xip::Session.new(id: controller.current_session_id)
370
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
371
+
372
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in).with(
373
+ 100.seconds,
374
+ controller.current_service,
375
+ controller.current_session_id,
376
+ 'mr_robot',
377
+ 'my_action3',
378
+ nil
379
+ )
380
+
381
+ expect {
382
+ controller.step_to_in 100.seconds, session: session
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(Xip::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)
401
+ end
402
+
403
+ it "should accept flow and string specified as symbols" do
404
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
405
+
406
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in).with(
407
+ 100.seconds,
408
+ controller.current_service,
409
+ controller.current_session_id,
410
+ 'mr_robot',
411
+ 'my_action3',
412
+ nil
413
+ )
414
+
415
+ expect {
416
+ controller.step_to_in 100.seconds, flow: :mr_robot, state: :my_action3
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(Xip::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
443
+ end
444
+ end
445
+
446
+ describe "step_to_at" do
447
+ let(:future_timestamp) { DateTime.now + 10.hours }
448
+
449
+ it "should raise an ArgumentError if a session, flow, or state is not specified" do
450
+ expect {
451
+ controller.step_to_at
452
+ }.to raise_error(ArgumentError)
453
+ end
454
+
455
+ it "should raise an ArgumentError if delay is not specifed as a DateTime" do
456
+ expect {
457
+ controller.step_to_at 100.seconds, flow: 'mr_robot'
458
+ }.to raise_error(ArgumentError)
459
+ end
460
+
461
+ it "should schedule a transition to flow's first state's controller action when only a flow is provided" do
462
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
463
+
464
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at).with(
465
+ future_timestamp,
466
+ controller.current_service,
467
+ controller.current_session_id,
468
+ 'mr_robot',
469
+ 'my_action',
470
+ nil
471
+ )
472
+
473
+ expect {
474
+ controller.step_to_at future_timestamp, flow: "mr_robot"
475
+ }.to_not change(controller.current_session, :get_session)
476
+ end
477
+
478
+ it "should schedule a transition to controller's corresponding action when only a state is provided" do
479
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
480
+
481
+ controller.current_session.set_session(new_flow: 'mr_tron', new_state: 'other_action')
482
+
483
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at).with(
484
+ future_timestamp,
485
+ controller.current_service,
486
+ controller.current_session_id,
487
+ 'mr_tron',
488
+ 'other_action3',
489
+ nil
490
+ )
491
+
492
+ expect {
493
+ controller.step_to_at future_timestamp, state: "other_action3"
494
+ }.to_not change(controller.current_session, :get_session)
495
+ end
496
+
497
+ it "should update session to controller's corresponding action when a state and flow is provided" do
498
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
499
+
500
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at).with(
501
+ future_timestamp,
502
+ controller.current_service,
503
+ controller.current_session_id,
504
+ 'mr_robot',
505
+ 'my_action3',
506
+ nil
507
+ )
508
+
509
+ expect {
510
+ controller.step_to_at future_timestamp, flow: 'mr_robot', state: "my_action3"
511
+ }.to_not change(controller.current_session, :get_session)
512
+ end
513
+
514
+ it "should update session to controller's corresponding action when a session is provided" do
515
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
516
+
517
+ session = Xip::Session.new(id: controller.current_session_id)
518
+ session.set_session(new_flow: 'mr_robot', new_state: 'my_action3')
519
+
520
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at).with(
521
+ future_timestamp,
522
+ controller.current_service,
523
+ controller.current_session_id,
524
+ 'mr_robot',
525
+ 'my_action3',
526
+ nil
527
+ )
528
+
529
+ expect {
530
+ controller.step_to_at future_timestamp, session: session
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(Xip::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)
549
+ end
550
+
551
+ it "should accept flow and string specified as symbols" do
552
+ expect_any_instance_of(MrRobotsController).to_not receive(:my_action)
553
+
554
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at).with(
555
+ future_timestamp,
556
+ controller.current_service,
557
+ controller.current_session_id,
558
+ 'mr_robot',
559
+ 'my_action3',
560
+ nil
561
+ )
562
+
563
+ expect {
564
+ controller.step_to_at future_timestamp, flow: :mr_robot, state: :my_action3
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(Xip::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 Xip::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(Xip::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 = Xip::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(Xip::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
701
+ end
702
+ end
703
+
704
+ describe "progressed?" do
705
+ it "should be truthy if an action calls step_to" do
706
+ expect(controller.progressed?).to be_falsey
707
+ controller.step_to flow: "mr_robot"
708
+ expect(controller.progressed?).to be_truthy
709
+ end
710
+
711
+ it "should be falsey if an action only calls step_to_at" do
712
+ expect(controller.progressed?).to be_falsey
713
+
714
+ expect(Xip::ScheduledReplyJob).to receive(:perform_at)
715
+ controller.step_to_at (DateTime.now + 10.hours), flow: 'mr_robot'
716
+
717
+ expect(controller.progressed?).to be_falsey
718
+ end
719
+
720
+ it "should be falsey if an action only calls step_to_in" do
721
+ expect(controller.progressed?).to be_falsey
722
+
723
+ expect(Xip::ScheduledReplyJob).to receive(:perform_in)
724
+ controller.step_to_in 100.seconds, flow: 'mr_robot'
725
+
726
+ expect(controller.progressed?).to be_falsey
727
+ end
728
+
729
+ it "should be truthy if an action calls update_session_to" do
730
+ expect(controller.progressed?).to be_falsey
731
+ controller.update_session_to flow: "mr_robot"
732
+ expect(controller.progressed?).to be_truthy
733
+ end
734
+
735
+ it "should be truthy if an action sends replies" do
736
+ expect(controller.progressed?).to be_falsey
737
+
738
+ # Stub out a service reply -- we just want send_replies to succeed here
739
+ stubbed_service_reply = double("service_reply")
740
+ allow(controller).to receive(:action_replies).and_return([], :erb)
741
+ allow(stubbed_service_reply).to receive(:replies).and_return([])
742
+ allow(Xip::ServiceReply).to receive(:new).and_return(stubbed_service_reply)
743
+
744
+ controller.send_replies
745
+ expect(controller.progressed?).to be_truthy
746
+ end
747
+
748
+ it "should be falsey otherwise" do
749
+ allow(controller).to receive(:flow_controller).and_return(controller)
750
+ expect(controller.progressed?).to be_falsey
751
+ controller.action(action: :other_action)
752
+ expect(controller.progressed?).to be_falsey
753
+ end
754
+ end
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(Xip::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(Xip.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(Xip).to receive(:env).and_return(dev_env)
798
+ controller.current_message.message = 'hello world'
799
+ expect(Xip.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(Xip).to receive(:env).and_return(dev_env)
805
+ controller.current_message.message = '1/23/84'
806
+ expect(Xip.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(Xip).to receive(:env).and_return(dev_env)
812
+ controller.current_message.message = '01/23/1984'
813
+ expect(Xip.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(Xip).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
+
889
+ end