stealth 0.9.8 → 0.10.0

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.
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ class NewTodoFlow
6
+ include Stealth::Flow
7
+
8
+ flow do
9
+ state :new
10
+
11
+ state :get_due_date
12
+
13
+ state :created, fails_to: :new
14
+
15
+ state :error
16
+ end
17
+ end
18
+
19
+ describe "Stealth::Session" do
20
+ let(:user_id) { '0xDEADBEEF' }
21
+
22
+ it "should raise an error if $redis is not set" do
23
+ $redis = nil
24
+
25
+ expect {
26
+ Stealth::Session.new(user_id: user_id)
27
+ }.to raise_error(Stealth::Errors::RedisNotConfigured)
28
+
29
+ $redis = MockRedis.new
30
+ end
31
+
32
+ describe "without a session" do
33
+ let(:session) { Stealth::Session.new(user_id: user_id) }
34
+
35
+ it "should have nil flow and state" do
36
+ expect(session.flow).to be_nil
37
+ expect(session.state).to be_nil
38
+ end
39
+
40
+ it "should have nil flow_string and state_string" do
41
+ expect(session.flow_string).to be_nil
42
+ expect(session.state_string).to be_nil
43
+ end
44
+
45
+ it "should respond to present? and blank?" do
46
+ expect(session.present?).to be false
47
+ expect(session.blank?).to be true
48
+ end
49
+ end
50
+
51
+ describe "with a session" do
52
+ class MarcoFlow
53
+ include Stealth::Flow
54
+
55
+ flow do
56
+ state :polo
57
+ end
58
+ end
59
+
60
+ let(:session) do
61
+ session = Stealth::Session.new(user_id: user_id)
62
+ session.set(flow: 'Marco', state: 'polo')
63
+ session
64
+ end
65
+
66
+ it "should return the flow" do
67
+ expect(session.flow).to be_a(MarcoFlow)
68
+ end
69
+
70
+ it "should return the state" do
71
+ expect(session.state).to be_a(Stealth::Flow::State)
72
+ expect(session.state).to eq :polo
73
+ end
74
+
75
+ it "should return the flow_string" do
76
+ expect(session.flow_string).to eq "Marco"
77
+ end
78
+
79
+ it "should return the state_string" do
80
+ expect(session.state_string).to eq "polo"
81
+ end
82
+
83
+ it "should respond to present? and blank?" do
84
+ expect(session.present?).to be true
85
+ expect(session.blank?).to be false
86
+ end
87
+ end
88
+
89
+ describe "incrementing and decrementing" do
90
+ let(:session) { Stealth::Session.new(user_id: user_id) }
91
+
92
+ it "should increment the state" do
93
+ session.set(flow: 'NewTodo', state: 'get_due_date')
94
+ new_session = session + 1.state
95
+ expect(new_session.state_string).to eq('created')
96
+ end
97
+
98
+ it "should decrement the state" do
99
+ session.set(flow: 'NewTodo', state: 'error')
100
+ new_session = session - 2.states
101
+ expect(new_session.state_string).to eq('get_due_date')
102
+ end
103
+
104
+ it "should return the first state if the decrement is out of bounds" do
105
+ session.set(flow: 'NewTodo', state: 'get_due_date')
106
+ new_session = session - 5.states
107
+ expect(new_session.state_string).to eq('new')
108
+ end
109
+
110
+ it "should return the last state if the increment is out of bounds" do
111
+ session.set(flow: 'NewTodo', state: 'created')
112
+ new_session = session + 5.states
113
+ expect(new_session.state_string).to eq('error')
114
+ end
115
+ end
116
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,11 +3,14 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
3
  require 'rspec'
4
4
 
5
5
  require 'stealth'
6
+ require 'mock_redis'
6
7
 
7
8
  # Requires supporting files with custom matchers and macros, etc,
8
9
  # in ./support/ and its subdirectories.
9
10
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
11
 
