xip 0.0.1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +116 -0
  3. data/.gitignore +12 -0
  4. data/CHANGELOG.md +135 -0
  5. data/Gemfile +4 -1
  6. data/Gemfile.lock +65 -15
  7. data/LICENSE +6 -4
  8. data/README.md +51 -1
  9. data/VERSION +1 -0
  10. data/bin/xip +3 -11
  11. data/lib/xip.rb +1 -3
  12. data/lib/xip/base.rb +189 -0
  13. data/lib/xip/cli.rb +273 -0
  14. data/lib/xip/cli_base.rb +24 -0
  15. data/lib/xip/commands/command.rb +13 -0
  16. data/lib/xip/commands/console.rb +74 -0
  17. data/lib/xip/commands/server.rb +63 -0
  18. data/lib/xip/configuration.rb +56 -0
  19. data/lib/xip/controller/callbacks.rb +63 -0
  20. data/lib/xip/controller/catch_all.rb +84 -0
  21. data/lib/xip/controller/controller.rb +274 -0
  22. data/lib/xip/controller/dev_jumps.rb +40 -0
  23. data/lib/xip/controller/dynamic_delay.rb +61 -0
  24. data/lib/xip/controller/helpers.rb +128 -0
  25. data/lib/xip/controller/interrupt_detect.rb +99 -0
  26. data/lib/xip/controller/messages.rb +283 -0
  27. data/lib/xip/controller/nlp.rb +49 -0
  28. data/lib/xip/controller/replies.rb +281 -0
  29. data/lib/xip/controller/unrecognized_message.rb +61 -0
  30. data/lib/xip/core_ext.rb +5 -0
  31. data/lib/xip/core_ext/numeric.rb +10 -0
  32. data/lib/xip/core_ext/string.rb +18 -0
  33. data/lib/xip/dispatcher.rb +68 -0
  34. data/lib/xip/errors.rb +55 -0
  35. data/lib/xip/flow/base.rb +69 -0
  36. data/lib/xip/flow/specification.rb +56 -0
  37. data/lib/xip/flow/state.rb +82 -0
  38. data/lib/xip/generators/builder.rb +41 -0
  39. data/lib/xip/generators/builder/.gitignore +30 -0
  40. data/lib/xip/generators/builder/Gemfile +19 -0
  41. data/lib/xip/generators/builder/Procfile.dev +2 -0
  42. data/lib/xip/generators/builder/README.md +9 -0
  43. data/lib/xip/generators/builder/Rakefile +2 -0
  44. data/lib/xip/generators/builder/bot/controllers/bot_controller.rb +55 -0
  45. data/lib/xip/generators/builder/bot/controllers/catch_alls_controller.rb +21 -0
  46. data/lib/xip/generators/builder/bot/controllers/concerns/.keep +0 -0
  47. data/lib/xip/generators/builder/bot/controllers/goodbyes_controller.rb +9 -0
  48. data/lib/xip/generators/builder/bot/controllers/hellos_controller.rb +9 -0
  49. data/lib/xip/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  50. data/lib/xip/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  51. data/lib/xip/generators/builder/bot/helpers/bot_helper.rb +2 -0
  52. data/lib/xip/generators/builder/bot/models/bot_record.rb +3 -0
  53. data/lib/xip/generators/builder/bot/models/concerns/.keep +0 -0
  54. data/lib/xip/generators/builder/bot/replies/catch_alls/level1.yml +2 -0
  55. data/lib/xip/generators/builder/bot/replies/goodbyes/say_goodbye.yml +2 -0
  56. data/lib/xip/generators/builder/bot/replies/hellos/say_hello.yml +2 -0
  57. data/lib/xip/generators/builder/config.ru +4 -0
  58. data/lib/xip/generators/builder/config/boot.rb +6 -0
  59. data/lib/xip/generators/builder/config/database.yml +25 -0
  60. data/lib/xip/generators/builder/config/environment.rb +2 -0
  61. data/lib/xip/generators/builder/config/flow_map.rb +25 -0
  62. data/lib/xip/generators/builder/config/initializers/autoload.rb +8 -0
  63. data/lib/xip/generators/builder/config/initializers/inflections.rb +16 -0
  64. data/lib/xip/generators/builder/config/puma.rb +25 -0
  65. data/lib/xip/generators/builder/config/services.yml +35 -0
  66. data/lib/xip/generators/builder/config/sidekiq.yml +3 -0
  67. data/lib/xip/generators/builder/db/seeds.rb +7 -0
  68. data/lib/xip/generators/generate.rb +39 -0
  69. data/lib/xip/generators/generate/flow/controllers/controller.tt +7 -0
  70. data/lib/xip/generators/generate/flow/helpers/helper.tt +3 -0
  71. data/lib/xip/generators/generate/flow/replies/ask_example.tt +9 -0
  72. data/lib/xip/helpers/redis.rb +40 -0
  73. data/lib/xip/jobs.rb +9 -0
  74. data/lib/xip/lock.rb +82 -0
  75. data/lib/xip/logger.rb +9 -3
  76. data/lib/xip/migrations/configurator.rb +73 -0
  77. data/lib/xip/migrations/generators.rb +16 -0
  78. data/lib/xip/migrations/railtie_config.rb +14 -0
  79. data/lib/xip/migrations/tasks.rb +43 -0
  80. data/lib/xip/nlp/client.rb +21 -0
  81. data/lib/xip/nlp/result.rb +56 -0
  82. data/lib/xip/reloader.rb +89 -0
  83. data/lib/xip/reply.rb +36 -0
  84. data/lib/xip/scheduled_reply.rb +18 -0
  85. data/lib/xip/server.rb +63 -0
  86. data/lib/xip/service_message.rb +17 -0
  87. data/lib/xip/service_reply.rb +44 -0
  88. data/lib/xip/services/base_client.rb +24 -0
  89. data/lib/xip/services/base_message_handler.rb +27 -0
  90. data/lib/xip/services/base_reply_handler.rb +72 -0
  91. data/lib/xip/services/jobs/handle_message_job.rb +21 -0
  92. data/lib/xip/session.rb +203 -0
  93. data/lib/xip/version.rb +7 -1
  94. data/logo.svg +17 -0
  95. data/spec/configuration_spec.rb +93 -0
  96. data/spec/controller/callbacks_spec.rb +217 -0
  97. data/spec/controller/catch_all_spec.rb +154 -0
  98. data/spec/controller/controller_spec.rb +889 -0
  99. data/spec/controller/dynamic_delay_spec.rb +70 -0
  100. data/spec/controller/helpers_spec.rb +119 -0
  101. data/spec/controller/interrupt_detect_spec.rb +171 -0
  102. data/spec/controller/messages_spec.rb +744 -0
  103. data/spec/controller/nlp_spec.rb +93 -0
  104. data/spec/controller/replies_spec.rb +694 -0
  105. data/spec/controller/unrecognized_message_spec.rb +168 -0
  106. data/spec/dispatcher_spec.rb +79 -0
  107. data/spec/flow/flow_spec.rb +82 -0
  108. data/spec/flow/state_spec.rb +109 -0
  109. data/spec/helpers/redis_spec.rb +77 -0
  110. data/spec/lock_spec.rb +100 -0
  111. data/spec/nlp/client_spec.rb +23 -0
  112. data/spec/nlp/result_spec.rb +57 -0
  113. data/spec/replies/hello.yml.erb +15 -0
  114. data/spec/replies/messages/say_hola.yml+facebook.erb +6 -0
  115. data/spec/replies/messages/say_hola.yml+twilio.erb +6 -0
  116. data/spec/replies/messages/say_hola.yml.erb +6 -0
  117. data/spec/replies/messages/say_howdy_with_dynamic.yml +79 -0
  118. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  119. data/spec/replies/messages/say_offer.yml +6 -0
  120. data/spec/replies/messages/say_offer_with_dynamic.yml +6 -0
  121. data/spec/replies/messages/say_oi.yml.erb +15 -0
  122. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  123. data/spec/replies/messages/say_randomize_text.yml +10 -0
  124. data/spec/replies/messages/say_yo.yml +6 -0
  125. data/spec/replies/messages/say_yo.yml+twitter +6 -0
  126. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  127. data/spec/reply_spec.rb +61 -0
  128. data/spec/scheduled_reply_spec.rb +23 -0
  129. data/spec/service_reply_spec.rb +92 -0
  130. data/spec/session_spec.rb +366 -0
  131. data/spec/spec_helper.rb +22 -66
  132. data/spec/support/alternate_helpers/foo_helper.rb +5 -0
  133. data/spec/support/controllers/vaders_controller.rb +24 -0
  134. data/spec/support/helpers/fun/games_helper.rb +7 -0
  135. data/spec/support/helpers/fun/pdf_helper.rb +7 -0
  136. data/spec/support/helpers/standalone_helper.rb +5 -0
  137. data/spec/support/helpers_typo/users_helper.rb +2 -0
  138. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  139. data/spec/support/nlp_clients/luis.rb +9 -0
  140. data/spec/support/nlp_results/luis_result.rb +163 -0
  141. data/spec/support/sample_messages.rb +66 -0
  142. data/spec/support/services.yml +31 -0
  143. data/spec/support/services_with_erb.yml +31 -0
  144. data/spec/version_spec.rb +16 -0
  145. data/xip.gemspec +25 -14
  146. metadata +320 -18
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Controller::DynamicDelay" do
6
+
7
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
8
+ let(:controller) { VadersController.new(service_message: facebook_message.message_with_text) }
9
+ let!(:service_replies) { YAML.load(File.read(File.expand_path("../replies/messages/say_howdy_with_dynamic.yml", __dir__))) }
10
+
11
+ it "should return a SHORT_DELAY for a dynamic delay at position 0" do
12
+ delay = controller.dynamic_delay(previous_reply: nil)
13
+ expect(delay).to eq(Xip::Controller::DynamicDelay::SHORT_DELAY)
14
+ end
15
+
16
+ it "should return a STANDARD_DELAY for a dynamic delay at position -2" do
17
+ delay = controller.dynamic_delay(previous_reply: service_replies[-2])
18
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
19
+ end
20
+
21
+ it "should return a SHORT_DELAY for text 35 chars long" do
22
+ delay = controller.dynamic_delay(previous_reply: service_replies[1])
23
+ expect(delay).to eq(Xip::Controller::DynamicDelay::SHORT_DELAY)
24
+ end
25
+
26
+ it "should return a STANDARD_DELAY for text 120 chars long" do
27
+ delay = controller.dynamic_delay(previous_reply: service_replies[3])
28
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
29
+ end
30
+
31
+ it "should return a (STANDARD_DELAY * 1.5) for text 230 chars long" do
32
+ delay = controller.dynamic_delay(previous_reply: service_replies[5])
33
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY * 1.5)
34
+ end
35
+
36
+ it "should return a LONG_DELAY for text 350 chars long" do
37
+ delay = controller.dynamic_delay(previous_reply: service_replies[7])
38
+ expect(delay).to eq(Xip::Controller::DynamicDelay::LONG_DELAY)
39
+ end
40
+
41
+ it "should return a STANDARD_DELAY for an image" do
42
+ delay = controller.dynamic_delay(previous_reply: service_replies[9])
43
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
44
+ end
45
+
46
+ it "should return a STANDARD_DELAY for a video" do
47
+ delay = controller.dynamic_delay(previous_reply: service_replies[11])
48
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
49
+ end
50
+
51
+ it "should return a STANDARD_DELAY for an audio" do
52
+ delay = controller.dynamic_delay(previous_reply: service_replies[13])
53
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
54
+ end
55
+
56
+ it "should return a STANDARD_DELAY for a file" do
57
+ delay = controller.dynamic_delay(previous_reply: service_replies[15])
58
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
59
+ end
60
+
61
+ it "should return a STANDARD_DELAY for cards" do
62
+ delay = controller.dynamic_delay(previous_reply: service_replies[17])
63
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
64
+ end
65
+
66
+ it "should return a STANDARD_DELAY for a list" do
67
+ delay = controller.dynamic_delay(previous_reply: service_replies[19])
68
+ expect(delay).to eq(Xip::Controller::DynamicDelay::STANDARD_DELAY)
69
+ end
70
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ $:.unshift File.expand_path("../support/helpers", __dir__)
6
+
7
+ describe "Xip::Controller helpers" do
8
+
9
+ Xip::Controller.helpers_path = File.expand_path("../support/helpers", __dir__)
10
+
11
+ module Fun
12
+ class GamesController < Xip::Controller
13
+ helper :all
14
+
15
+ def say_hello_world
16
+ hello_world
17
+ end
18
+
19
+ def say_kaboom
20
+ hello_world2
21
+ end
22
+ end
23
+
24
+ class PdfController < Xip::Controller
25
+ def say_pdf_name
26
+ generate_pdf_name
27
+ end
28
+ end
29
+ end
30
+
31
+ class BaseController < Xip::Controller
32
+
33
+ end
34
+
35
+ class AllHelpersController < Xip::Controller
36
+ helper :all
37
+ end
38
+
39
+ class InheritedHelpersController < AllHelpersController
40
+ def say_hello_world
41
+ hello_world
42
+ end
43
+ end
44
+
45
+ class SizzleController < Xip::Controller
46
+ helper :standalone
47
+
48
+ def say_sizzle
49
+
50
+ end
51
+ end
52
+
53
+ class HelpersTypoController < Xip::Controller
54
+ path = File.expand_path("../support/helpers_typo", __dir__)
55
+ $:.unshift(path)
56
+ self.helpers_path = path
57
+ end
58
+
59
+ class VoodooController < Xip::Controller
60
+ helpers_path = File.expand_path("../support/alternate_helpers", __dir__)
61
+
62
+ # Reload helpers
63
+ _helpers = Module.new
64
+ helper :all
65
+
66
+ def zoom
67
+
68
+ end
69
+ end
70
+
71
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
72
+ let(:all_helper_methods) { [:hello_world, :baz, :generate_pdf_name] }
73
+
74
+ describe "loading" do
75
+
76
+ it "should load all helpers if none are specified by default" do
77
+ expect(BaseController._helpers.instance_methods).to match_array(all_helper_methods)
78
+ end
79
+
80
+ it "should not load helpers if none are specified by default and include_all_helpers = false" do
81
+ Xip::Controller.include_all_helpers = false
82
+ class HelperlessController < Xip::Controller; end
83
+ expect(HelperlessController._helpers.instance_methods).to eq []
84
+ end
85
+
86
+ it "should load all helpers if :all is used" do
87
+ expect(AllHelpersController._helpers.instance_methods).to match_array(all_helper_methods)
88
+ end
89
+
90
+ it "should load all helpers if parent class inherits all helpers" do
91
+ expect(InheritedHelpersController._helpers.instance_methods).to match_array(all_helper_methods)
92
+ end
93
+
94
+ it "should allow a controller that has inherited all helpers to access a helper method" do
95
+ expect {
96
+ InheritedHelpersController.new(service_message: facebook_message.message_with_text).say_hello_world
97
+ }.to_not raise_error
98
+ end
99
+
100
+ it "should allow a controller that has loaded all helpers to access a helper method" do
101
+ expect {
102
+ Fun::GamesController.new(service_message: facebook_message.message_with_text).say_hello_world
103
+ }.to_not raise_error
104
+ end
105
+
106
+ it "should raise an error if a helper method does not exist" do
107
+ expect {
108
+ Fun::GamesController.new(service_message: facebook_message.message_with_text).say_kaboom
109
+ }.to raise_error(NameError)
110
+ end
111
+
112
+ it "should allow a controller action to access a helper method" do
113
+ expect {
114
+ Fun::PdfController.new(service_message: facebook_message.message_with_text).say_pdf_name
115
+ }.to_not raise_error
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Controller::InterruptDetect" do
6
+
7
+ let(:fb_message) { SampleMessage.new(service: 'facebook') }
8
+ let(:controller) { VadersController.new(service_message: fb_message.message_with_text) }
9
+ let(:lock_key) { "#{fb_message.sender_id}-lock" }
10
+ let(:example_tid) { 'ovefhgJvx' }
11
+ let(:example_session) { 'goodbye->say_goodbye' }
12
+ let(:example_position) { 2 }
13
+ let(:example_lock) { "#{example_tid}##{example_session}:#{example_position}" }
14
+
15
+ describe 'current_lock' do
16
+ it "should return the current lock for the session if it is locked" do
17
+ $redis.set(lock_key, example_lock)
18
+ current_lock = controller.current_lock
19
+ expect(current_lock).to be_a(Xip::Lock)
20
+ expect(current_lock.session_id).to eq fb_message.sender_id
21
+ end
22
+
23
+ it "should return nil if the current session is not locked" do
24
+ random_lock_key = "xyz123-lock"
25
+
26
+ # clear the memoization
27
+ controller.instance_eval do
28
+ @current_lock = nil
29
+ @current_session_id = random_lock_key
30
+ end
31
+
32
+ expect($redis.get(random_lock_key)).to be_nil
33
+ expect(controller.current_lock).to be_nil
34
+ end
35
+ end
36
+
37
+ describe 'run_interrupt_action' do
38
+ let(:interrupts_controller) { InterruptController.new(service_message: fb_message) }
39
+
40
+ it "should return false if an InterruptsController is not defined" do
41
+ expect(Xip::Logger).to receive(:l).with(
42
+ topic: 'interrupt',
43
+ message: "Interrupt detected for session #{fb_message.sender_id}"
44
+ ).ordered
45
+
46
+ expect(Xip::Logger).to receive(:l).with(
47
+ topic: 'interrupt',
48
+ message: 'Ignoring interrupt; InterruptsController not defined.'
49
+ ).ordered
50
+
51
+ expect(controller.run_interrupt_action).to be false
52
+ end
53
+
54
+ it "should call say_interrupted on the InterruptsController" do
55
+ class InterruptsController < Xip::Controller
56
+ def say_interrupted
57
+ end
58
+ end
59
+
60
+ expect_any_instance_of(InterruptsController).to receive(:say_interrupted)
61
+ controller.run_interrupt_action
62
+ end
63
+
64
+ it "should log if the InterruptsController#say_interrupted does not progress the session" do
65
+ class InterruptsController < Xip::Controller
66
+ def say_interrupted
67
+ end
68
+ end
69
+
70
+ expect(Xip::Logger).to receive(:l).with(
71
+ topic: 'interrupt',
72
+ message: "Interrupt detected for session #{fb_message.sender_id}"
73
+ ).ordered
74
+
75
+ expect(Xip::Logger).to receive(:l).with(
76
+ topic: 'interrupt',
77
+ message: 'Did not send replies, update session, or step'
78
+ ).ordered
79
+
80
+ controller.run_interrupt_action
81
+ end
82
+
83
+ it "should catch StandardError from within InterruptController and log it" do
84
+ class InterruptsController < Xip::Controller
85
+ def say_interrupted
86
+ raise Xip::Errors::ReplyNotFound
87
+ end
88
+ end
89
+
90
+ # Once for the interrupt detection, once for the error
91
+ expect(Xip::Logger).to receive(:l).exactly(2).times
92
+
93
+ controller.run_interrupt_action
94
+ end
95
+ end
96
+
97
+ describe 'interrupt_detected?' do
98
+ it "should return false if there is not a lock on the session" do
99
+ random_lock_key = "xyz123-lock"
100
+
101
+ # clear the memoization
102
+ controller.instance_eval do
103
+ @current_lock = nil
104
+ @current_session_id = random_lock_key
105
+ end
106
+
107
+ expect(controller.send(:interrupt_detected?)).to be false
108
+ end
109
+
110
+ it "should return false if the current thread owns the lock" do
111
+ $redis.set(lock_key, example_lock)
112
+ lock = controller.current_lock
113
+ expect(lock).to receive(:tid).and_return(Xip.tid)
114
+
115
+ expect(controller.send(:interrupt_detected?)).to be false
116
+ end
117
+
118
+ it 'should return true if the session is locked by another thread' do
119
+ $redis.set(lock_key, example_lock)
120
+ # our mock tid will not match the real tid for this test
121
+ expect(controller.send(:interrupt_detected?)).to be true
122
+ end
123
+ end
124
+
125
+ describe 'current_thread_has_control?' do
126
+ it "should return true if the current tid matches the lock tid" do
127
+ $redis.set(lock_key, example_lock)
128
+ lock = controller.current_lock
129
+ expect(lock).to receive(:tid).and_return(Xip.tid)
130
+ expect(controller.send(:current_thread_has_control?)).to be true
131
+ end
132
+
133
+ it "should return false if the current tid does not match the lock tid" do
134
+ $redis.set(lock_key, example_lock)
135
+ lock = controller.current_lock
136
+ expect(controller.send(:current_thread_has_control?)).to be false
137
+ end
138
+ end
139
+
140
+ describe 'lock_session!' do
141
+ it "should create a lock for the session" do
142
+ $redis.del(lock_key)
143
+ controller.send(:lock_session!, session_slug: example_session, position: example_position)
144
+ expect($redis.get(lock_key)).to match(/goodbye\-\>say_goodbye\:2/)
145
+ end
146
+ end
147
+
148
+ describe 'release_lock!' do
149
+ it "should not raise an error if current_lock is nil" do
150
+ expect(controller).to receive(:current_lock).and_return(nil)
151
+ controller.send(:release_lock!)
152
+ end
153
+
154
+ it "should not release the lock if we are in the InterruptsController" do
155
+ class InterruptsController
156
+ end
157
+
158
+ lock = controller.current_lock
159
+ expect(controller).to receive(:class).and_return InterruptsController
160
+ expect(lock).to_not receive(:release)
161
+ controller.send(:release_lock!)
162
+ end
163
+
164
+ it "should release the lock if there is one and we are not in the InterruptsController" do
165
+ $redis.set(lock_key, example_lock)
166
+ controller.send(:release_lock!)
167
+ expect($redis.get(lock_key)).to be_nil
168
+ end
169
+ end
170
+
171
+ end
@@ -0,0 +1,744 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Xip::Controller::Messages do
6
+
7
+ class MrTronsController < Xip::Controller
8
+
9
+ end
10
+
11
+ let(:facebook_message) { SampleMessage.new(service: 'facebook') }
12
+ let(:test_controller) {
13
+ MrTronsController.new(service_message: facebook_message.message_with_text)
14
+ }
15
+
16
+ describe "normalized_msg" do
17
+ let(:padded_msg) { ' Hello World! 👋 ' }
18
+ let(:weird_case_msg) { 'Oh BaBy Oh BaBy' }
19
+
20
+ it 'should normalize blank-padded messages' do
21
+ test_controller.current_message.message = padded_msg
22
+ expect(test_controller.normalized_msg).to eq('HELLO WORLD! 👋')
23
+ end
24
+
25
+ it 'should normalize differently cased messages' do
26
+ test_controller.current_message.message = weird_case_msg
27
+ expect(test_controller.normalized_msg).to eq('OH BABY OH BABY')
28
+ end
29
+ end
30
+
31
+ describe "homophone_translated_msg" do
32
+ it 'should convert homophones to their respective alpha ordinal' do
33
+ Xip::Controller::Messages::HOMOPHONES.each do |homophone, ordinal|
34
+ test_controller.current_message.message = homophone
35
+ test_controller.normalized_msg = test_controller.homophone_translated_msg = nil
36
+ expect(test_controller.homophone_translated_msg).to eq(ordinal)
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "get_match" do
42
+ it "should match messages with different casing" do
43
+ test_controller.current_message.message = "NICE"
44
+ expect(
45
+ test_controller.get_match(['nice', 'woot'])
46
+ ).to eq('nice')
47
+ end
48
+
49
+ it "should match messages with blank padding" do
50
+ test_controller.current_message.message = " NiCe "
51
+ expect(
52
+ test_controller.get_match(['nice', 'woot'])
53
+ ).to eq('nice')
54
+ end
55
+
56
+ it "should match messages utilizing a lower case SMS quick reply" do
57
+ test_controller.current_message.message = "a "
58
+ expect(
59
+ test_controller.get_match(['nice', 'woot'])
60
+ ).to eq('nice')
61
+ end
62
+
63
+ it "should match messages utilizing an upper case SMS quick reply" do
64
+ test_controller.current_message.message = " B "
65
+ expect(
66
+ test_controller.get_match(['nice', 'woot'])
67
+ ).to eq('woot')
68
+ end
69
+
70
+ it "should match messages utilizing a single-quoted SMS quick reply" do
71
+ test_controller.current_message.message = "'B'"
72
+ expect(
73
+ test_controller.get_match(['nice', 'woot'])
74
+ ).to eq('woot')
75
+ end
76
+
77
+ it "should match messages utilizing a double-quoted SMS quick reply" do
78
+ test_controller.current_message.message = '"A"'
79
+ expect(
80
+ test_controller.get_match(['nice', 'woot'])
81
+ ).to eq('nice')
82
+ end
83
+
84
+ it "should match messages utilizing a double-smartquoted SMS quick reply" do
85
+ test_controller.current_message.message = '“A”'
86
+ expect(
87
+ test_controller.get_match(['nice', 'woot'])
88
+ ).to eq('nice')
89
+ end
90
+
91
+ it "should match messages utilizing a single-smartquoted SMS quick reply" do
92
+ test_controller.current_message.message = '‘A’'
93
+ expect(
94
+ test_controller.get_match(['nice', 'woot'])
95
+ ).to eq('nice')
96
+ end
97
+
98
+ it "should match messages with a period in the SMS quick reply" do
99
+ test_controller.current_message.message = 'A.'
100
+ expect(
101
+ test_controller.get_match(['nice', 'woot'])
102
+ ).to eq('nice')
103
+ end
104
+
105
+ it "should match messages with a question mark in the SMS quick reply" do
106
+ test_controller.current_message.message = 'B?'
107
+ expect(
108
+ test_controller.get_match(['nice', 'woot'])
109
+ ).to eq('woot')
110
+ end
111
+
112
+ it "should match messages in parens in the SMS quick reply" do
113
+ test_controller.current_message.message = '(B)'
114
+ expect(
115
+ test_controller.get_match(['nice', 'woot'])
116
+ ).to eq('woot')
117
+ end
118
+
119
+ it "should match messages with backticks in the SMS quick reply" do
120
+ test_controller.current_message.message = '`B`'
121
+ expect(
122
+ test_controller.get_match(['nice', 'woot'])
123
+ ).to eq('woot')
124
+ end
125
+
126
+ it "should match messages utilizing a homophone" do
127
+ test_controller.current_message.message = " bee "
128
+ expect(
129
+ test_controller.get_match(['nice', 'woot'])
130
+ ).to eq('woot')
131
+ end
132
+
133
+ it "should raise ReservedHomophoneUsed if a homophone is used" do
134
+ test_controller.current_message.message = " B "
135
+ expect {
136
+ test_controller.get_match(['nice', 'woot', 'sea', 'bee'])
137
+ }.to raise_error(Xip::Errors::ReservedHomophoneUsed, 'Cannot use `SEA, BEE`. Reserved for homophones.')
138
+ end
139
+
140
+ it "should raise Xip::Errors::UnrecognizedMessage if a response was not matched" do
141
+ test_controller.current_message.message = "uh oh"
142
+ expect {
143
+ test_controller.get_match(['nice', 'woot'])
144
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
145
+ end
146
+
147
+ it "should raise Xip::Errors::UnrecognizedMessage if an SMS quick reply was not matched" do
148
+ test_controller.current_message.message = "C"
149
+ expect {
150
+ test_controller.get_match(['nice', 'woot'])
151
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
152
+ end
153
+
154
+ it "should not run NLP entity detection if an ordinal is entered by the user" do
155
+ test_controller.current_message.message = "C"
156
+
157
+ expect(test_controller).to_not receive(:perform_nlp!)
158
+ expect(
159
+ test_controller.get_match([:yes, :no, 'unsubscribe'])
160
+ ).to eq('unsubscribe')
161
+ end
162
+
163
+ describe "entity detection" do
164
+ let(:no_intent) { :no }
165
+ let(:yes_intent) { :yes }
166
+ let(:single_number_nlp_result) { TestNlpResult::Luis.new(intent: yes_intent, entity: :single_number_entity) }
167
+ let(:double_number_nlp_result) { TestNlpResult::Luis.new(intent: no_intent, entity: :double_number_entity) }
168
+ let(:triple_number_nlp_result) { TestNlpResult::Luis.new(intent: yes_intent, entity: :triple_number_entity) }
169
+
170
+ describe 'single nlp_result entity' do
171
+ it 'should return the :number entity' do
172
+ allow(test_controller).to receive(:perform_nlp!).and_return(single_number_nlp_result)
173
+ test_controller.nlp_result = single_number_nlp_result
174
+
175
+ test_controller.current_message.message = "hi"
176
+ expect(
177
+ test_controller.get_match(['nice', :number])
178
+ ).to eq(test_controller.nlp_result.entities[:number].first)
179
+ end
180
+
181
+ it 'should return the first :number entity if fuzzy_match=true' do
182
+ allow(test_controller).to receive(:perform_nlp!).and_return(double_number_nlp_result)
183
+ test_controller.nlp_result = double_number_nlp_result
184
+
185
+ test_controller.current_message.message = "hi"
186
+ expect(
187
+ test_controller.get_match(['nice', :number])
188
+ ).to eq(test_controller.nlp_result.entities[:number].first)
189
+ end
190
+
191
+ it 'should raise Xip::Errors::UnrecognizedMessage if more than one :number entity is returned and fuzzy_match=false' do
192
+ allow(test_controller).to receive(:perform_nlp!).and_return(double_number_nlp_result)
193
+ test_controller.nlp_result = double_number_nlp_result
194
+
195
+ test_controller.current_message.message = "hi"
196
+ expect {
197
+ test_controller.get_match(['nice', :number], fuzzy_match: false)
198
+ }.to raise_error(Xip::Errors::UnrecognizedMessage, "Encountered 2 entity matches of type :number and expected 1. To allow, set fuzzy_match to true.")
199
+ end
200
+
201
+ it 'should log the NLP result if log_all_nlp_results=true' do
202
+ Xip.config.log_all_nlp_results = true
203
+ Xip.config.nlp_integration = :luis
204
+
205
+ luis_client = double('luis_client')
206
+ allow(luis_client).to receive(:understand).and_return(single_number_nlp_result)
207
+ allow(Xip::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
208
+
209
+ expect(Xip::Logger).to receive(:l).with(
210
+ topic: :nlp,
211
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
212
+ )
213
+ expect(Xip::Logger).to receive(:l).with(
214
+ topic: :nlp,
215
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: #{single_number_nlp_result.parsed_result.inspect}"
216
+ )
217
+ test_controller.current_message.message = "hi"
218
+ test_controller.get_match(['nice', :number])
219
+
220
+ Xip.config.log_all_nlp_results = false
221
+ Xip.config.nlp_integration = nil
222
+ end
223
+ end
224
+
225
+ describe 'multiple nlp_result entity matches' do
226
+ it 'should return the [:number, :number] entity' do
227
+ allow(test_controller).to receive(:perform_nlp!).and_return(double_number_nlp_result)
228
+ test_controller.nlp_result = double_number_nlp_result
229
+
230
+ test_controller.current_message.message = "hi"
231
+ expect(
232
+ test_controller.get_match(['nice', [:number, :number]])
233
+ ).to eq(double_number_nlp_result.entities[:number])
234
+ end
235
+
236
+ it 'should return the [:number, :number, :number] entity' do
237
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
238
+ test_controller.nlp_result = triple_number_nlp_result
239
+
240
+ test_controller.current_message.message = "hi"
241
+ expect(
242
+ test_controller.get_match(['nice', [:number, :number, :number]])
243
+ ).to eq(triple_number_nlp_result.entities[:number])
244
+ end
245
+
246
+ it 'should return the [:number, :number] entity from a triple :number entity result' do
247
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
248
+ test_controller.nlp_result = triple_number_nlp_result
249
+
250
+ test_controller.current_message.message = "hi"
251
+ expect(
252
+ test_controller.get_match(['nice', [:number, :number]])
253
+ ).to eq(triple_number_nlp_result.entities[:number].slice(0, 2))
254
+ end
255
+
256
+ it 'should return the :number entity from a triple :number entity result' do
257
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
258
+ test_controller.nlp_result = triple_number_nlp_result
259
+
260
+ test_controller.current_message.message = "hi"
261
+ expect(
262
+ test_controller.get_match(['nice', :number])
263
+ ).to eq(triple_number_nlp_result.entities[:number].first)
264
+ end
265
+
266
+ it 'should return the [:number, :key_phrase] entities' do
267
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
268
+ test_controller.nlp_result = triple_number_nlp_result
269
+
270
+ test_controller.current_message.message = "hi"
271
+ expect(
272
+ test_controller.get_match(['nice', [:number, :key_phrase]])
273
+ ).to eq([89, 'scores'])
274
+ end
275
+
276
+ it 'should raise Xip::Errors::UnrecognizedMessage if more than one :number entity is returned and fuzzy_match=false' do
277
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
278
+ test_controller.nlp_result = triple_number_nlp_result
279
+
280
+ test_controller.current_message.message = "hi"
281
+ expect {
282
+ test_controller.get_match(['nice', :number], fuzzy_match: false)
283
+ }.to raise_error(Xip::Errors::UnrecognizedMessage, "Encountered 3 entity matches of type :number and expected 1. To allow, set fuzzy_match to true.")
284
+ end
285
+
286
+ it 'should raise Xip::Errors::UnrecognizedMessage if more than two :number entities are returned and fuzzy_match=false' do
287
+ allow(test_controller).to receive(:perform_nlp!).and_return(triple_number_nlp_result)
288
+ test_controller.nlp_result = triple_number_nlp_result
289
+
290
+ test_controller.current_message.message = "hi"
291
+ expect {
292
+ test_controller.get_match(['nice', [:number, :number]], fuzzy_match: false)
293
+ }.to raise_error(Xip::Errors::UnrecognizedMessage, "Encountered 1 additional entity matches of type :number for match [:number, :number]. To allow, set fuzzy_match to true.")
294
+ end
295
+
296
+ it 'should log the NLP result if log_all_nlp_results=true' do
297
+ Xip.config.log_all_nlp_results = true
298
+ Xip.config.nlp_integration = :luis
299
+
300
+ luis_client = double('luis_client')
301
+ allow(luis_client).to receive(:understand).and_return(triple_number_nlp_result)
302
+ allow(Xip::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
303
+
304
+ expect(Xip::Logger).to receive(:l).with(
305
+ topic: :nlp,
306
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
307
+ )
308
+ expect(Xip::Logger).to receive(:l).with(
309
+ topic: :nlp,
310
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: #{triple_number_nlp_result.parsed_result.inspect}"
311
+ )
312
+ test_controller.current_message.message = "hi"
313
+ test_controller.get_match(['nice', [:number, :number]])
314
+
315
+ Xip.config.log_all_nlp_results = false
316
+ Xip.config.nlp_integration = nil
317
+ end
318
+ end
319
+
320
+ describe 'custom entities' do
321
+ let(:custom_entity_nlp_result) { TestNlpResult::Luis.new(intent: yes_intent, entity: :custom_entity) }
322
+
323
+ it 'should return the text matched by the custom entity' do
324
+ allow(test_controller).to receive(:perform_nlp!).and_return(custom_entity_nlp_result)
325
+ test_controller.nlp_result = custom_entity_nlp_result
326
+
327
+ test_controller.current_message.message = "call me right away"
328
+ expect(
329
+ test_controller.get_match(['nice', :asap])
330
+ ).to eq 'right away'
331
+ end
332
+ end
333
+ end
334
+
335
+ describe "mismatch" do
336
+ describe 'raise_on_mismatch: true' do
337
+ it "should raise a Xip::Errors::UnrecognizedMessage" do
338
+ test_controller.current_message.message = 'C'
339
+ expect {
340
+ test_controller.get_match(['nice', 'woot'])
341
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
342
+ end
343
+
344
+ it "should NOT log if an nlp_result is not present" do
345
+ test_controller.current_message.message = 'spicy'
346
+ expect(Xip::Logger).to_not receive(:l)
347
+ expect {
348
+ test_controller.get_match(['nice', 'woot'])
349
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
350
+ end
351
+
352
+ it "should log if an nlp_result is present" do
353
+ test_controller.current_message.message = 'spicy'
354
+ nlp_result = double('nlp_result')
355
+ allow(nlp_result).to receive(:parsed_result).and_return({})
356
+
357
+ expect(Xip::Logger).to receive(:l).with(
358
+ topic: :nlp,
359
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: {}"
360
+ )
361
+
362
+ test_controller.nlp_result = nlp_result
363
+
364
+ expect {
365
+ test_controller.get_match(['nice', 'woot'])
366
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
367
+ end
368
+ end
369
+
370
+ describe 'raise_on_mismatch: false' do
371
+ it "should not raise a Xip::Errors::UnrecognizedMessage" do
372
+ test_controller.current_message.message = 'C'
373
+ expect {
374
+ test_controller.get_match(['nice', 'woot'], raise_on_mismatch: false)
375
+ }.to_not raise_error(Xip::Errors::UnrecognizedMessage)
376
+ end
377
+
378
+ it "should return the original message" do
379
+ test_controller.current_message.message = 'spicy'
380
+ expect(
381
+ test_controller.get_match(['nice', 'woot'], raise_on_mismatch: false)
382
+ ).to eq 'spicy'
383
+ end
384
+
385
+ it "should NOT log if an nlp_result is not present" do
386
+ test_controller.current_message.message = 'spicy'
387
+ expect(Xip::Logger).to_not receive(:l)
388
+ test_controller.get_match(['nice', 'woot'], raise_on_mismatch: false)
389
+ end
390
+
391
+ it "should log if an nlp_result is present" do
392
+ test_controller.current_message.message = 'spicy'
393
+ nlp_result = double('nlp_result')
394
+ allow(nlp_result).to receive(:parsed_result).and_return({})
395
+
396
+ expect(Xip::Logger).to receive(:l).with(
397
+ topic: :nlp,
398
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: {}"
399
+ )
400
+
401
+ test_controller.nlp_result = nlp_result
402
+
403
+ test_controller.get_match(['nice', 'woot'], raise_on_mismatch: false)
404
+ end
405
+ end
406
+ end
407
+ end
408
+
409
+ describe "handle_message" do
410
+ it "should run the proc of the matched reply" do
411
+ expect(STDOUT).to receive(:puts).with('Cool, Refinance 👍')
412
+
413
+ test_controller.current_message.message = "B"
414
+ test_controller.handle_message(
415
+ 'Buy' => proc { puts 'Buy' },
416
+ 'Refinance' => proc { puts 'Cool, Refinance 👍' }
417
+ )
418
+ end
419
+
420
+ it "should run proc in the binding of the calling instance" do
421
+ test_controller.current_message.message = "B"
422
+ x = 0
423
+ test_controller.handle_message(
424
+ 'Buy' => proc { x += 1 },
425
+ 'Refinance' => proc { x += 2 }
426
+ )
427
+
428
+ expect(x).to eq 2
429
+ end
430
+
431
+ it "should match against single-quoted ordinals" do
432
+ test_controller.current_message.message = "'B'"
433
+ x = 0
434
+ test_controller.handle_message(
435
+ 'Buy' => proc { x += 1 },
436
+ 'Refinance' => proc { x += 2 }
437
+ )
438
+
439
+ expect(x).to eq 2
440
+ end
441
+
442
+ it "should match against double-quoted ordinals" do
443
+ test_controller.current_message.message = '"A"'
444
+ x = 0
445
+ test_controller.handle_message(
446
+ 'Buy' => proc { x += 1 },
447
+ 'Refinance' => proc { x += 2 }
448
+ )
449
+
450
+ expect(x).to eq 1
451
+ end
452
+
453
+ it "should match against double-smartquoted ordinals" do
454
+ test_controller.current_message.message = '“A”'
455
+ x = 0
456
+ test_controller.handle_message(
457
+ 'Buy' => proc { x += 1 },
458
+ 'Refinance' => proc { x += 2 }
459
+ )
460
+
461
+ expect(x).to eq 1
462
+ end
463
+
464
+ it "should match against single-smartquoted ordinals" do
465
+ test_controller.current_message.message = '‘A’'
466
+ x = 0
467
+ test_controller.handle_message(
468
+ 'Buy' => proc { x += 1 },
469
+ 'Refinance' => proc { x += 2 }
470
+ )
471
+
472
+ expect(x).to eq 1
473
+ end
474
+
475
+ it "should match against ordinals with periods" do
476
+ test_controller.current_message.message = 'A.'
477
+ x = 0
478
+ test_controller.handle_message(
479
+ 'Buy' => proc { x += 1 },
480
+ 'Refinance' => proc { x += 2 }
481
+ )
482
+
483
+ expect(x).to eq 1
484
+ end
485
+
486
+ it "should match against ordinals with question marks" do
487
+ test_controller.current_message.message = 'A?'
488
+ x = 0
489
+ test_controller.handle_message(
490
+ 'Buy' => proc { x += 1 },
491
+ 'Refinance' => proc { x += 2 }
492
+ )
493
+
494
+ expect(x).to eq 1
495
+ end
496
+
497
+ it "should match against ordinals with parens" do
498
+ test_controller.current_message.message = '(A)'
499
+ x = 0
500
+ test_controller.handle_message(
501
+ 'Buy' => proc { x += 1 },
502
+ 'Refinance' => proc { x += 2 }
503
+ )
504
+
505
+ expect(x).to eq 1
506
+ end
507
+
508
+ it "should match against ordinals with backticks" do
509
+ test_controller.current_message.message = '`A`'
510
+ x = 0
511
+ test_controller.handle_message(
512
+ 'Buy' => proc { x += 1 },
513
+ 'Refinance' => proc { x += 2 }
514
+ )
515
+
516
+ expect(x).to eq 1
517
+ end
518
+
519
+ it "should match homophones" do
520
+ test_controller.current_message.message = 'sea'
521
+ x = 0
522
+ test_controller.handle_message(
523
+ 'Buy' => proc { x += 1 },
524
+ 'Refinance' => proc { x += 2 },
525
+ 'Other' => proc { x += 3 }
526
+ )
527
+
528
+ expect(x).to eq 3
529
+ end
530
+
531
+ it "should raise ReservedHomophoneUsed error if an arm contains a reserved homophone" do
532
+ test_controller.current_message.message = "B"
533
+ x = 0
534
+
535
+ expect {
536
+ test_controller.handle_message(
537
+ 'Buy' => proc { x += 1 },
538
+ :woot => proc { x += 2 },
539
+ 'Sea' => proc { x += 3 }
540
+ )
541
+ }.to raise_error(Xip::Errors::ReservedHomophoneUsed, 'Cannot use `SEA`. Reserved for homophones.')
542
+ end
543
+
544
+ it "should not run NLP if an ordinal is entered by the user" do
545
+ test_controller.current_message.message = "C"
546
+ x = 0
547
+ test_controller.handle_message(
548
+ :yes => proc { x += 1 },
549
+ :no => proc { x += 2 },
550
+ 'Unsubscribe' => proc { x += 3 }
551
+ )
552
+
553
+ expect(test_controller).to_not receive(:perform_nlp!)
554
+ expect(x).to eq 3
555
+ end
556
+
557
+ describe "intent detection" do
558
+ let(:no_intent) { :no }
559
+ let(:yes_intent) { :yes }
560
+ let(:yes_intent_nlp_result) { TestNlpResult::Luis.new(intent: yes_intent, entity: :single_number_entity) }
561
+ let(:no_intent_nlp_result) { TestNlpResult::Luis.new(intent: no_intent, entity: :double_number_entity) }
562
+
563
+ it 'should support :yes intent' do
564
+ test_controller.current_message.message = "YAS"
565
+ allow(test_controller).to receive(:perform_nlp!).and_return(yes_intent_nlp_result)
566
+ test_controller.nlp_result = yes_intent_nlp_result
567
+
568
+ x = 0
569
+ test_controller.send(
570
+ :handle_message, {
571
+ 'Buy' => proc { x += 1 },
572
+ :yes => proc { x += 9 },
573
+ :no => proc { x += 8 }
574
+ }
575
+ )
576
+
577
+ expect(x).to eq 9
578
+ end
579
+
580
+ it 'should support :no intent' do
581
+ test_controller.current_message.message = "NAH"
582
+ allow(test_controller).to receive(:perform_nlp!).and_return(no_intent_nlp_result)
583
+ test_controller.nlp_result = no_intent_nlp_result
584
+
585
+ x = 0
586
+ test_controller.send(
587
+ :handle_message, {
588
+ 'Buy' => proc { x += 1 },
589
+ :yes => proc { x += 9 },
590
+ :no => proc { x += 8 }
591
+ }
592
+ )
593
+
594
+ expect(x).to eq 8
595
+ end
596
+
597
+ it 'should log the NLP result if log_all_nlp_results=true' do
598
+ Xip.config.log_all_nlp_results = true
599
+ Xip.config.nlp_integration = :luis
600
+
601
+ luis_client = double('luis_client')
602
+ allow(luis_client).to receive(:understand).and_return(yes_intent_nlp_result)
603
+ allow(Xip::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
604
+
605
+ expect(Xip::Logger).to receive(:l).with(
606
+ topic: :nlp,
607
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
608
+ )
609
+ expect(Xip::Logger).to receive(:l).with(
610
+ topic: :nlp,
611
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: #{yes_intent_nlp_result.parsed_result.inspect}"
612
+ )
613
+ test_controller.current_message.message = "YAS"
614
+ x = 0
615
+ test_controller.send(
616
+ :handle_message, {
617
+ 'Buy' => proc { x += 1 },
618
+ :yes => proc { x += 9 },
619
+ :no => proc { x += 8 }
620
+ }
621
+ )
622
+
623
+ Xip.config.log_all_nlp_results = false
624
+ Xip.config.nlp_integration = nil
625
+ end
626
+ end
627
+
628
+ describe 'Regexp matcher' do
629
+ it "should match when the Regexp matches" do
630
+ test_controller.current_message.message = "About Encom"
631
+ x = 0
632
+ test_controller.handle_message(
633
+ 'Buy' => proc { x += 1 },
634
+ 'Refinance' => proc { x += 2 },
635
+ /about/i => proc { x += 10 }
636
+ )
637
+ expect(x).to eq 10
638
+ end
639
+
640
+ it "should match positional Regexes" do
641
+ test_controller.current_message.message = "Jump about"
642
+ x = 0
643
+ test_controller.handle_message(
644
+ 'Buy' => proc { x += 1 },
645
+ /\Aabout/i => proc { x += 2 },
646
+ /about/i => proc { x += 10 }
647
+ )
648
+ expect(x).to eq 10
649
+ end
650
+
651
+ it "should match as an alpha ordinal" do
652
+ test_controller.current_message.message = "C"
653
+ x = 0
654
+ test_controller.handle_message(
655
+ 'Buy' => proc { x += 1 },
656
+ 'Refinance' => proc { x += 2 },
657
+ /about/i => proc { x += 10 }
658
+ )
659
+ expect(x).to eq 10
660
+ end
661
+ end
662
+
663
+ describe 'nil matcher' do
664
+ it "should match the respective ordinal" do
665
+ test_controller.current_message.message = "C"
666
+ x = 0
667
+ test_controller.handle_message(
668
+ 'Buy' => proc { x += 1 },
669
+ 'Refinance' => proc { x += 2 },
670
+ nil => proc { x += 10 }
671
+ )
672
+ expect(x).to eq 10
673
+ end
674
+
675
+ it "should match an unknown ordinal" do
676
+ test_controller.current_message.message = "D"
677
+ x = 0
678
+ test_controller.handle_message(
679
+ 'Buy' => proc { x += 1 },
680
+ 'Refinance' => proc { x += 2 },
681
+ nil => proc { x += 10 }
682
+ )
683
+ expect(x).to eq 10
684
+ end
685
+
686
+ it "should match free-form text" do
687
+ test_controller.current_message.message = "Hello world!"
688
+ x = 0
689
+ test_controller.handle_message(
690
+ 'Buy' => proc { x += 1 },
691
+ 'Refinance' => proc { x += 2 },
692
+ nil => proc { x += 10 }
693
+ )
694
+ expect(x).to eq 10
695
+ end
696
+ end
697
+
698
+ it "should raise Xip::Errors::UnrecognizedMessage if the reply does not match" do
699
+ test_controller.current_message.message = "C"
700
+ x = 0
701
+ expect {
702
+ test_controller.handle_message(
703
+ 'Buy' => proc { x += 1 },
704
+ 'Refinance' => proc { x += 2 }
705
+ )
706
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
707
+ end
708
+
709
+ it "should NOT log if an nlp_result is not present" do
710
+ test_controller.current_message.message = 'spicy'
711
+ expect(Xip::Logger).to_not receive(:l)
712
+
713
+ x = 0
714
+ expect {
715
+ test_controller.handle_message(
716
+ 'Buy' => proc { x += 1 },
717
+ 'Refinance' => proc { x += 2 }
718
+ )
719
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
720
+ end
721
+
722
+ it "should log if an nlp_result is present" do
723
+ test_controller.current_message.message = 'spicy'
724
+ nlp_result = double('nlp_result')
725
+ allow(nlp_result).to receive(:parsed_result).and_return({})
726
+
727
+ expect(Xip::Logger).to receive(:l).with(
728
+ topic: :nlp,
729
+ message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> NLP Result: {}"
730
+ )
731
+
732
+ test_controller.nlp_result = nlp_result
733
+
734
+ x = 0
735
+ expect {
736
+ test_controller.handle_message(
737
+ 'Buy' => proc { x += 1 },
738
+ 'Refinance' => proc { x += 2 }
739
+ )
740
+ }.to raise_error(Xip::Errors::UnrecognizedMessage)
741
+ end
742
+ end
743
+
744
+ end