stealth 2.0.0.beta2 → 2.0.0.beta5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acde4522ee0154ec5557379bfd1fa928e89771637e4c3dd99880adb624b7c2f7
4
- data.tar.gz: f30cc740eb9c40fb90c19cbe35a27cf12ce24209245813d338db25df61ca238a
3
+ metadata.gz: 24ba7ba55d264e21395db7b9166ecf96064c2c0d8c8bc51d09dd274ecaf5fd34
4
+ data.tar.gz: c42254f819550577f249fddb13266cfa6bc8e2f27760994e3efdf7798447ecdd
5
5
  SHA512:
6
- metadata.gz: 63ad339fe8d1473ab1118eef9279cf66f1a895748fe61ca7671d49ca8de635fe27f9e4f200c1ae05523501f62e135eb4147d3609f9550c920311498a7abfa26c
7
- data.tar.gz: cbcc9083bb56acc3d30aedbcc6a853fb71cf5a57b0586e81b586c36d1f186c5bc39cf5acf6d03768c86c486c7a6ff987ca5e151248cf43c34b70b21778c285ef
6
+ metadata.gz: 3f1af77ae1b811b4f53f85df2365fae399efe6caf9db77a801898250dfcdb732e366b06efa1641b255eda53e2a22fff38ca7a8ebd6577d6dcf860509d002c261
7
+ data.tar.gz: 7146a30ad60302ccc322e6691c6676b6b281dee1625bc1acf674e133b87d371b5cc470c5d3d4eb6d50526d33992a81f424364fbf4b10e23d93910aed815b5864
data/.circleci/config.yml CHANGED
@@ -3,7 +3,7 @@ version: 2
3
3
  jobs:
4
4
  ruby_2_6:
5
5
  docker:
6
- - image: circleci/ruby:2.6-node-browsers
6
+ - image: cimg/ruby:2.6
7
7
  environment:
8
8
  STEALTH_ENV: test
9
9
 
@@ -56,7 +56,115 @@ jobs:
56
56
  destination: test-results
57
57
  ruby_2_7:
58
58
  docker:
59
- - image: circleci/ruby:2.7-node-browsers
59
+ - image: cimg/ruby:2.7
60
+ environment:
61
+ STEALTH_ENV: test
62
+
63
+ working_directory: ~/repo
64
+
65
+ steps:
66
+ - checkout
67
+
68
+ # Download and cache dependencies
69
+ - restore_cache:
70
+ keys:
71
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
72
+ # fallback to using the latest cache if no exact match is found
73
+ - v1-dependencies-
74
+ - run:
75
+ name: Configure Bundler
76
+ command: |
77
+ echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
78
+ source $BASH_ENV
79
+ gem install bundler
80
+ - run:
81
+ name: install dependencies
82
+ command: |
83
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
84
+
85
+ - save_cache:
86
+ paths:
87
+ - ./vendor/bundle
88
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
89
+
90
+ # run tests!
91
+ - run:
92
+ name: run tests
93
+ command: |
94
+ mkdir /tmp/test-results
95
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
96
+
97
+ bundle exec rspec --format progress \
98
+ --format RspecJunitFormatter \
99
+ --out /tmp/test-results/rspec.xml \
100
+ --format progress \
101
+ -- \
102
+ $TEST_FILES
103
+
104
+ # collect reports
105
+ - store_test_results:
106
+ path: /tmp/test-results
107
+ - store_artifacts:
108
+ path: /tmp/test-results
109
+ destination: test-results
110
+
111
+ ruby_3_0:
112
+ docker:
113
+ - image: cimg/ruby:3.0
114
+ environment:
115
+ STEALTH_ENV: test
116
+
117
+ working_directory: ~/repo
118
+
119
+ steps:
120
+ - checkout
121
+
122
+ # Download and cache dependencies
123
+ - restore_cache:
124
+ keys:
125
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}
126
+ # fallback to using the latest cache if no exact match is found
127
+ - v1-dependencies-
128
+ - run:
129
+ name: Configure Bundler
130
+ command: |
131
+ echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
132
+ source $BASH_ENV
133
+ gem install bundler
134
+ - run:
135
+ name: install dependencies
136
+ command: |
137
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
138
+
139
+ - save_cache:
140
+ paths:
141
+ - ./vendor/bundle
142
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}
143
+
144
+ # run tests!
145
+ - run:
146
+ name: run tests
147
+ command: |
148
+ mkdir /tmp/test-results
149
+ TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
150
+
151
+ bundle exec rspec --format progress \
152
+ --format RspecJunitFormatter \
153
+ --out /tmp/test-results/rspec.xml \
154
+ --format progress \
155
+ -- \
156
+ $TEST_FILES
157
+
158
+ # collect reports
159
+ - store_test_results:
160
+ path: /tmp/test-results
161
+ - store_artifacts:
162
+ path: /tmp/test-results
163
+ destination: test-results
164
+
165
+ ruby_3_1:
166
+ docker:
167
+ - image: cimg/ruby:3.1
60
168
  environment:
61
169
  STEALTH_ENV: test
62
170
 
@@ -114,3 +222,5 @@ workflows:
114
222
  jobs:
115
223
  - ruby_2_6
