stealth 1.1.2 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +18 -8
- data/CHANGELOG.md +100 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +49 -43
- data/LICENSE +4 -17
- data/README.md +9 -17
- data/VERSION +1 -1
- data/lib/stealth/base.rb +62 -13
- data/lib/stealth/cli.rb +1 -2
- data/lib/stealth/commands/console.rb +1 -1
- data/lib/stealth/configuration.rb +0 -3
- data/lib/stealth/controller/callbacks.rb +1 -1
- data/lib/stealth/controller/catch_all.rb +27 -4
- data/lib/stealth/controller/controller.rb +168 -49
- data/lib/stealth/controller/dev_jumps.rb +41 -0
- data/lib/stealth/controller/dynamic_delay.rb +4 -6
- data/lib/stealth/controller/interrupt_detect.rb +100 -0
- data/lib/stealth/controller/messages.rb +283 -0
- data/lib/stealth/controller/nlp.rb +50 -0
- data/lib/stealth/controller/replies.rb +179 -41
- data/lib/stealth/controller/unrecognized_message.rb +62 -0
- data/lib/stealth/core_ext.rb +5 -0
- data/lib/stealth/{flow/core_ext.rb → core_ext/numeric.rb} +0 -1
- data/lib/stealth/core_ext/string.rb +18 -0
- data/lib/stealth/dispatcher.rb +21 -0
- data/lib/stealth/errors.rb +12 -0
- data/lib/stealth/flow/base.rb +1 -2
- data/lib/stealth/flow/specification.rb +3 -2
- data/lib/stealth/flow/state.rb +3 -3
- data/lib/stealth/generators/builder/Gemfile +4 -3
- data/lib/stealth/generators/builder/bot/controllers/bot_controller.rb +42 -0
- data/lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/goodbyes_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/hellos_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
- data/lib/stealth/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
- data/lib/stealth/generators/builder/config/flow_map.rb +8 -0
- data/lib/stealth/generators/builder/config/initializers/autoload.rb +8 -0
- data/lib/stealth/generators/builder/config/initializers/inflections.rb +16 -0
- data/lib/stealth/generators/builder/config/puma.rb +15 -0
- data/lib/stealth/helpers/redis.rb +40 -0
- data/lib/stealth/lock.rb +83 -0
- data/lib/stealth/logger.rb +27 -18
- data/lib/stealth/nlp/client.rb +22 -0
- data/lib/stealth/nlp/result.rb +57 -0
- data/lib/stealth/reloader.rb +90 -0
- data/lib/stealth/reply.rb +17 -0
- data/lib/stealth/scheduled_reply.rb +3 -3
- data/lib/stealth/server.rb +4 -4
- data/lib/stealth/service_message.rb +3 -2
- data/lib/stealth/service_reply.rb +5 -1
- data/lib/stealth/services/base_reply_handler.rb +2 -2
- data/lib/stealth/session.rb +106 -53
- data/spec/configuration_spec.rb +9 -2
- data/spec/controller/callbacks_spec.rb +23 -28
- data/spec/controller/catch_all_spec.rb +81 -29
- data/spec/controller/controller_spec.rb +444 -43
- data/spec/controller/dynamic_delay_spec.rb +16 -18
- data/spec/controller/helpers_spec.rb +1 -2
- data/spec/controller/interrupt_detect_spec.rb +171 -0
- data/spec/controller/messages_spec.rb +744 -0
- data/spec/controller/nlp_spec.rb +93 -0
- data/spec/controller/replies_spec.rb +446 -11
- data/spec/controller/unrecognized_message_spec.rb +168 -0
- data/spec/dispatcher_spec.rb +79 -0
- data/spec/flow/flow_spec.rb +1 -2
- data/spec/flow/state_spec.rb +14 -3
- data/spec/helpers/redis_spec.rb +77 -0
- data/spec/lock_spec.rb +100 -0
- data/spec/nlp/client_spec.rb +23 -0
- data/spec/nlp/result_spec.rb +57 -0
- data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
- data/spec/replies/messages/say_randomize_speech.yml +10 -0
- data/spec/replies/messages/say_randomize_text.yml +10 -0
- data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
- data/spec/reply_spec.rb +61 -0
- data/spec/scheduled_reply_spec.rb +23 -0
- data/spec/service_reply_spec.rb +1 -2
- data/spec/session_spec.rb +251 -12
- data/spec/spec_helper.rb +21 -0
- data/spec/support/controllers/vaders_controller.rb +24 -0
- data/spec/support/nlp_clients/dialogflow.rb +9 -0
- data/spec/support/nlp_clients/luis.rb +9 -0
- data/spec/support/nlp_results/luis_result.rb +163 -0
- data/spec/version_spec.rb +1 -2
- data/stealth.gemspec +6 -6
- metadata +83 -39
- data/docs/00-introduction.md +0 -37
- data/docs/01-getting-started.md +0 -21
- data/docs/02-local-development.md +0 -40
- data/docs/03-basics.md +0 -171
- data/docs/04-sessions.md +0 -29
- data/docs/05-controllers.md +0 -179
- data/docs/06-models.md +0 -39
- data/docs/07-replies.md +0 -114
- data/docs/08-catchalls.md +0 -49
- data/docs/09-messaging-integrations.md +0 -80
- data/docs/10-nlp-integrations.md +0 -13
- data/docs/11-analytics.md +0 -13
- data/docs/12-commands.md +0 -62
- data/docs/13-deployment.md +0 -50
- data/lib/stealth/generators/builder/config/initializers/.keep +0 -0
@@ -1,72 +1,70 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
3
|
+
require 'spec_helper'
|
5
4
|
|
6
5
|
describe "Stealth::Controller::DynamicDelay" do
|
7
6
|
|
8
7
|
let(:facebook_message) { SampleMessage.new(service: 'facebook') }
|
9
8
|
let(:controller) { VadersController.new(service_message: facebook_message.message_with_text) }
|
10
|
-
let(:service_replies) { YAML.load(File.read(File.expand_path("../replies/messages/say_howdy_with_dynamic.yml", __dir__))) }
|
9
|
+
let!(:service_replies) { YAML.load(File.read(File.expand_path("../replies/messages/say_howdy_with_dynamic.yml", __dir__))) }
|
11
10
|
|
12
11
|
it "should return a SHORT_DELAY for a dynamic delay at position 0" do
|
13
|
-
delay = controller.dynamic_delay(
|
12
|
+
delay = controller.dynamic_delay(previous_reply: nil)
|
14
13
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::SHORT_DELAY)
|
15
14
|
end
|
16
15
|
|
17
|
-
it "should return a
|
18
|
-
delay = controller.dynamic_delay(
|
19
|
-
expect(delay).to eq(Stealth::Controller::DynamicDelay::
|
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(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
20
19
|
end
|
21
20
|
|
22
21
|
it "should return a SHORT_DELAY for text 35 chars long" do
|
23
|
-
delay = controller.dynamic_delay(
|
22
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[1])
|
24
23
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::SHORT_DELAY)
|
25
24
|
end
|
26
25
|
|
27
26
|
it "should return a STANDARD_DELAY for text 120 chars long" do
|
28
|
-
delay = controller.dynamic_delay(
|
27
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[3])
|
29
28
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
30
29
|
end
|
31
30
|
|
32
31
|
it "should return a (STANDARD_DELAY * 1.5) for text 230 chars long" do
|
33
|
-
delay = controller.dynamic_delay(
|
32
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[5])
|
34
33
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY * 1.5)
|
35
34
|
end
|
36
35
|
|
37
36
|
it "should return a LONG_DELAY for text 350 chars long" do
|
38
|
-
delay = controller.dynamic_delay(
|
37
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[7])
|
39
38
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::LONG_DELAY)
|
40
39
|
end
|
41
40
|
|
42
41
|
it "should return a STANDARD_DELAY for an image" do
|
43
|
-
delay = controller.dynamic_delay(
|
42
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[9])
|
44
43
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
45
44
|
end
|
46
45
|
|
47
46
|
it "should return a STANDARD_DELAY for a video" do
|
48
|
-
delay = controller.dynamic_delay(
|
47
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[11])
|
49
48
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
50
49
|
end
|
51
50
|
|
52
51
|
it "should return a STANDARD_DELAY for an audio" do
|
53
|
-
delay = controller.dynamic_delay(
|
52
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[13])
|
54
53
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
55
54
|
end
|
56
55
|
|
57
56
|
it "should return a STANDARD_DELAY for a file" do
|
58
|
-
delay = controller.dynamic_delay(
|
57
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[15])
|
59
58
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
60
59
|
end
|
61
60
|
|
62
61
|
it "should return a STANDARD_DELAY for cards" do
|
63
|
-
delay = controller.dynamic_delay(
|
62
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[17])
|
64
63
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
65
64
|
end
|
66
65
|
|
67
66
|
it "should return a STANDARD_DELAY for a list" do
|
68
|
-
delay = controller.dynamic_delay(
|
67
|
+
delay = controller.dynamic_delay(previous_reply: service_replies[19])
|
69
68
|
expect(delay).to eq(Stealth::Controller::DynamicDelay::STANDARD_DELAY)
|
70
69
|
end
|
71
70
|
end
|
72
|
-
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "Stealth::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(Stealth::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(Stealth::Logger).to receive(:l).with(
|
42
|
+
topic: 'interrupt',
|
43
|
+
message: "Interrupt detected for session #{fb_message.sender_id}"
|
44
|
+
).ordered
|
45
|
+
|
46
|
+
expect(Stealth::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 < Stealth::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 < Stealth::Controller
|
66
|
+
def say_interrupted
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
expect(Stealth::Logger).to receive(:l).with(
|
71
|
+
topic: 'interrupt',
|
72
|
+
message: "Interrupt detected for session #{fb_message.sender_id}"
|
73
|
+
).ordered
|
74
|
+
|
75
|
+
expect(Stealth::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 < Stealth::Controller
|
85
|
+
def say_interrupted
|
86
|
+
raise Stealth::Errors::ReplyNotFound
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Once for the interrupt detection, once for the error
|
91
|
+
expect(Stealth::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(Stealth.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(Stealth.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 Stealth::Controller::Messages do
|
6
|
+
|
7
|
+
class MrTronsController < Stealth::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
|
+
Stealth::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(Stealth::Errors::ReservedHomophoneUsed, 'Cannot use `SEA, BEE`. Reserved for homophones.')
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should raise Stealth::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(Stealth::Errors::UnrecognizedMessage)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should raise Stealth::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(Stealth::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 Stealth::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(Stealth::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
|
+
Stealth.config.log_all_nlp_results = true
|
203
|
+
Stealth.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(Stealth::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
|
208
|
+
|
209
|
+
expect(Stealth::Logger).to receive(:l).with(
|
210
|
+
topic: :nlp,
|
211
|
+
message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
|
212
|
+
)
|
213
|
+
expect(Stealth::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
|
+
Stealth.config.log_all_nlp_results = false
|
221
|
+
Stealth.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 Stealth::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(Stealth::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 Stealth::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(Stealth::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
|
+
Stealth.config.log_all_nlp_results = true
|
298
|
+
Stealth.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(Stealth::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
|
303
|
+
|
304
|
+
expect(Stealth::Logger).to receive(:l).with(
|
305
|
+
topic: :nlp,
|
306
|
+
message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
|
307
|
+
)
|
308
|
+
expect(Stealth::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
|
+
Stealth.config.log_all_nlp_results = false
|
316
|
+
Stealth.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 Stealth::Errors::UnrecognizedMessage" do
|
338
|
+
test_controller.current_message.message = 'C'
|
339
|
+
expect {
|
340
|
+
test_controller.get_match(['nice', 'woot'])
|
341
|
+
}.to raise_error(Stealth::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(Stealth::Logger).to_not receive(:l)
|
347
|
+
expect {
|
348
|
+
test_controller.get_match(['nice', 'woot'])
|
349
|
+
}.to raise_error(Stealth::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(Stealth::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(Stealth::Errors::UnrecognizedMessage)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
describe 'raise_on_mismatch: false' do
|
371
|
+
it "should not raise a Stealth::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(Stealth::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(Stealth::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(Stealth::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(Stealth::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
|
+
Stealth.config.log_all_nlp_results = true
|
599
|
+
Stealth.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(Stealth::Nlp::Luis::Client).to receive(:new).and_return(luis_client)
|
604
|
+
|
605
|
+
expect(Stealth::Logger).to receive(:l).with(
|
606
|
+
topic: :nlp,
|
607
|
+
message: "User 8b3e0a3c-62f1-401e-8b0f-615c9d256b1f -> Performing NLP."
|
608
|
+
)
|
609
|
+
expect(Stealth::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
|
+
Stealth.config.log_all_nlp_results = false
|
624
|
+
Stealth.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 Stealth::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(Stealth::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(Stealth::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(Stealth::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(Stealth::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(Stealth::Errors::UnrecognizedMessage)
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
744
|
+
end
|