12
+ $redis = MockRedis.new
13
+
11
14
  RSpec.configure do |config|
12
15
 
13
16
  end
@@ -0,0 +1,65 @@
1
+ class SampleMessage
2
+
3
+ def initialize(service:)
4
+ @service = service
5
+ @base_message = Stealth::ServiceMessage.new(service: @service)
6
+ @base_message.sender_id = sender_id
7
+ @base_message.timestamp = timestamp
8
+ @base_message
9
+ end
10
+
11
+ def message_with_text
12
+ @base_message.message = message
13
+ @base_message
14
+ end
15
+
16
+ def message_with_payload
17
+ @base_message.payload = payload
18
+ @base_message
19
+ end
20
+
21
+ def message_with_location
22
+ @base_message.location = location
23
+ @base_message
24
+ end
25
+
26
+ def message_with_attachments
27
+ @base_message.attachments = attachments
28
+ @base_message
29
+ end
30
+
31
+ private
32
+
33
+ def sender_id
34
+ if @service == 'twilio'
35
+ '+15554561212'
36
+ else
37
+ "8b3e0a3c-62f1-401e-8b0f-615c9d256b1f"
38
+ end
39
+ end
40
+
41
+ def timestamp
42
+ Time.now
43
+ end
44
+
45
+ def message
46
+ "Hello World!"
47
+ end
48
+
49
+ def payload
50
+ "some_payload"
51
+ end
52
+
53
+ def location
54
+ { lat: '42.323724' , lng: '-83.047543' }
55
+ end
56
+
57
+ def attachments
58
+ [ { type: 'image', url: 'https://domain.none/image.jpg' } ]
59
+ end
60
+
61
+ def referral
62
+ {}
63
+ end
64
+
65
+ end
File without changes
data/stealth.gemspec CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency 'rspec', '~> 3.6'
23
23
  s.add_development_dependency 'rspec_junit_formatter', '~> 0.3'
24
24
  s.add_development_dependency 'rack-test', '~> 0.7'
25
+ s.add_development_dependency 'mock_redis', '~> 0.17'
25
26
 
26
27
  s.files = `git ls-files`.split("\n")
27
28
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stealth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.8
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauricio Gomes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-07 00:00:00.000000000 Z
11
+ date: 2018-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.7'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mock_redis
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.17'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.17'
139
153
  description: Ruby framework for building conversational bots.
140
154
  email: mauricio@edge14.com
141
155
  executables:
@@ -159,13 +173,13 @@ files:
159
173
  - lib/stealth/commands/console.rb
160
174
  - lib/stealth/commands/server.rb
161
175
  - lib/stealth/configuration.rb
162
- - lib/stealth/controller.rb
176
+ - lib/stealth/controller/callbacks.rb
177
+ - lib/stealth/controller/catch_all.rb
178
+ - lib/stealth/controller/controller.rb
163
179
  - lib/stealth/dispatcher.rb
164
180
  - lib/stealth/errors.rb
165
181
  - lib/stealth/flow/base.rb
166
- - lib/stealth/flow/errors.rb
167
- - lib/stealth/flow/event.rb
168
- - lib/stealth/flow/event_collection.rb
182
+ - lib/stealth/flow/core_ext.rb
169
183
  - lib/stealth/flow/specification.rb
170
184
  - lib/stealth/flow/state.rb
171
185
  - lib/stealth/jobs.rb
@@ -182,14 +196,17 @@ files:
182
196
  - lib/stealth/session.rb
183
197
  - lib/stealth/version.rb
184
198
  - spec/configuration_spec.rb
185
- - spec/flow/custom_transitions_spec.rb
199
+ - spec/controller/callbacks_spec.rb
200
+ - spec/controller/state_transitions_spec.rb
186
201
  - spec/flow/flow_spec.rb
187
- - spec/flow/transition_callbacks_spec.rb
202
+ - spec/flow/state_spec.rb
188
203
  - spec/replies/nested_reply_with_erb.yml