116
224
  - ruby_2_7
225
+ - ruby_3_0
226
+ - ruby_3_1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ## Enhancements
4
4
 
5
+ * Added support for Ruby 3.0
5
6
  * [Controllers] Added support for Dev Jumping. This feature allows developers to jump around flows and states for bot's in development.
6
7
  * [NLP] Added base classes for `Stealth::Nlp::Result` and `Stealth::Nlp::Client` to be used by NLP drivers.
7
8
  * [Controllers] Scheduled replies no longer call `controller.route` when they run. Instead we `step_to` to the flow and state directly. This ensures the `route` is reserved for incoming messages.
@@ -13,7 +14,8 @@
13
14
  * [Logging] `primary_session`, `previous_session`, and `back_to_session` now explicitly logged
14
15
  * [Sessions] The session is no longer set on update or stepping witht destination flow and state match the existing session.
15
16
  * [Scheduled Replies] The `service_message.target_id` is now set for scheduled replies. NOTE: scheduled replies that are already enqueued will NOT have this set.
16
- * [Server] Updated to Puma 4.3
17
+ * [Server] Updated to Puma 5.5
18
+ * [Server] Updated to Sidekiq 6.3
17
19
  * [Server] Updated to Sinatra 2.1
18
20
  * [Sessions] Added `to_s` for sessions to pretty print the slug. Useful when debugging.
19
21
  * `send_reples` now supports two additional options for replies:
@@ -40,7 +42,7 @@
40
42
  * [Controllers] `normalized_msg` and `homophone_translated_msg` are now memoized for performance.
41
43
  * [Errors] `Stealth::Errors::MessageNotRecognized` has been renamed to `Stealth::Errors::UnrecognizedMessage`
42
44
  * [Controllers] When `handle_message` or `get_match` raise a `Stealth::Errors::UnrecognizedMessage`, the user is first routed to a new `UnrecognizedMessagesController` to perform NLP. If that controller fails to match, the `catch_all` is run as normal.
43
- * [Errors] Client errors now call respective BotController actions: `handle_opt_out` and `handle_invalid_session_id`. Each client is responsible for raising `Stealth::Errors::UserOptOut` or `Stealth::Errors::InvalidSessionId` errors.
45
+ * [Errors] Client errors now call respective BotController actions: `handle_opt_out`, `handle_invalid_session_id`, `handle_message_filtered`, `handle_unknown_error`. Each client is responsible for raising `Stealth::Errors::UserOptOut`, `Stealth::Errors::InvalidSessionId`, `Stealth::Errors::MessageFiltered`, `Stealth::Errors::UnknownError` errors, respectively.
44
46
  * [Controllers] `handle_message` and `get_match` now detect homophones for alpha ordinals (A-Z)
45
47
  * [Controllers] `handle_message` and `get_match` now ignore single and double quotes for alpha-ordinals
46
48
  * [CoreExt] Strings now have a `normalize` method for removing padding and quotes
@@ -51,6 +53,9 @@
51
53
  * [Controllers] `handle_message` now supports `Regexp` keys.
52
54
  * [Configuration] `database.yml` is now parsed with ERB in order to support environment variables. Thanks @malkovro.
53
55
  * [Replies] Speech and SSML replies now use `speech` and `ssml` as keys, respectively, instead of `text`
56
+ * [Replies] Voice services (determined by having "voice" in the name) now automatically skip auto-delays.
57
+ * [Controllers] `current_message` now has a `confidence` attribute containing a float with the confidence value of the transcription (from 0 to 1).
58
+ * [Controllers] Added a `halt!` method that can be used with the controller error handlers to stop code execution.
54
59
 
55
60
  ## Bug Fixes
56
61
 
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stealth (2.0.0.beta2)
4
+ stealth (2.0.0.beta5)
5
5
  activesupport (~> 6.0)
6
6
  multi_json (~> 1.12)
7
- puma (>= 4.2, < 5.0)
7
+ puma (>= 4.2, < 6.0)
8
8
  sidekiq (~> 6.0)
9
9
  sinatra (~> 2.0)
10
10
  thor (~> 1.0)
@@ -12,32 +12,32 @@ PATH
12
12
  GEM
13
13
  remote: https://rubygems.org/
14
14
  specs:
15
- activesupport (6.1.0)
15
+ activesupport (6.1.5)
16
16
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
17
  i18n (>= 1.6, < 2)
18
18
  minitest (>= 5.1)
19
19
  tzinfo (~> 2.0)
20
20
  zeitwerk (~> 2.3)
21
- concurrent-ruby (1.1.7)
22
- connection_pool (2.2.3)
21
+ concurrent-ruby (1.1.10)
22
+ connection_pool (2.2.5)
23
23
  diff-lcs (1.3)
24
- i18n (1.8.5)
24
+ i18n (1.10.0)
25
25
  concurrent-ruby (~> 1.0)
26
- minitest (5.14.2)
26
+ minitest (5.15.0)
27
27
  mock_redis (0.23.0)
28
28
  multi_json (1.15.0)
29
29
  mustermann (1.1.1)
30
30
  ruby2_keywords (~> 0.0.1)
31
- nio4r (2.5.4)
31
+ nio4r (2.5.8)
32
32
  oj (3.10.6)
