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,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Xip::Controller::Nlp do
6
+
7
+ let(:fb_message) { SampleMessage.new(service: 'facebook') }
8
+ let(:controller) { VadersController.new(service_message: fb_message.message_with_text) }
9
+
10
+ describe 'nlp_client_klass' do
11
+ it 'should return the correct class for LUIS' do
12
+ config_dbl = double('Xip Config', nlp_integration: :luis).as_null_object
13
+ allow(Xip).to receive(:config).and_return(config_dbl)
14
+ expect(controller.send(:nlp_client_klass)).to eq Xip::Nlp::Luis::Client
15
+ end
16
+
17
+ it 'should return the correct class for Dialogflow' do
18
+ config_dbl = double('Xip Config', nlp_integration: :dialogflow).as_null_object
19
+ allow(Xip).to receive(:config).and_return(config_dbl)
20
+ expect(controller.send(:nlp_client_klass)).to eq Xip::Nlp::Dialogflow::Client
21
+ end
22
+
23
+ it 'should raise an error if it cannot locate a class' do
24
+ config_dbl = double('Xip Config', nlp_integration: :unknown).as_null_object
25
+ allow(Xip).to receive(:config).and_return(config_dbl)
26
+ expect {
27
+ controller.send(:nlp_client_klass)
28
+ }.to raise_error(NameError)
29
+ end
30
+ end
31
+
32
+ describe 'perform_nlp!' do
33
+
34
+ describe 'NLP has not yet been configured' do
35
+ it 'should raise Xip::Errors::ConfigurationError' do
36
+ config_dbl = double('Xip Config', nlp_integration: nil).as_null_object
37
+ allow(Xip).to receive(:config).and_return(config_dbl)
38
+
39
+ expect {
40
+ controller.perform_nlp!
41
+ }.to raise_error(Xip::Errors::ConfigurationError)
42
+ end
43
+ end
44
+
45
+ describe 'NLP has been configured' do
46
+ before(:each) do
47
+ config_dbl = double('Xip Config', nlp_integration: :luis).as_null_object
48
+ @luis_client_dbl = double('LUIS Client')
49
+ allow(Xip).to receive(:config).and_return(config_dbl)
50
+ allow(Xip::Nlp::Luis::Client).to receive(:new).and_return(@luis_client_dbl)
51
+ end
52
+
53
+ let(:nlp_result) { Xip::Nlp::Result.new(result: {}) }
54
+
55
+ it 'should call understand on the NLP client' do
56
+ expect(@luis_client_dbl).to receive(:understand).with(query: 'Hello World!').and_return(nlp_result)
57
+ controller.perform_nlp!
58
+ end
59
+
60
+ it 'should return an Nlp::Result object' do
61
+ expect(@luis_client_dbl).to receive(:understand).with(query: 'Hello World!').and_return(nlp_result)
62
+ expect(controller.perform_nlp!).to eq nlp_result
63
+ end
64
+
65
+ it 'should memoize the understand call' do
66
+ expect(@luis_client_dbl).to receive(:understand).once.with(query: 'Hello World!').and_return(nlp_result)
67
+ controller.perform_nlp!
68
+ controller.perform_nlp!
69
+ controller.perform_nlp!
70
+ end
71
+
72
+ it 'should store the nlp_result for the current controller' do
73
+ expect(@luis_client_dbl).to receive(:understand).once.with(query: 'Hello World!').and_return(nlp_result)
74
+ controller.perform_nlp!
75
+ expect(controller.nlp_result).to eq nlp_result
76
+ end
77
+
78
+ it 'should store the nlp_result for the current service_message' do
79
+ expect(@luis_client_dbl).to receive(:understand).once.with(query: 'Hello World!').and_return(nlp_result)
80
+ controller.perform_nlp!
81
+ expect(controller.current_message.nlp_result).to eq nlp_result
82
+ end
83
+
84
+ it 'should perform the NLP query if it has been cleared out' do
85
+ expect(@luis_client_dbl).to receive(:understand).exactly(2).times.with(query: 'Hello World!').and_return(nlp_result)
86
+ controller.perform_nlp!
87
+ controller.nlp_result = nil
88
+ controller.perform_nlp!
89
+ end
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,694 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Controller replies" do
6
+
7
+ Xip::Controller._replies_path = File.expand_path("../replies", __dir__)
8
+
9
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
10
+ let(:controller) { MessagesController.new(service_message: facebook_message.message_with_text) }
11
+
12
+ # Stub out base Facebook integration
13
+ module Xip
14
+ module Services
15
+ module Facebook
16
+ class ReplyHandler
17
+
18
+ end
19
+
20
+ class Client
21
+
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ class MessagesController < Xip::Controller
28
+ def say_oi
29
+ @first_name = "Presley"
30
+ send_replies
31
+ end
32
+
33
+ def say_offer
34
+ send_replies
35
+ end
36
+
37
+ def say_offer_with_dynamic
38
+ send_replies
39
+ end
40
+
41
+ def say_msgs_without_breaks
42
+ send_replies
43
+ end
44
+
45
+ def say_uh_oh
46
+ send_replies
47
+ end
48
+
49
+ def say_randomize_text
50
+ send_replies
51
+ end
52
+
53
+ def say_randomize_speech
54
+ send_replies
55
+ end
56
+
57
+ def say_custom_reply
58
+ send_replies custom_reply: 'messages/say_offer'
59
+ end
60
+
61
+ def say_howdy_with_dynamic
62
+ send_replies
63
+ end
64
+
65
+ def say_nested_custom_reply
66
+ send_replies custom_reply: 'messages/sub1/sub2/say_nested'
67
+ end
68
+
69
+ def say_inline_reply
70
+ reply = [
71
+ { 'reply_type' => 'text', 'text' => 'Hi, Morty. Welcome to Xip bot...' },
72
+ { 'reply_type' => 'delay', 'duration' => 2 },
73
+ { 'reply_type' => 'text', 'text' => 'We offer users an awesome Ruby framework for building chat bots.' }
74
+ ]
75
+
76
+ send_replies inline: reply
77
+ end
78
+ end
79
+
80
+ describe "missing reply" do
81
+ it "should raise a Xip::Errors::ReplyNotFound" do
82
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
83
+ allow(controller.current_session).to receive(:state_string).and_return("say_uh_oh")
84
+
85
+ expect {
86
+ controller.send_replies
87
+ }.to raise_error(Xip::Errors::ReplyNotFound)
88
+ end
89
+ end
90
+
91
+ describe "class attributes" do
92
+ it "should have altered the _replies_path class attribute" do
93
+ expect(MessagesController._replies_path).to eq(File.expand_path("../replies", __dir__))
94
+ end
95
+
96
+ it "should have altered the _preprocessors class attribute" do
97
+ expect(MessagesController._preprocessors).to eq([:erb])
98
+ end
99
+ end
100
+
101
+ describe "action_replies" do
102
+ it "should select the :erb preprocessor when reply extension is .yml" do
103
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
104
+ allow(controller.current_session).to receive(:state_string).and_return("say_oi")
105
+ file_contents, selected_preprocessor = controller.send(:action_replies)
106
+
107
+ expect(selected_preprocessor).to eq(:erb)
108
+ end
109
+
110
+ it "should select the :none preprocessor when there is no reply extension" do
111
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
112
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
113
+ file_contents, selected_preprocessor = controller.send(:action_replies)
114
+
115
+ expect(selected_preprocessor).to eq(:none)
116
+ end
117
+
118
+ it "should read the reply's file contents" do
119
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
120
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
121
+ file_contents, selected_preprocessor = controller.send(:action_replies)
122
+
123
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_offer.yml", __dir__)))
124
+ end
125
+ end
126
+
127
+ describe "reply with ERB" do
128
+ let(:stubbed_handler) { double("handler") }
129
+ let(:stubbed_client) { double("client") }
130
+
131
+ before(:each) do
132
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
133
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
134
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
135
+ allow(controller.current_session).to receive(:state_string).and_return("say_oi")
136
+ Xip.config.auto_insert_delays = false
137
+ end
138
+
139
+ after(:each) do
140
+ Xip.config.auto_insert_delays = true
141
+ end
142
+
143
+ it "should translate each reply_type in the reply" do
144
+ allow(stubbed_client).to receive(:transmit).and_return(true)
145
+ allow(controller).to receive(:sleep).and_return(true)
146
+
147
+ expect(stubbed_handler).to receive(:text).exactly(3).times
148
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
149
+ controller.say_oi
150
+ end
151
+
152
+ it "should transmit each reply_type in the reply" do
153
+ allow(stubbed_handler).to receive(:text).exactly(3).times
154
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
155
+ allow(controller).to receive(:sleep).and_return(true)
156
+
157
+ expect(stubbed_client).to receive(:transmit).exactly(5).times
158
+ controller.say_oi
159
+ end
160
+
161
+ it "should sleep on delays" do
162
+ allow(stubbed_handler).to receive(:text).exactly(3).times
163
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
164
+ allow(stubbed_client).to receive(:transmit).exactly(5).times
165
+
166
+ expect(controller).to receive(:sleep).exactly(2).times
167
+ controller.say_oi
168
+ end
169
+ end
170
+
171
+ describe "plain reply" do
172
+ let(:stubbed_handler) { double("handler") }
173
+ let(:stubbed_client) { double("client") }
174
+
175
+ before(:each) do
176
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
177
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
178
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
179
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
180
+ Xip.config.auto_insert_delays = false
181
+ end
182
+
183
+ after(:each) do
184
+ Xip.config.auto_insert_delays = true
185
+ end
186
+
187
+ it "should translate each reply_type in the reply" do
188
+ allow(stubbed_client).to receive(:transmit).and_return(true)
189
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
190
+
191
+ expect(stubbed_handler).to receive(:text).exactly(2).times
192
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
193
+ controller.say_offer
194
+ end
195
+
196
+ it "should transmit each reply_type in the reply" do
197
+ allow(stubbed_handler).to receive(:text).exactly(2).times
198
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
199
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
200
+
201
+ expect(stubbed_client).to receive(:transmit).exactly(3).times
202
+ controller.say_offer
203
+ end
204
+
205
+ it "should sleep on delays" do
206
+ allow(stubbed_handler).to receive(:text).exactly(2).times
207
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
208
+ allow(stubbed_client).to receive(:transmit).exactly(3).times
209
+
210
+ expect(controller).to receive(:sleep).exactly(1).times.with(2.0)
211
+ controller.say_offer
212
+ end
213
+ end
214
+
215
+ describe "custom_reply" do
216
+ let(:stubbed_handler) { double("handler") }
217
+ let(:stubbed_client) { double("client") }
218
+
219
+ before(:each) do
220
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
221
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
222
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
223
+ allow(controller.current_session).to receive(:state_string).and_return("say_custom_reply")
224
+ Xip.config.auto_insert_delays = false
225
+ end
226
+
227
+ after(:each) do
228
+ Xip.config.auto_insert_delays = true
229
+ end
230
+
231
+ it "should translate each reply_type in the reply" do
232
+ allow(stubbed_client).to receive(:transmit).and_return(true)
233
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
234
+
235
+ expect(stubbed_handler).to receive(:text).exactly(2).times
236
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
237
+ controller.say_custom_reply
238
+ end
239
+
240
+ it "should transmit each reply_type in the reply" do
241
+ allow(stubbed_handler).to receive(:text).exactly(2).times
242
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
243
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
244
+
245
+ expect(stubbed_client).to receive(:transmit).exactly(3).times
246
+ controller.say_custom_reply
247
+ end
248
+
249
+ it "should sleep on delays" do
250
+ allow(stubbed_handler).to receive(:text).exactly(2).times
251
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
252
+ allow(stubbed_client).to receive(:transmit).exactly(3).times
253
+
254
+ expect(controller).to receive(:sleep).exactly(1).times.with(2.0)
255
+ controller.say_custom_reply
256
+ end
257
+
258
+ it "should correctly load from sub-dirs" do
259
+ expect(stubbed_handler).to receive(:text).exactly(3).times
260
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
261
+ expect(stubbed_client).to receive(:transmit).exactly(5).times
262
+
263
+ expect(controller).to receive(:sleep).exactly(2).times.with(2.0)
264
+ controller.say_nested_custom_reply
265
+ end
266
+ end
267
+
268
+ describe "inline replies" do
269
+ let(:stubbed_handler) { double("handler") }
270
+ let(:stubbed_client) { double("client") }
271
+
272
+ before(:each) do
273
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
274
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
275
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
276
+ allow(controller.current_session).to receive(:state_string).and_return("say_inline_reply")
277
+ Xip.config.auto_insert_delays = false
278
+ end
279
+
280
+ after(:each) do
281
+ Xip.config.auto_insert_delays = true
282
+ end
283
+
284
+ it "should translate each reply_type in the reply" do
285
+ allow(stubbed_client).to receive(:transmit).and_return(true)
286
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
287
+
288
+ expect(stubbed_handler).to receive(:text).exactly(2).times
289
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
290
+ controller.say_inline_reply
291
+ end
292
+
293
+ it "should transmit each reply_type in the reply" do
294
+ allow(stubbed_handler).to receive(:text).exactly(2).times
295
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
296
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
297
+
298
+ expect(stubbed_client).to receive(:transmit).exactly(3).times
299
+ controller.say_inline_reply
300
+ end
301
+
302
+ it "should sleep on delays" do
303
+ allow(stubbed_handler).to receive(:text).exactly(2).times
304
+ allow(stubbed_handler).to receive(:delay).exactly(1).times
305
+ allow(stubbed_client).to receive(:transmit).exactly(3).times
306
+
307
+ expect(controller).to receive(:sleep).exactly(1).times.with(2.0)
308
+ controller.say_inline_reply
309
+ end
310
+ end
311
+
312
+ describe "auto delays" do
313
+ let(:stubbed_handler) { double("handler") }
314
+ let(:stubbed_client) { double("client") }
315
+
316
+ before(:each) do
317
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
318
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
319
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
320
+ allow(controller.current_session).to receive(:state_string).and_return("say_msgs_without_breaks")
321
+ end
322
+
323
+ it "should add two additional delays to a reply without delays" do
324
+ allow(stubbed_client).to receive(:transmit).and_return(true)
325
+ allow(controller).to receive(:sleep).and_return(true)
326
+
327
+ expect(stubbed_handler).to receive(:text).exactly(2).times
328
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
329
+ controller.say_msgs_without_breaks
330
+ end
331
+
332
+ it "should only add a single delay to a reply that already contains a delay" do
333
+ allow(stubbed_client).to receive(:transmit).and_return(true)
334
+ allow(controller).to receive(:sleep).and_return(true)
335
+
336
+ expect(stubbed_handler).to receive(:text).exactly(2).times
337
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
338
+ controller.say_offer
339
+ end
340
+
341
+ it "should not add delays if auto_insert_delays = false" do
342
+ allow(stubbed_client).to receive(:transmit).and_return(true)
343
+ allow(controller).to receive(:sleep).and_return(true)
344
+
345
+ expect(stubbed_handler).to receive(:text).exactly(2).times
346
+ expect(stubbed_handler).to_not receive(:delay)
347
+
348
+ Xip.config.auto_insert_delays = false
349
+ controller.say_msgs_without_breaks
350
+ Xip.config.auto_insert_delays = true
351
+ end
352
+ end
353
+
354
+ describe "session locking" do
355
+ let(:stubbed_handler) { double("handler") }
356
+ let(:stubbed_client) { double("client") }
357
+
358
+ before(:each) do
359
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
360
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
361
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
362
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
363
+ Xip.config.auto_insert_delays = false
364
+ end
365
+
366
+ after(:each) do
367
+ Xip.config.auto_insert_delays = true
368
+ end
369
+
370
+ it "should update the lock for each reply_type in the reply" do
371
+ allow(stubbed_client).to receive(:transmit).and_return(true)
372
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
373
+
374
+ expect(controller).to receive(:lock_session!).exactly(3).times
375
+ expect(stubbed_handler).to receive(:text).exactly(2).times
376
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
377
+ controller.say_offer
378
+ end
379
+
380
+ it "should update the lock position for each reply_type in the reply" do
381
+ allow(stubbed_client).to receive(:transmit).and_return(true)
382
+ allow(controller).to receive(:sleep).and_return(true).with(2.0)
383
+
384
+ expect(controller).to receive(
385
+ :lock_session!
386
+ ).with(
387
+ session_slug: controller.current_session.get_session,
388
+ position: 0
389
+ ).exactly(1).times
390
+
391
+ expect(controller).to receive(
392
+ :lock_session!
393
+ ).with(
394
+ session_slug: controller.current_session.get_session,
395
+ position: 1
396
+ ).exactly(1).times
397
+
398
+ expect(controller).to receive(
399
+ :lock_session!
400
+ ).with(
401
+ session_slug: controller.current_session.get_session,
402
+ position: 2
403
+ ).exactly(1).times
404
+
405
+ expect(stubbed_handler).to receive(:text).exactly(2).times
406
+ expect(stubbed_handler).to receive(:delay).exactly(1).times
407
+ controller.say_offer
408
+ end
409
+
410
+ it "should update the lock position with an offset for each reply_type in the reply" do
411
+ allow(stubbed_client).to receive(:transmit).and_return(true)
412
+ allow(controller).to receive(:sleep).and_return(true)
413
+
414
+ controller.pos = 17 # set the offset
415
+
416
+ expect(controller).to receive(
417
+ :lock_session!
418
+ ).with(
419
+ session_slug: controller.current_session.get_session,
420
+ position: 17
421
+ ).exactly(1).times
422
+
423
+ expect(controller).to receive(
424
+ :lock_session!
425
+ ).with(
426
+ session_slug: controller.current_session.get_session,
427
+ position: 18
428
+ ).exactly(1).times
429
+
430
+ expect(controller).to receive(
431
+ :lock_session!
432
+ ).with(
433
+ session_slug: controller.current_session.get_session,
434
+ position: 19
435
+ ).exactly(1).times
436
+
437
+ expect(controller).to receive(
438
+ :lock_session!
439
+ ).with(
440
+ session_slug: controller.current_session.get_session,
441
+ position: 20
442
+ ).exactly(1).times
443
+
444
+ expect(stubbed_handler).to receive(:cards).exactly(1).time
445
+ expect(stubbed_handler).to receive(:list).exactly(1).time
446
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
447
+ allow(controller.current_session).to receive(:state_string).and_return("say_howdy_with_dynamic")
448
+ controller.say_howdy_with_dynamic
449
+ end
450
+ end
451
+
452
+ describe "dynamic delays" do
453
+ let(:stubbed_handler) { double("handler") }
454
+ let(:stubbed_client) { double("client") }
455
+
456
+ before(:each) do
457
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
458
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
459
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
460
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer_with_dynamic")
461
+ end
462
+
463
+ it "should use the default multiplier if none is set" do
464
+ allow(stubbed_handler).to receive(:text).exactly(2).times
465
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
466
+ allow(stubbed_client).to receive(:transmit).exactly(4).times
467
+
468
+ delay = Xip.config.dynamic_delay_muliplier * Xip::Controller::DynamicDelay::SHORT_DELAY
469
+ expect(controller).to receive(:sleep).exactly(2).times.with(delay)
470
+ controller.say_offer_with_dynamic
471
+ end
472
+
473
+ it "should slow down SHORT_DELAY if dynamic_delay_muliplier > 1" do
474
+ allow(stubbed_handler).to receive(:text).exactly(2).times
475
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
476
+ allow(stubbed_client).to receive(:transmit).exactly(4).times
477
+
478
+ Xip.config.dynamic_delay_muliplier = 5
479
+ delay = Xip.config.dynamic_delay_muliplier * Xip::Controller::DynamicDelay::SHORT_DELAY
480
+ expect(controller).to receive(:sleep).exactly(2).times.with(delay)
481
+ controller.say_offer_with_dynamic
482
+ end
483
+
484
+ it "should speed up SHORT_DELAY if dynamic_delay_muliplier < 1" do
485
+ allow(stubbed_handler).to receive(:text).exactly(2).times
486
+ allow(stubbed_handler).to receive(:delay).exactly(2).times
487
+ allow(stubbed_client).to receive(:transmit).exactly(4).times
488
+
489
+ Xip.config.dynamic_delay_muliplier = 0.1
490
+ delay = Xip.config.dynamic_delay_muliplier * Xip::Controller::DynamicDelay::SHORT_DELAY
491
+ expect(controller).to receive(:sleep).exactly(2).times.with(delay)
492
+ controller.say_offer_with_dynamic
493
+ end
494
+ end
495
+
496
+ describe "variants" do
497
+ let(:twilio_message) { SampleMessage.new(service: 'twilio') }
498
+ let(:twilio_controller) { MessagesController.new(service_message: twilio_message.message_with_text) }
499
+
500
+ let(:epsilon_message) { SampleMessage.new(service: 'epsilon') }
501
+ let(:epsilon_controller) { MessagesController.new(service_message: epsilon_message.message_with_text) }
502
+
503
+ let(:gamma_message) { SampleMessage.new(service: 'twitter') }
504
+ let(:gamma_controller) { MessagesController.new(service_message: gamma_message.message_with_text) }
505
+
506
+ it "should load the Facebook reply variant if current_service == facebook" do
507
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
508
+ allow(controller.current_session).to receive(:state_string).and_return("say_hola")
509
+ file_contents, selected_preprocessor = controller.send(:action_replies)
510
+
511
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_hola.yml+facebook.erb", __dir__)))
512
+ end
513
+
514
+ it "should load the Twilio reply variant if current_service == twilio" do
515
+ allow(twilio_controller.current_session).to receive(:flow_string).and_return("message")
516
+ allow(twilio_controller.current_session).to receive(:state_string).and_return("say_hola")
517
+ file_contents, selected_preprocessor = twilio_controller.send(:action_replies)
518
+
519
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_hola.yml+twilio.erb", __dir__)))
520
+ end
521
+
522
+ it "should load the base reply variant if current_service does not have a custom variant" do
523
+ allow(epsilon_controller.current_session).to receive(:flow_string).and_return("message")
524
+ allow(epsilon_controller.current_session).to receive(:state_string).and_return("say_hola")
525
+ file_contents, selected_preprocessor = epsilon_controller.send(:action_replies)
526
+
527
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_hola.yml.erb", __dir__)))
528
+ end
529
+
530
+ it "should load the correct variant when there is no preprocessor" do
531
+ allow(gamma_controller.current_session).to receive(:flow_string).and_return("message")
532
+ allow(gamma_controller.current_session).to receive(:state_string).and_return("say_yo")
533
+ file_contents, selected_preprocessor = gamma_controller.send(:action_replies)
534
+
535
+ expect(file_contents).to eq(File.read(File.expand_path("../replies/messages/say_yo.yml+twitter", __dir__)))
536
+ end
537
+ end
538
+
539
+ describe "randomized replies" do
540
+ let(:stubbed_handler) { double("handler") }
541
+ let(:stubbed_client) { double("client") }
542
+
543
+ before(:each) do
544
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
545
+ end
546
+
547
+ describe "text replies" do
548
+ before(:each) do
549
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
550
+ allow(controller.current_session).to receive(:state_string).and_return("say_randomize_text")
551
+ Xip.config.auto_insert_delays = false
552
+ end
553
+
554
+ after(:each) do
555
+ Xip.config.auto_insert_delays = true
556
+ end
557
+
558
+ it "should receive a single text string" do
559
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new) do |*args|
560
+ expect(args.first[:reply]['text']).to be_a(String)
561
+ stubbed_handler
562
+ end
563
+ allow(stubbed_handler).to receive(:text).exactly(1).time
564
+ expect(stubbed_client).to receive(:transmit).exactly(1).time
565
+ controller.say_randomize_text
566
+ end
567
+ end
568
+ end
569
+
570
+ describe "sub-state replies" do
571
+ let(:stubbed_handler) { double("handler") }
572
+ let(:stubbed_client) { double("client") }
573
+
574
+ before(:each) do
575
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
576
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
577
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
578
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
579
+ allow(stubbed_client).to receive(:transmit).and_return(true)
580
+ allow(controller).to receive(:sleep).and_return(true)
581
+ end
582
+
583
+ it "should transmit only the last reply in the file when @pos = -1" do
584
+ expect(stubbed_handler).to receive(:text).exactly(1).time
585
+ expect(stubbed_handler).to receive(:delay).exactly(1).time # auto-delay
586
+ controller.pos = -1
587
+ controller.say_offer
588
+ end
589
+
590
+ it "should transmit the last two replies in the file when @pos = -2" do
591
+ expect(stubbed_handler).to receive(:text).exactly(1).time
592
+ expect(stubbed_handler).to receive(:delay).exactly(1).time
593
+ controller.pos = -2
594
+ controller.say_offer
595
+ end
596
+
597
+ it "should transmit all the replies in the file when @pos = 0" do
598
+ expect(stubbed_handler).to receive(:text).exactly(2).times
599
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
600
+ controller.pos = 0
601
+ controller.say_offer
602
+ end
603
+
604
+ it "should transmit all the replies in the file when @pos = nil" do
605
+ expect(stubbed_handler).to receive(:text).exactly(2).times
606
+ expect(stubbed_handler).to receive(:delay).exactly(2).times
607
+ expect(controller.pos).to be_nil
608
+ controller.say_offer
609
+ end
610
+ end
611
+
612
+ describe "client errors" do
613
+ let(:stubbed_handler) { double("handler") }
614
+ let(:stubbed_client) { double("client") }
615
+
616
+ before(:each) do
617
+ allow(Xip::Services::Facebook::ReplyHandler).to receive(:new).and_return(stubbed_handler)
618
+ allow(Xip::Services::Facebook::Client).to receive(:new).and_return(stubbed_client)
619
+ allow(controller.current_session).to receive(:flow_string).and_return("message")
620
+ allow(controller.current_session).to receive(:state_string).and_return("say_offer")
621
+ allow(stubbed_handler).to receive(:delay).exactly(1).time
622
+ allow(stubbed_handler).to receive(:text).exactly(1).time
623
+ end
624
+
625
+ describe "Xip::Errors::UserOptOut" do
626
+ before(:each) do
627
+ expect(stubbed_client).to receive(:transmit).and_raise(
628
+ Xip::Errors::UserOptOut.new('boom')
629
+ ).once # Retuns early; doesn't send the remaining replies in the file
630
+ end
631
+
632
+ it "should log the unhandled exception if the controller does not have a handle_opt_out method" do
633
+ expect(Xip::Logger).to receive(:l).with(
634
+ topic: :err,
635
+ message: "User #{facebook_message.sender_id} unhandled exception due to opt-out."
636
+ )
637
+ expect(controller).to receive(:do_nothing)
638
+ controller.say_offer
639
+ end
640
+
641
+ it "should call handle_opt_out method" do
642
+ expect(controller).to receive(:handle_opt_out)
643
+ expect(Xip::Logger).to receive(:l).with(
644
+ topic: 'facebook',
645
+ message: "User #{facebook_message.sender_id} opted out. [boom]"
646
+ )
647
+ expect(controller).to receive(:do_nothing)
648
+ controller.say_offer
649
+ end
650
+ end
651
+
652
+ describe "Xip::Errors::InvalidSessionID" do
653
+ before(:each) do
654
+ expect(stubbed_client).to receive(:transmit).and_raise(
655
+ Xip::Errors::InvalidSessionID.new('boom')
656
+ ).once # Retuns early; doesn't send the remaining replies in the file
657
+ end
658
+
659
+ it "should log the unhandled exception if the controller does not have a handle_invalid_session_id method" do
660
+ expect(Xip::Logger).to receive(:l).with(
661
+ topic: :err,
662
+ message: "User #{facebook_message.sender_id} unhandled exception due to invalid session_id."
663
+ )
664
+ expect(controller).to receive(:do_nothing)
665
+ controller.say_offer
666
+ end
667
+
668
+ it "should call handle_invalid_session_id method" do
669
+ expect(controller).to receive(:handle_invalid_session_id)
670
+ expect(Xip::Logger).to receive(:l).with(
671
+ topic: 'facebook',
672
+ message: "User #{facebook_message.sender_id} has an invalid session_id. [boom]"
673
+ )
674
+ expect(controller).to receive(:do_nothing)
675
+ controller.say_offer
676
+ end
677
+ end
678
+
679
+ describe 'an unknown client error' do
680
+ before(:each) do
681
+ allow(stubbed_client).to receive(:transmit).and_raise(
682
+ StandardError
683
+ )
684
+ end
685
+
686
+ it 'should raise the error' do
687
+ expect {
688
+ controller.say_offer
689
+ }.to raise_error(StandardError)
690
+ end
691
+ end
692
+ end
693
+
694
+ end