189
- - spec/sample_services_yml/services.yml
190
- - spec/sample_services_yml/services_with_erb.yml
191
204
  - spec/service_reply_spec.rb
205
+ - spec/session_spec.rb
192
206
  - spec/spec_helper.rb
207
+ - spec/support/sample_messages.rb
208
+ - spec/support/services.yml
209
+ - spec/support/services_with_erb.yml
193
210
  - spec/version_spec.rb
194
211
  - stealth.gemspec
195
212
  homepage: https://github.com/whoisblackops/stealth
@@ -212,18 +229,21 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
229
  version: '0'
213
230
  requirements: []
214
231
  rubyforge_project:
215
- rubygems_version: 2.6.11
232
+ rubygems_version: 2.6.12
216
233
  signing_key:
217
234
  specification_version: 4
218
235
  summary: Ruby framework for conversational bots
219
236
  test_files:
220
237
  - spec/configuration_spec.rb
221
- - spec/flow/custom_transitions_spec.rb
238
+ - spec/controller/callbacks_spec.rb
239
+ - spec/controller/state_transitions_spec.rb
222
240
  - spec/flow/flow_spec.rb
223
- - spec/flow/transition_callbacks_spec.rb
241
+ - spec/flow/state_spec.rb
224
242
  - spec/replies/nested_reply_with_erb.yml
225
- - spec/sample_services_yml/services.yml
226
- - spec/sample_services_yml/services_with_erb.yml
227
243
  - spec/service_reply_spec.rb
244
+ - spec/session_spec.rb
228
245
  - spec/spec_helper.rb
246
+ - spec/support/sample_messages.rb
247
+ - spec/support/services.yml
248
+ - spec/support/services_with_erb.yml
229
249
  - spec/version_spec.rb