33
- puma (4.3.7)
33
+ puma (5.6.2)
34
34
  nio4r (~> 2.0)
35
35
  rack (2.2.3)
36
- rack-protection (2.1.0)
36
+ rack-protection (2.2.0)
37
37
  rack
38
38
  rack-test (1.1.0)
39
39
  rack (>= 1.0, < 3)
40
- redis (4.2.5)
40
+ redis (4.6.0)
41
41
  rspec (3.9.0)
42
42
  rspec-core (~> 3.9.0)
43
43
  rspec-expectations (~> 3.9.0)
@@ -53,21 +53,21 @@ GEM
53
53
  rspec-support (3.9.2)
54
54
  rspec_junit_formatter (0.4.1)
55
55
  rspec-core (>= 2, < 4, != 2.12.0)
56
- ruby2_keywords (0.0.2)
57
- sidekiq (6.1.2)
56
+ ruby2_keywords (0.0.5)
57
+ sidekiq (6.4.1)
58
58
  connection_pool (>= 2.2.2)
59
59
  rack (~> 2.0)
60
60
  redis (>= 4.2.0)
61
- sinatra (2.1.0)
61
+ sinatra (2.2.0)
62
62
  mustermann (~> 1.0)
63
63
  rack (~> 2.2)
64
- rack-protection (= 2.1.0)
64
+ rack-protection (= 2.2.0)
65
65
  tilt (~> 2.0)
66
- thor (1.0.1)
66
+ thor (1.2.1)
67
67
  tilt (2.0.10)
68
- tzinfo (2.0.3)
68
+ tzinfo (2.0.4)
69
69
  concurrent-ruby (~> 1.0)
70
- zeitwerk (2.4.2)
70
+ zeitwerk (2.5.4)
71
71
 
72
72
  PLATFORMS
73
73
  ruby
@@ -81,4 +81,4 @@ DEPENDENCIES
81
81
  stealth!
82
82
 
83
83
  BUNDLED WITH
84
- 2.1.4
84
+ 2.2.32
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017-2020 Mauricio Gomes, Mav Automation Ventures Inc.
1
+ Copyright (c) 2017-2020 Mauricio Gomes
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
data/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Stealth is a Ruby framework for creating text and voice chatbots. It's design is inspired by Ruby on Rails's philosophy of convention over configuration. It has an MVC architecture with the slight caveat that `views` are aptly named `replies`.
4
4
 
