stealth 0.9.8 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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