stealth 1.1.5 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +27 -11
  3. data/CHANGELOG.md +77 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +53 -49
  6. data/LICENSE +4 -17
  7. data/README.md +9 -17
  8. data/VERSION +1 -1
  9. data/lib/stealth/base.rb +72 -21
  10. data/lib/stealth/cli.rb +1 -2
  11. data/lib/stealth/commands/console.rb +1 -1
  12. data/lib/stealth/configuration.rb +6 -3
  13. data/lib/stealth/controller/callbacks.rb +1 -1
  14. data/lib/stealth/controller/catch_all.rb +27 -4
  15. data/lib/stealth/controller/controller.rb +168 -49
  16. data/lib/stealth/controller/dev_jumps.rb +41 -0
  17. data/lib/stealth/controller/dynamic_delay.rb +4 -6
  18. data/lib/stealth/controller/interrupt_detect.rb +100 -0
  19. data/lib/stealth/controller/messages.rb +283 -0
  20. data/lib/stealth/controller/nlp.rb +50 -0
  21. data/lib/stealth/controller/replies.rb +183 -40
  22. data/lib/stealth/controller/unrecognized_message.rb +62 -0
  23. data/lib/stealth/{flow/core_ext.rb → core_ext/numeric.rb} +0 -1
  24. data/lib/stealth/core_ext/string.rb +18 -0
  25. data/lib/stealth/core_ext.rb +5 -0
  26. data/lib/stealth/dispatcher.rb +21 -0
  27. data/lib/stealth/errors.rb +12 -0
  28. data/lib/stealth/flow/base.rb +1 -2
  29. data/lib/stealth/flow/specification.rb +3 -2
  30. data/lib/stealth/flow/state.rb +3 -3
  31. data/lib/stealth/generators/builder/Gemfile +4 -3
  32. data/lib/stealth/generators/builder/bot/controllers/bot_controller.rb +42 -0
  33. data/lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb +2 -0
  34. data/lib/stealth/generators/builder/bot/controllers/goodbyes_controller.rb +2 -0
  35. data/lib/stealth/generators/builder/bot/controllers/hellos_controller.rb +2 -0
  36. data/lib/stealth/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  37. data/lib/stealth/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  38. data/lib/stealth/generators/builder/config/flow_map.rb +8 -0
  39. data/lib/stealth/generators/builder/config/initializers/autoload.rb +8 -0
  40. data/lib/stealth/generators/builder/config/initializers/inflections.rb +16 -0
  41. data/lib/stealth/generators/builder/config/puma.rb +15 -0
  42. data/lib/stealth/helpers/redis.rb +40 -0
  43. data/lib/stealth/lock.rb +83 -0
  44. data/lib/stealth/logger.rb +27 -18
  45. data/lib/stealth/nlp/client.rb +22 -0
  46. data/lib/stealth/nlp/result.rb +57 -0
  47. data/lib/stealth/reloader.rb +90 -0
  48. data/lib/stealth/reply.rb +17 -0
  49. data/lib/stealth/scheduled_reply.rb +3 -3
  50. data/lib/stealth/server.rb +8 -3
  51. data/lib/stealth/service_message.rb +3 -2
  52. data/lib/stealth/service_reply.rb +5 -1
  53. data/lib/stealth/services/base_reply_handler.rb +10 -2
  54. data/lib/stealth/session.rb +106 -53
  55. data/spec/configuration_spec.rb +42 -2
  56. data/spec/controller/callbacks_spec.rb +23 -28
  57. data/spec/controller/catch_all_spec.rb +87 -29
  58. data/spec/controller/controller_spec.rb +444 -43
  59. data/spec/controller/dynamic_delay_spec.rb +16 -18
  60. data/spec/controller/helpers_spec.rb +1 -2
  61. data/spec/controller/interrupt_detect_spec.rb +171 -0
  62. data/spec/controller/messages_spec.rb +744 -0
  63. data/spec/controller/nlp_spec.rb +93 -0
  64. data/spec/controller/replies_spec.rb +446 -11
  65. data/spec/controller/unrecognized_message_spec.rb +168 -0
  66. data/spec/dispatcher_spec.rb +79 -0
  67. data/spec/flow/flow_spec.rb +1 -2
  68. data/spec/flow/state_spec.rb +14 -3
  69. data/spec/helpers/redis_spec.rb +77 -0
  70. data/spec/lock_spec.rb +100 -0
  71. data/spec/nlp/client_spec.rb +23 -0
  72. data/spec/nlp/result_spec.rb +57 -0
  73. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  74. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  75. data/spec/replies/messages/say_randomize_text.yml +10 -0
  76. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  77. data/spec/reply_spec.rb +61 -0
  78. data/spec/scheduled_reply_spec.rb +23 -0
  79. data/spec/service_reply_spec.rb +1 -2
  80. data/spec/session_spec.rb +251 -12
  81. data/spec/spec_helper.rb +21 -0
  82. data/spec/support/controllers/vaders_controller.rb +24 -0
  83. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  84. data/spec/support/nlp_clients/luis.rb +9 -0
  85. data/spec/support/nlp_results/luis_result.rb +163 -0
  86. data/spec/version_spec.rb +1 -2
  87. data/stealth.gemspec +6 -6
  88. metadata +83 -38
  89. data/docs/00-introduction.md +0 -37
  90. data/docs/01-getting-started.md +0 -21
  91. data/docs/02-local-development.md +0 -40
  92. data/docs/03-basics.md +0 -171
  93. data/docs/04-sessions.md +0 -29
  94. data/docs/05-controllers.md +0 -179
  95. data/docs/06-models.md +0 -39
  96. data/docs/07-replies.md +0 -114
  97. data/docs/08-catchalls.md +0 -49
  98. data/docs/09-messaging-integrations.md +0 -80
  99. data/docs/10-nlp-integrations.md +0 -13
  100. data/docs/11-analytics.md +0 -13
  101. data/docs/12-commands.md +0 -62
  102. data/docs/13-deployment.md +0 -50
  103. 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 File.expand_path(File.join(File.dirname(__FILE__), '..', '/spec_helper'))
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(service_replies: service_replies, position: 0)
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 SHORT_DELAY for a dynamic delay at position -1" do
18
- delay = controller.dynamic_delay(service_replies: service_replies, position: -1)
19
- expect(delay).to eq(Stealth::Controller::DynamicDelay::SHORT_DELAY)
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(service_replies: service_replies, position: 2)
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(service_replies: service_replies, position: 4)
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(service_replies: service_replies, position: 6)
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(service_replies: service_replies, position: 8)
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(service_replies: service_replies, position: 10)
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(service_replies: service_replies, position: 12)
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(service_replies: service_replies, position: 14)
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(service_replies: service_replies, position: 16)
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(service_replies: service_replies, position: 18)
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(service_replies: service_replies, position: 20)
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
-
@@ -1,7 +1,6 @@
1
- # coding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '/spec_helper'))
3
+ require 'spec_helper'
5
4
 
6
5
  $:.unshift File.expand_path("../support/helpers", __dir__)
7
6
 
@@ -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