5
- [![CircleCI](https://circleci.com/gh/hellostealth/stealth.svg?style=svg)](https://circleci.com/gh/hellostealth/stealth)
5
+ ![CircleCI](https://img.shields.io/circleci/build/github/hellostealth/stealth?style=for-the-badge)
6
+ ![Gem (including prereleases)](https://img.shields.io/gem/v/stealth?include_prereleases&style=for-the-badge)
6
7
 
7
8
  ## Features
8
9
 
@@ -37,6 +38,7 @@ Currently, there are gems for:
37
38
  * [Alexa Skill](https://github.com/hellostealth/stealth-alexa) (Early alpha)
38
39
 
39
40
  ### Natural Language Processing
41
+ * [Microsoft LUIS](https://github.com/hellostealth/stealth-luis)
40
42
  * [AWS Comprehend](https://github.com/hellostealth/stealth-aws-comprehend)
41
43
 
42
44
  ### Analytics
@@ -52,4 +54,4 @@ Stealth is versioned using [Semantic Versioning](https://semver.org), but it's m
52
54
 
53
55
  ## License
54
56
 
55
- "Stealth" and the Stealth logo are Copyright (c) 2020 Mauricio Gomes and Mav Automation Ventures Inc.
57
+ "Stealth" and the Stealth logo are Copyright (c) 2017-2022 MAV Automated Ventures Inc.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0.beta2
1
+ 2.0.0.beta5
data/lib/stealth/base.rb CHANGED
@@ -88,7 +88,7 @@ module Stealth
88
88
 
89
89
  Thread.current[:configuration] ||= begin
90
90
  @semaphore.synchronize do
91
- services_config = YAML.load(ERB.new(services_yaml).result)
91
+ services_config = YAML.safe_load(ERB.new(services_yaml).result, aliases: true)
92
92
 
93
93
  unless services_config.has_key?(env)
94
94
  raise Stealth::Errors::ConfigurationError, "Could not find services.yml configuration for #{env} environment"
@@ -91,6 +91,11 @@ module Stealth
91
91
  unless flow_controller.progressed?
92
92
  run_catch_all(reason: 'Did not send replies, update session, or step')
93
93
  end
94
+ rescue Stealth::Errors::Halted
95
+ Stealth::Logger.l(
96
+ topic: "session",
97
+ message: "User #{current_session_id}: session halted."
98
+ )
94
99
  rescue StandardError => e
95
100
  if e.class == Stealth::Errors::UnrecognizedMessage
96
101
  run_unrecognized_message(err: e)
@@ -215,6 +220,10 @@ module Stealth
215
220
  @progressed = :do_nothing
216
221
  end
217
222
 
223
+ def halt!
224
+ raise Stealth::Errors::Halted
225
+ end
226
+
218
227
  private
219
228
 
220
229
  def update_session(flow:, state:)
@@ -33,10 +33,32 @@ module Stealth
33
33
  begin
34
34
  send_reply(reply: reply)
35
35
  rescue Stealth::Errors::UserOptOut => e
36
- user_opt_out_handler(msg: e.message)
36
+ msg = "User #{current_session_id} opted out. [#{e.message}]"
37
+ service_error_dispatcher(
38
+ handler_method: :handle_opt_out,
39
+ error_msg: msg
40
+ )
37
41
  return
38
42
  rescue Stealth::Errors::InvalidSessionID => e
39
- invalid_session_id_handler(msg: e.message)
43
+ msg = "User #{current_session_id} has an invalid session_id. [#{e.message}]"
44
+ service_error_dispatcher(
45
+ handler_method: :handle_invalid_session_id,
46
+ error_msg: msg
47
+ )
48
+ return
49
+ rescue Stealth::Errors::MessageFiltered => e
50
+ msg = "Message to user #{current_session_id} was filtered. [#{e.message}]"
51
+ service_error_dispatcher(
52
+ handler_method: :handle_message_filtered,
53
+ error_msg: msg
54
+ )
55
+ return
56
+ rescue Stealth::Errors::UnknownServiceError => e
57
+ msg = "User #{current_session_id} had an unknown error. [#{e.message}]"
58
+ service_error_dispatcher(
59
+ handler_method: :handle_unknown_error,
60
+ error_msg: msg
61
+ )
40
62
  return
41
63
  end
42
64
 
@@ -50,8 +72,12 @@ module Stealth
50
72
 
51
73
  private
52
74
 
75
+ def voice_service?
76
+ current_service.match?(/voice/)
77
+ end
78
+
53
79
  def send_reply(reply:)
54
- if !reply.delay? && Stealth.config.auto_insert_delays
80
+ if !reply.delay? && Stealth.config.auto_insert_delays && !voice_service?
55
81
  # if it's the first reply in the service_reply or the previous reply
56
82
  # wasn't a custom delay, then insert a delay
57
83
  if @previous_reply.blank? || !@previous_reply.delay?
@@ -211,34 +237,17 @@ module Stealth
211
237
  return file_contents, selected_preprocessor
212
238
  end
213
239
 
214
- def user_opt_out_handler(msg:)
215
- if self.respond_to?(:handle_opt_out, true)
216
- self.send(:handle_opt_out)
217
- Stealth::Logger.l(
218
- topic: current_service,
219
- message: "User #{current_session_id} opted out. [#{msg}]"
220
- )
221
- else
222
- Stealth::Logger.l(
223
- topic: :err,
224
- message: "User #{current_session_id} unhandled exception due to opt-out."
225
- )
226
- end
227
-
228
- do_nothing
229
- end
230
-
231
- def invalid_session_id_handler(msg:)
232
- if self.respond_to?(:handle_invalid_session_id, true)
233
- self.send(:handle_invalid_session_id)
240
+ def service_error_dispatcher(handler_method:, error_msg:)
241
+ if self.respond_to?(handler_method, true)
234
242
  Stealth::Logger.l(
235
243
  topic: current_service,
236
- message: "User #{current_session_id} has an invalid session_id. [#{msg}]"
244
+ message: error_msg
237
245
  )
246
+ self.send(handler_method)
238
247
  else
239
248
  Stealth::Logger.l(
240
249
  topic: :err,
241
- message: "User #{current_session_id} unhandled exception due to invalid session_id."
250
+ message: "Unhandled service exception for user #{current_session_id}. No error handler for `#{handler_method}` found."
242
251
  )
243
252
  end
244
253
 
@@ -43,14 +43,25 @@ module Stealth
43
43
  class FlowDefinitionError < Errors
44
44
  end
45
45
 
46
+ # Service errors
46
47
  class InvalidSessionID < Errors
47
48
  end
48
49
 
49
50
  class UserOptOut < Errors
50
51
  end
51
52
 
53
+ class MessageFiltered < Errors
54
+ end
55
+
56
+ class UnknownServiceError < Errors
57
+ end
58
+
59
+ # Homophone errors
52
60
  class ReservedHomophoneUsed < Errors
53
61
  end
54
62
 
63
+ class Halted < Errors
64
+ end
65
+
55
66
  end
56
67
  end
@@ -40,16 +40,28 @@ private
40
40
  end
41
41
 
42
42
  # Automatically called when clients receive an opt-out error from
43
- # the platform. You can write your own steps for handling.
43
+ # the platform. You may write your own steps for handling.
44
44
  def handle_opt_out
45
45
  do_nothing
46
46
  end
47
47
 
48
48
  # Automatically called when clients receive an invalid session_id error from
49
49
  # the platform. For example, attempting to text a landline.
50
- # You can write your own steps for handling.
50
+ # You may write your own steps for handling.
51
51
  def handle_invalid_session_id
52
52
  do_nothing
53
53
  end
54
54
 
55
+ # Automatically called when a transmitted message is filtered/marked as spam.
56
+ # You may write your own steps for handling.
57
+ def handle_message_filtered
58
+ do_nothing
59
+ end
60
+
61
+ # Automatically called when an unknown error is returned by the platform.
62
+ # You may write your own steps for handling.
63
+ def handle_unknown_error
64
+ do_nothing
65
+ end
66
+
55
67
  end
@@ -21,9 +21,9 @@ module Stealth
21
21
  end
22
22
 
23
23
  def getex(key, expiration=Stealth.config.session_ttl)
24
- $redis.multi do
25
- $redis.expire(key, expiration)
26
- $redis.get(key)
24
+ $redis.multi do |pipeline|
25
+ pipeline.expire(key, expiration)
26
+ pipeline.get(key)
27
27
  end.last
28
28
  end
29
29
 
@@ -45,7 +45,7 @@ module Stealth
45
45
  :blue
46
46
  when :smooch
47
47
  :magenta
48
- when :alexa, :voice, :unrecognized_message
48
+ when :alexa, :voice, :twilio_voice, :unrecognized_message
49
49
  :light_cyan
50
50
  when :nlp
51
51
  :cyan
@@ -21,7 +21,7 @@ module Stealth
21
21
  <body>
22
22
  <center>
23
23
  <a href='https://hellostealth.org'>
24
- <img src='http://assets.blackops.nyc/stealth/logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' />
24
+ <img src='https://raw.githubusercontent.com/hellostealth/stealth/master/logo.svg' height='120' alt='Stealth Logo' aria-label='hellostealth.org' />
25
25
  </a>
26
26
  </center>
27
27
  </body>
@@ -44,6 +44,11 @@ module Stealth
44
44
  headers: get_helpers_from_request(request)
45
45
  )
46
46
 
47
+ headers 'Access-Control-Allow-Origin' => '*',
48
+ 'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST']
49
+ # content_type 'audio/mp3'
50
+ content_type 'application/octet-stream'
51
+
47
52
  dispatcher.coordinate
48
53
  end
49
54
 
@@ -6,7 +6,7 @@ module Stealth
6
6
 
7
7
  attr_accessor :sender_id, :target_id, :timestamp, :service, :message,
8
8
  :location, :attachments, :payload, :referral, :nlp_result,
9
- :catch_all_reason
9
+ :catch_all_reason, :confidence
10
10
 
11
11
  def initialize(service:)
12
12
  @service = service
data/logo.svg CHANGED
@@ -1 +1,28 @@
1
- <svg viewBox="0 0 92 95.79" xmlns="http://www.w3.org/2000/svg"><g fill="#000a38"><path d="m5.63 92.27a1.69 1.69 0 0 0 1.79 1.15c.88 0 1.49-.38 1.49-1.08 0-.53-.34-.77-1-.94l-2.17-.4c-1.62-.37-2.84-1.23-2.84-3.14 0-2.21 1.8-3.66 4.25-3.66 2.7 0 4.23 1.51 4.53 3.45h-2.82a1.66 1.66 0 0 0 -1.7-1.1c-.82 0-1.38.37-1.38 1s.34.78.92.9l2.16.52c1.89.46 2.93 1.5 2.93 3.25 0 2.31-1.91 3.63-4.32 3.63-2.65 0-4.45-1.3-4.84-3.52z"/><path d="m14.72 84.34h9.73v2.53h-3.45v8.74h-2.87v-8.74h-3.41z"/><path d="m35.88 84.34v2.53h-5.06v1.76h4.62v2.48h-4.62v2h5.06v2.55h-7.88v-11.32z"/><path d="m46.12 84.34 4.21 11.27h-2.94l-.79-2.19h-4l-.78 2.19h-2.82l4.21-11.27zm-2.65 6.66h2.27l-1.14-3.18z"/><path d="m53.62 84.34h2.9v8.72h4.95v2.55h-7.85z"/><path d="m63 84.34h9.72v2.53h-3.4v8.74h-2.9v-8.74h-3.42z"/><path d="m86.33 84.34v11.27h-2.9v-4.46h-4.27v4.46h-2.9v-11.27h2.9v4.27h4.27v-4.27z"/><path d="m92 46-14.84-14.84-1.48-1.48-13.36-13.36-1.48-1.48-14.84-14.84-14.84 14.84-1.48 1.48-13.36 13.36-1.48 1.48-14.84 14.84 16.32 16.32 14.84-14.84 14.84 14.84 14.84-14.84 14.84 14.84zm-31.16-28.19 13.35 13.35-6.67 6.68-6.68 6.68-13.36-13.36 6.68-6.68zm-14.84-14.81 13.36 13.32-6.68 6.68-6.68 6.68-6.68-6.68-6.68-6.68zm-28.19 28.16 13.35-13.35 6.68 6.67 6.68 6.68-13.36 13.36-6.68-6.68zm-1.49 28.2-13.32-13.36 13.32-13.36 6.68 6.68 6.68 6.68zm29.68 0-13.36-13.36 13.36-13.36 13.36 13.36zm23-20 6.68-6.68 13.32 13.32-13.32 13.36-13.36-13.36z"/></g></svg>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="180px" height="180px" viewBox="0 0 180 180" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>stealth_app_logo</title>
4
+ <defs>
5
+ <rect id="path-1" x="0" y="0" width="180" height="180"></rect>
6
+ <path d="M51.3778558,-2.07572687e-15 L128.622144,2.07572687e-15 C146.487344,-1.20605709e-15 152.965697,1.86013822 159.496947,5.35308895 C166.028197,8.84603967 171.15396,13.9718029 174.646911,20.5030529 C178.139862,27.0343029 180,33.512656 180,51.3778558 L180,128.622144 C180,146.487344 178.139862,152.965697 174.646911,159.496947 C171.15396,166.028197 166.028197,171.15396 159.496947,174.646911 C152.965697,178.139862 146.487344,180 128.622144,180 L51.3778558,180 C33.512656,180 27.0343029,178.139862 20.5030529,174.646911 C13.9718029,171.15396 8.84603967,166.028197 5.35308895,159.496947 C1.86013822,152.965697 8.04038057e-16,146.487344 -1.38381791e-15,128.622144 L1.38381791e-15,51.3778558 C-8.04038057e-16,33.512656 1.86013822,27.0343029 5.35308895,20.5030529 C8.84603967,13.9718029 13.9718029,8.84603967 20.5030529,5.35308895 C27.0343029,1.86013822 33.512656,1.20605709e-15 51.3778558,-2.07572687e-15 Z" id="path-3"></path>
7
+ </defs>
8
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
9
+ <g id="icon@180">
10
+ <g id="icon">
11
+ <mask id="mask-2" fill="white">
12
+ <use xlink:href="#path-1"></use>
13
+ </mask>
14
+ <g id="Mask"></g>
15
+ <g id="Rectangle" mask="url(#mask-2)">
16
+ <mask id="mask-4" fill="white">
17
+ <use xlink:href="#path-3"></use>
18
+ </mask>
19
+ <use id="Mask" fill="#FFFFFF" xlink:href="#path-3"></use>
20
+ </g>
21
+ </g>
22
+ <g id="logo" transform="translate(24.960938, 18.984375)" fill="#000A38">
23
+ <path d="M65.0390625,0 L44.0583093,20.9046079 L41.9615065,22.9953504 L23.077556,41.8106246 L20.9807532,43.9013672 L0,64.8073839 L23.077556,87.8027344 L44.0583093,66.8967176 L65.0390625,87.8027344 L86.0198157,66.8967176 L106.999155,87.8027344 L130.078125,64.8073839 L109.098786,43.9013672 L106.999155,41.8106246 L88.1180324,22.9953504 L86.0198157,20.9046079 L65.0390625,0 Z M55.5970873,32.403692 L46.156526,22.9953504 L65.0390625,4.18007626 L83.921599,22.9953504 L74.4810377,32.403692 L65.0390625,41.8106246 L55.5970873,32.403692 Z M67.1372792,43.9013672 L76.5778405,34.4944345 L86.0198157,25.086093 L104.902352,43.9013672 L95.460377,53.3097087 L86.0198157,62.7166414 L67.1372792,43.9013672 Z M34.617748,53.3097087 L25.1771867,43.9013672 L44.0583093,25.086093 L53.5002845,34.4944345 L62.9408458,43.9013672 L44.0583093,62.7166414 L34.617748,53.3097087 Z M88.1180324,64.8073839 L97.5585938,55.3990424 L106.999155,45.9921097 L125.883105,64.8073839 L106.999155,83.6226581 L88.1180324,64.8073839 Z M46.156526,64.8073839 L65.0390625,45.9921097 L83.921599,64.8073839 L65.0390625,83.6226581 L46.156526,64.8073839 Z M4.19501953,64.8073839 L23.077556,45.9921097 L32.5195313,55.3990424 L41.9615065,64.8073839 L23.077556,83.6226581 L4.19501953,64.8073839 Z" id="glyph"></path>
24
+ <path d="M7.93868906,127.899703 C11.7243984,127.899703 14.7190641,125.865591 14.7190641,122.305894 C14.7190641,119.621995 13.0804734,118.011656 10.1140594,117.305367 L6.72387188,116.514323 C5.81982188,116.316562 5.28304219,115.892789 5.28304219,115.129997 C5.28304219,114.169444 6.15884063,113.604412 7.4584125,113.604412 C8.95574531,113.604412 9.74678906,114.338953 10.1140594,115.299506 L14.5495547,115.299506 C14.0692781,112.304841 11.6678953,109.988213 7.43016094,109.988213 C3.58794844,109.988213 0.762792188,112.220086 0.762792188,115.610273 C0.762792188,118.548436 2.68389844,119.876259 5.22653906,120.441291 L8.56022344,121.14758 C9.66203438,121.401844 10.1988141,121.769114 10.1988141,122.588409 C10.1988141,123.661969 9.23826094,124.255252 7.85393438,124.255252 C6.44135625,124.255252 5.4243,123.661969 5.05702969,122.475403 L0.33901875,122.475403 C0.960553125,125.893842 3.78570938,127.899703 7.93868906,127.899703 Z M28.539987,127.617187 L28.539987,114.169444 L33.9077839,114.169444 L33.9077839,110.270728 L18.6519401,110.270728 L18.6519401,114.169444 L23.9914855,114.169444 L23.9914855,127.617187 L28.539987,127.617187 Z M51.1753975,127.617187 L51.1753975,123.69022 L43.2367084,123.69022 L43.2367084,120.695555 L50.49736,120.695555 L50.49736,116.881594 L43.2367084,116.881594 L43.2367084,114.169444 L51.1753975,114.169444 L51.1753975,110.270728 L38.7729615,110.270728 L38.7729615,127.617187 L51.1753975,127.617187 Z M59.8262845,127.617187 L61.0411017,124.255252 L67.3412001,124.255252 L68.5842689,127.617187 L73.1892736,127.617187 L66.5784079,110.270728 L61.9451517,110.270728 L55.3342861,127.617187 L59.8262845,127.617187 Z M65.9851251,120.526045 L62.4254283,120.526045 L64.2052767,115.638525 L65.9851251,120.526045 Z M90.0048622,127.617187 L90.0048622,123.69022 L82.2356825,123.69022 L82.2356825,110.270728 L77.6871809,110.270728 L77.6871809,127.617187 L90.0048622,127.617187 Z M101.650415,127.617187 L101.650415,114.169444 L107.018212,114.169444 L107.018212,110.270728 L91.7623679,110.270728 L91.7623679,114.169444 L97.1019132,114.169444 L97.1019132,127.617187 L101.650415,127.617187 Z M116.431891,127.617187 L116.431891,120.752058 L123.127511,120.752058 L123.127511,127.617187 L127.676013,127.617187 L127.676013,110.270728 L123.127511,110.270728 L123.127511,116.853342 L116.431891,116.853342 L116.431891,110.270728 L111.883389,110.270728 L111.883389,127.617187 L116.431891,127.617187 Z" id="STEALTH" fill-rule="nonzero"></path>
25
+ </g>
26
+ </g>
27
+ </g>
28
+ </svg>
@@ -6,7 +6,7 @@ describe "Stealth::Configuration" do
6
6
 
7
7
  describe "accessing via method calling" do
8
8
  let(:services_yml) { File.read(File.join(File.dirname(__FILE__), 'support', 'services.yml')) }
9
- let(:parsed_config) { YAML.load(ERB.new(services_yml).result)[Stealth.env] }
9
+ let(:parsed_config) { YAML.safe_load(ERB.new(services_yml).result, aliases: true)[Stealth.env] }
10
10
  let(:config) { Stealth.load_services_config!(services_yml) }
11
11
 
12
12
  it "should return the root node" do
@@ -115,7 +115,13 @@ describe "Stealth::Controller::CatchAll" do
115
115
  end
116
116
 
117
117
  it "should log the error message" do
118
- expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "[Level 1] for user #{facebook_message.sender_id} OpenStruct\noops\n/stealth/lib/stealth/controller/controller.rb\n/stealth/lib/stealth/controller/catch_all.rb")
118
+ err_klass = if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0')
119
+ "RuntimeError"
120
+ else
121
+ "OpenStruct"
122
+ end
123
+
124
+ expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "[Level 1] for user #{facebook_message.sender_id} #{err_klass}\noops\n/stealth/lib/stealth/controller/controller.rb\n/stealth/lib/stealth/controller/catch_all.rb")
119
125
  expect(Stealth::Logger).to receive(:l).with(topic: 'catch_all', message: "CatchAll triggered for user #{facebook_message.sender_id} from within CatchAll; ignoring.")
120
126
  controller.run_catch_all(err: e)
121
127
  end
@@ -42,6 +42,11 @@ describe "Stealth::Controller" do
42
42
  def parts_unknown
43
43
  step_to flow: :parts, state: :unknown
44
44
  end
45
+
46
+ def halted
47
+ halt!
48
+ step_to state: :other_action2
49
+ end
45
50
  end
46
51
 
47
52
  class FlowMap
@@ -60,6 +65,7 @@ describe "Stealth::Controller" do
60
65
  state :other_action4
61
66
  state :broken_action
62
67
  state :part_unknown
68
+ state :halted
63
69
  state :deprecated_action, redirects_to: :other_action
64
70
  state :deprecated_action2, redirects_to: 'mr_robot->my_action'
65
71
  end
@@ -884,6 +890,34 @@ describe "Stealth::Controller" do
884
890
  controller.action(action: :parts_unknown)
885
891
  end
886
892
  end
893
+
894
+ describe "halt!" do
895
+ it "should catch the error and log the sessio halt" do
896
+ ### It's lame we have to include these two
897
+ expect(Stealth::Logger).to receive(:l).with(
898
+ topic: "primary_session",
899
+ message: "User #{facebook_message.sender_id}: setting session to mr_tron->halted"
900
+ )
901
+ expect(Stealth::Logger).to receive(:l).with(
902
+ topic: "previous_session",
903
+ message: "User #{facebook_message.sender_id}: setting to parts->unknown"
904
+ )
905
+ ###
906
+
907
+ expect(Stealth::Logger).to receive(:l).with(
908
+ topic: "session",
909
+ message: "User #{facebook_message.sender_id}: session halted."
910
+ )
911
+
912
+ controller.step_to(flow: :mr_tron, state: :halted)
913
+ end
914
+
915
+ it "should NOT continue with the rest of the controller code" do
916
+ expect_any_instance_of(MrTronsController).to_not receive(:other_action2)
917
+ expect_any_instance_of(MrTronsController).to_not receive(:step_to).with(state: :other_action2)
918
+ controller.step_to(flow: :mr_tron, state: :halted)
919
+ end
920
+ end
887
921
  end
888
922
 
889
923
  end
@@ -632,7 +632,7 @@ describe "Stealth::Controller replies" do
632
632
  it "should log the unhandled exception if the controller does not have a handle_opt_out method" do
633
633
  expect(Stealth::Logger).to receive(:l).with(
634
634
  topic: :err,
635
- message: "User #{facebook_message.sender_id} unhandled exception due to opt-out."
635
+ message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_opt_out` found."
636
636
  )
637
637
  expect(controller).to receive(:do_nothing)
638
638
  controller.say_offer
@@ -659,7 +659,7 @@ describe "Stealth::Controller replies" do
659
659
  it "should log the unhandled exception if the controller does not have a handle_invalid_session_id method" do
660
660
  expect(Stealth::Logger).to receive(:l).with(
661
661
  topic: :err,
662
- message: "User #{facebook_message.sender_id} unhandled exception due to invalid session_id."
662
+ message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_invalid_session_id` found."
663
663
  )
664
664
  expect(controller).to receive(:do_nothing)
665
665
  controller.say_offer
@@ -676,6 +676,60 @@ describe "Stealth::Controller replies" do
676
676
  end
677
677
  end
678
678
 
679
+ describe "Stealth::Errors::MessageFiltered" do
680
+ before(:each) do
681
+ expect(stubbed_client).to receive(:transmit).and_raise(
682
+ Stealth::Errors::MessageFiltered.new('boom')
683
+ ).once # Retuns early; doesn't send the remaining replies in the file
684
+ end
685
+
686
+ it "should log the unhandled exception if the controller does not have a handle_message_filtered method" do
687
+ expect(Stealth::Logger).to receive(:l).with(
688
+ topic: :err,
689
+ message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_message_filtered` found."
690
+ )
691
+ expect(controller).to receive(:do_nothing)
692
+ controller.say_offer
693
+ end
694
+
695
+ it "should call handle_message_filtered method" do
696
+ expect(controller).to receive(:handle_message_filtered)
697
+ expect(Stealth::Logger).to receive(:l).with(
698
+ topic: 'facebook',
699
+ message: "Message to user #{facebook_message.sender_id} was filtered. [boom]"
700
+ )
701
+ expect(controller).to receive(:do_nothing)
702
+ controller.say_offer
703
+ end
704
+ end
705
+
706
+ describe "Stealth::Errors::UnknownServiceError" do
707
+ before(:each) do
708
+ expect(stubbed_client).to receive(:transmit).and_raise(
709
+ Stealth::Errors::UnknownServiceError.new('boom')
710
+ ).once # Retuns early; doesn't send the remaining replies in the file
711
+ end
712
+
713
+ it "should log the unhandled exception if the controller does not have a handle_unknown_error method" do
714
+ expect(Stealth::Logger).to receive(:l).with(
715
+ topic: :err,
716
+ message: "Unhandled service exception for user #{facebook_message.sender_id}. No error handler for `handle_unknown_error` found."
717
+ )
718
+ expect(controller).to receive(:do_nothing)
719
+ controller.say_offer
720
+ end
721
+
722
+ it "should call handle_unknown_error method" do
723
+ expect(controller).to receive(:handle_unknown_error)
724
+ expect(Stealth::Logger).to receive(:l).with(
725
+ topic: 'facebook',
726
+ message: "User #{facebook_message.sender_id} had an unknown error. [boom]"
727
+ )
728
+ expect(controller).to receive(:do_nothing)
729
+ controller.say_offer
730
+ end
731
+ end
732
+
679
733
  describe 'an unknown client error' do
680
734
  before(:each) do
681
735
  allow(stubbed_client).to receive(:transmit).and_raise(
data/stealth.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
13
13
  s.email = 'mauricio@edge14.com'
14
14
 
15
15
  s.add_dependency 'sinatra', '~> 2.0'
16
- s.add_dependency 'puma', '>= 4.2', '< 5.0'
16
+ s.add_dependency 'puma', '>= 4.2', '< 6.0'
17
17
  s.add_dependency 'thor', '~> 1.0'
18
18
  s.add_dependency 'multi_json', '~> 1.12'
19
19
  s.add_dependency 'sidekiq', '~> 6.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stealth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta2
4
+ version: 2.0.0.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-12-09 00:00:00.000000000 Z
12
+ date: 2022-03-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -34,7 +34,7 @@ dependencies:
34
34
  version: '4.2'
35
35
  - - "<"
36
36
  - !ruby/object:Gem::Version
37
- version: '5.0'
37
+ version: '6.0'
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: '4.2'
45
45
  - - "<"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: '6.0'
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: thor
50
50
  requirement: !ruby/object:Gem::Requirement
@@ -327,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
327
327
  - !ruby/object:Gem::Version
328
328
  version: 1.3.1
329
329
  requirements: []
330
- rubygems_version: 3.1.2
330
+ rubygems_version: 3.3.7
331
331
  signing_key:
332
332
  specification_version: 4
333
333
  summary: Ruby framework for conversational bots