@@ -1,25 +0,0 @@
1
- # coding: utf-8
2
- # frozen_string_literal: true
3
-
4
- module Stealth
5
- module Flow
6
- class Error < StandardError; end
7
-
8
- class TransitionHalted < Error
9
-
10
- attr_reader :halted_because
11
-
12
- def initialize(msg = nil)
13
- @halted_because = msg
14
- super msg
15
- end
16
-
17
- end
18
-
19
- class NoTransitionAllowed < Error; end
20
-
21
- class StealthFlowError < Error; end
22
-
23
- class StealthFlowDefinitionError < Error; end
24
- end
25
- end
@@ -1,43 +0,0 @@
1
- # coding: utf-8
2
- # frozen_string_literal: true
3
-
4
- module Stealth
5
- module Flow
6
- class Event
7
-
8
- attr_accessor :name, :transitions_to, :meta, :action, :condition
9
-
10
- def initialize(name, transitions_to, condition = nil, meta = {}, &action)
11
- @name = name
12
- @transitions_to = transitions_to.to_sym
13
- @meta = meta
14
- @action = action
15
- @condition = if condition.nil? || condition.is_a?(Symbol) || condition.respond_to?(:call)
16
- condition
17
- else
18
- raise TypeError, 'condition must be nil, an instance method name symbol or a callable (eg. a proc or lambda)'
19
- end
20
- end
21
-
22
- def condition_applicable?(object)
23
- if condition
24
- if condition.is_a?(Symbol)
25
- object.send(condition)
26
- else
27
- condition.call(object)
28
- end
29
- else
30
- true
31
- end
32
- end
33
-
34
- def draw(graph, from_state)
35
- graph.add_edges(from_state.name.to_s, transitions_to.to_s, meta.merge(:label => to_s))
36
- end
37
-
38
- def to_s
39
- @name.to_s
40
- end
41
- end
42
- end
43
- end
@@ -1,41 +0,0 @@
1
- # coding: utf-8
2
- # frozen_string_literal: true
3
-
4
- module Stealth
5
- module Flow
6
- class EventCollection < Hash
7
-
8
- def [](name)
9
- super name.to_sym # Normalize to symbol
10
- end
11
-
12
- def push(name, event)
13
- key = name.to_sym
14
- self[key] ||= []
15
- self[key] << event
16
- end
17
-
18
- def flat
19
- self.values.flatten.uniq do |event|
20
- [:name, :transitions_to, :meta, :action].map { |m| event.send(m) }
21
- end
22
- end
23
-
24
- def include?(name_or_obj)
25
- case name_or_obj
26
- when Event
27
- flat.include? name_or_obj
28
- else
29
- !(self[name_or_obj].nil?)
30
- end
31
- end
32
-
33
- def first_applicable(name, object_context)
34
- (self[name] || []).detect do |event|
35
- event.condition_applicable?(object_context) && event
36
- end
37
- end
38
-
39
- end
40
- end
41
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
4
-
5
- describe "custom transitions" do
6
-
7
- class FetchTodosFlow
8
- include Stealth::Flow
9
-
10
- flow do
11
- state :todays_todos do
12
- event :fetch_tomorrows_todos, :transitions_to => :tomorrows_todos
13
- event :fetch_yesterdays_todos, :transitions_to => :yesterdays_todos
14
- end
15
-
16
- state :tomorrows_todos do
17
- event :view_todo, :transitions_to => :show
18
- event :edit_todo, :transitions_to => :edit
19
- end
20
-
21
- state :tomorrows_todos do
22
- event :view_todo, :transitions_to => :show
23
- event :edit_todo, :transitions_to => :edit
24
- end
25
-
26
- state :show
27
-
28
- state :edit do
29
- event :save_todo, :transitions_to => :show
30
- event :error_in_input, :transitions_to => :error
31
- end
32
-
33
- state :error
34
- end
35
-
36
- def view_todo(todo_id)
37
- unless todo_id > 0
38
- halt('ID is not valid.')
39
- end
40
-
41
- :the_todo
42
- end
43
-
44
- def edit_todo(todo_id)
45
- unless todo_id > 0
46
- halt('ID is not valid.')
47
- end
48
-
49
- :edit_todo_view
50
- end
51
-
52
- def save_todo(params)
53
- if params.nil?
54
- halt!('Invalid todo params specified.')
55
- end
56
-
57
- :todo_saved
58
- end
59
- end
60
-
61
- let(:flow) { FetchTodosFlow.new }
62
-
63
- it "should transition via custom transition methods" do
64
- flow.fetch_tomorrows_todos!
65
- expect(flow.view_todo!(1)).to eq :the_todo
66
- expect(flow.current_state).to eq :show
67
- end
68
-
69
- it "should follow multiple custom transitions" do
70
- flow.fetch_tomorrows_todos!
71
- expect(flow.edit_todo!(1)).to eq :edit_todo_view
72
- expect(flow.current_state).to eq :edit
73
-
74
- expect(flow.save_todo!({ task: 'test' })).to eq :todo_saved
75
- expect(flow.current_state).to eq :show
76
- end
77
-
78
- describe "halting transitions" do
79
- it "should halt the transition when halt() is called" do
80
- flow.fetch_tomorrows_todos!
81
- flow.view_todo!(-1)
82
- expect(flow.current_state).to eq :tomorrows_todos
83
- expect(flow.halted_because).to eq "ID is not valid."
84
- end
85
-
86
- it "should halt the transition when halt!() is called and raise Stealth::Flow::TransitionHalted" do
87
- flow.fetch_tomorrows_todos!
88
- flow.edit_todo!(1)
89
- expect(flow.current_state).to eq :edit
90
-
91
- expect {
92
- flow.save_todo!(nil)
93
- }.to raise_error(Stealth::Flow::TransitionHalted)
94
-
95
- expect(flow.halted_because).to eq "Invalid todo params specified."
96
- end
97
- end
98
-
99
- end