xip 0.0.1 → 2.0.0.beta2

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 (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