larynx 0.1.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,290 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Larynx::CallHandler do
4
+ attr_reader :call
5
+
6
+ before do
7
+ @call = TestCallHandler.new(1)
8
+ end
9
+
10
+ context "execute" do
11
+ before do
12
+ call.queue = []
13
+ end
14
+
15
+ it "should queue commands" do
16
+ call.execute Larynx::Command.new('dummy1')
17
+ call.execute Larynx::Command.new('dummy2')
18
+ call.queue[0].command.should == 'dummy1'
19
+ call.queue[1].command.should == 'dummy2'
20
+ end
21
+
22
+ it "should push command on front of queue when immediate is true" do
23
+ call.execute Larynx::Command.new('dummy1')
24
+ call.execute Larynx::Command.new('dummy2'), true
25
+ call.queue[0].command.should == 'dummy2'
26
+ call.queue[1].command.should == 'dummy1'
27
+ end
28
+ end
29
+
30
+ it "should return first command in queue for current_command" do
31
+ call.queue = []
32
+ call.execute Larynx::Command.new('dummy')
33
+ call.current_command.command.should == 'dummy'
34
+ end
35
+
36
+ it "should send current command message on send_next_command" do
37
+ call.queue = []
38
+ call.execute Larynx::Command.new('dummy')
39
+ call.send_next_command
40
+ call.sent_data.should == 'dummy'
41
+ end
42
+
43
+ context "reply received" do
44
+ before do
45
+ call.queue = []
46
+ end
47
+
48
+ it "should finalise current command if it is an API command" do
49
+ call.should_receive :finalize_command
50
+ call.connect
51
+ call.send_response :reply_ok
52
+ end
53
+
54
+ it "should not finalise current command if it is an App command" do
55
+ call.should_not_receive :finalize_command
56
+ call.speak 'hello'
57
+ call.send_response :reply_ok
58
+ end
59
+ end
60
+
61
+ context "executing event received" do
62
+ before do
63
+ call.queue = []
64
+ end
65
+
66
+ it "should run app command before callback" do
67
+ call.speak('hello world').before { @callback = true }
68
+ call.send_response :execute
69
+ @callback.should be_true
70
+ end
71
+
72
+ it "should change state to executing" do
73
+ call.speak 'hello'
74
+ call.send_response :execute
75
+ call.state.should == :executing
76
+ end
77
+
78
+ it "should not finalize command" do
79
+ call.should_not_receive :finalize_command
80
+ call.speak 'hello'
81
+ call.send_response :execute
82
+ end
83
+ end
84
+
85
+ context "execution complete event received" do
86
+ before do
87
+ call.queue = []
88
+ end
89
+
90
+ it "should finalize command" do
91
+ call.should_receive :finalize_command
92
+ call.speak 'hello'
93
+ call.send_response :execute_complete
94
+ end
95
+
96
+ it "should change state to ready" do
97
+ call.speak 'hello'
98
+ call.send_response :execute_complete
99
+ call.state.should == :ready
100
+ end
101
+ end
102
+
103
+ context "finalizing command" do
104
+ before do
105
+ call.queue = []
106
+ call.speak('hello world').after { @callback = true }
107
+ @command = call.current_command
108
+ call.finalize_command
109
+ end
110
+
111
+ it "should run after callback" do
112
+ @callback.should be_true
113
+ end
114
+
115
+ it "should remove command from queue" do
116
+ call.queue.should be_empty
117
+ end
118
+
119
+ it "should set command as last command" do
120
+ call.last_command.should == @command
121
+ end
122
+ end
123
+
124
+ it "should queue connect command on init" do
125
+ call.current_command.name.should == 'connect'
126
+ end
127
+
128
+ context "on connection" do
129
+ it "should create session object" do
130
+ connect_call
131
+ call.session.should_not be_nil
132
+ end
133
+
134
+ it "should fire global connect callback" do
135
+ Larynx.connect {|call| @callback = true }
136
+ connect_call
137
+ @callback.should be_true
138
+ end
139
+
140
+ it "should start the call session" do
141
+ call.should_receive :start_session
142
+ connect_call
143
+ end
144
+
145
+ def connect_call
146
+ call.send_response :channel_data
147
+ end
148
+
149
+ after do
150
+ Larynx.connect {|call|}
151
+ end
152
+ end
153
+
154
+ context "on session start" do
155
+ before do
156
+ call.queue = []
157
+ call.session = mock('session', :unique_id => '123')
158
+ end
159
+
160
+ it "should subscribe to myevents" do
161
+ call.should_receive(:myevents)
162
+ call.start_session
163
+ end
164
+
165
+ it "should set event lingering on after filter events" do
166
+ call.should_receive(:linger)
167
+ call.start_session
168
+ call.send_response :reply_ok
169
+ end
170
+
171
+ it "should answer call after filter events" do
172
+ call.should_receive(:answer)
173
+ call.start_session
174
+ call.send_response :reply_ok
175
+ end
176
+ end
177
+
178
+ context "on answer" do
179
+ before do
180
+ call.queue = []
181
+ call.session = mock('session', :unique_id => '123')
182
+ end
183
+
184
+ it "should change state to ready" do
185
+ answer_call
186
+ call.state.should == :ready
187
+ end
188
+
189
+ it "should fire global answer callback" do
190
+ Larynx.answer {|call| @callback = true }
191
+ answer_call
192
+ @callback.should be_true
193
+ end
194
+
195
+ def answer_call
196
+ call.start_session
197
+ call.send_response :reply_ok
198
+ call.send_response :reply_ok
199
+ call.send_response :reply_ok
200
+ call.send_response :execute_complete
201
+ end
202
+ end
203
+
204
+ context "DTMF event" do
205
+ it "should add DTMF digit to input" do
206
+ run_command
207
+ call.send_response :dtmf
208
+ call.input.should == ['1']
209
+ end
210
+
211
+ it "should send break if interruptable command" do
212
+ run_command true
213
+ call.send_response :dtmf
214
+ call.sent_data.should match(/break/)
215
+ end
216
+
217
+ it "should not send break if non-interruptable command" do
218
+ run_command false
219
+ call.send_response :dtmf
220
+ call.sent_data.should_not match(/break/)
221
+ end
222
+
223
+ it "should send next command if state is ready" do
224
+ call.state = :ready
225
+ call.should_receive(:send_next_command)
226
+ call.send_response :dtmf
227
+ end
228
+
229
+ it "should notify observers and pass digit" do
230
+ app = mock('App')
231
+ call.add_observer app
232
+ app.should_receive(:dtmf_received).with('1')
233
+ call.send_response :dtmf
234
+ end
235
+
236
+ def run_command(interruptable=true)
237
+ call.queue = []
238
+ call.speak 'hello', :bargein => interruptable
239
+ call.send_next_command
240
+ call.send_response :execute
241
+ end
242
+ end
243
+
244
+ context "timer" do
245
+ it "should add EM timer with name and timeout" do
246
+ Larynx::RestartableTimer.stub!(:new)
247
+ call.timer(:test, 0.1)
248
+ call.timers[:test].should_not be_nil
249
+ end
250
+
251
+ it "should run callback on timeout" do
252
+ em do
253
+ call.timer(:test, 0.2) { @callback = true; done }
254
+ end
255
+ @callback.should be_true
256
+ end
257
+ end
258
+
259
+ context "stop_timer" do
260
+ it "should run callback on timeout" do
261
+ em do
262
+ call.timer(:test, 1) { @callback = true }
263
+ EM::Timer.new(0.1) { call.stop_timer :test; done }
264
+ end
265
+ @callback.should be_true
266
+ end
267
+ end
268
+
269
+ context "cancel_timer" do
270
+ it "should not run callback" do
271
+ em do
272
+ call.timer(:test, 0.2) { @callback = true }
273
+ EM::Timer.new(0.1) { call.cancel_timer :test; done }
274
+ end
275
+ @callback.should_not be_true
276
+ end
277
+ end
278
+
279
+ context "restart_timer" do
280
+ it "should start timer again" do
281
+ start = Time.now
282
+ em do
283
+ EM::Timer.new(0.5) { call.restart_timer :test }
284
+ call.timer(:test, 1) { done }
285
+ end
286
+ (Time.now-start).should be_close(1.5, 0.2)
287
+ end
288
+ end
289
+
290
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Larynx::Command do
4
+
5
+ it "should allow before callback" do
6
+ cmd = Larynx::Command.new('dummy').before { @callback = true }
7
+ @callback.should_not be_true
8
+ cmd.fire_callback :before
9
+ @callback.should be_true
10
+ end
11
+
12
+ it "should allow after callback" do
13
+ cmd = Larynx::Command.new('dummy').after { @callback = true }
14
+ @callback.should_not be_true
15
+ cmd.fire_callback :after
16
+ @callback.should be_true
17
+ end
18
+
19
+ it "should add block given to new as after block" do
20
+ cmd = Larynx::Command.new('dummy') { @callback = true }
21
+ @callback.should_not be_true
22
+ cmd.fire_callback :after
23
+ @callback.should be_true
24
+ end
25
+
26
+ context 'call command' do
27
+ before do
28
+ @cmd = Larynx::CallCommand.new('dummy', 'arg')
29
+ end
30
+
31
+ it "should return name as command and params" do
32
+ @cmd.name.should == 'dummy arg'
33
+ end
34
+
35
+ it "should return to_s as full command message" do
36
+ @cmd.to_s.should == "dummy arg\n\n"
37
+ end
38
+ end
39
+
40
+ context 'api command' do
41
+ before do
42
+ @cmd = Larynx::ApiCommand.new('dummy', 'arg')
43
+ end
44
+
45
+ it "should return name as command and params" do
46
+ @cmd.name.should == 'dummy arg'
47
+ end
48
+
49
+ it "should return to_s as full command message" do
50
+ @cmd.to_s.should == "api dummy arg\n\n"
51
+ end
52
+ end
53
+
54
+ context 'app command' do
55
+ before do
56
+ @cmd = Larynx::AppCommand.new('dummy', 'arg', :bargein => true)
57
+ end
58
+
59
+ it "should return name as command and params" do
60
+ @cmd.name.should == "dummy 'arg'"
61
+ end
62
+
63
+ it "should return to_s as full command message" do
64
+ @cmd.to_s.should == "sendmsg\ncall-command: execute\nexecute-app-name: dummy\nexecute-app-arg: arg\n\n"
65
+ end
66
+
67
+ it "should return to_s as with arg if no param" do
68
+ cmd = Larynx::AppCommand.new('dummy')
69
+ cmd.to_s.should == "sendmsg\ncall-command: execute\nexecute-app-name: dummy\n\n"
70
+ end
71
+
72
+ it "should return true for interruptable if bargein option is true" do
73
+ @cmd.interruptable?.should be_true
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Larynx::RestartableTimer do
4
+
5
+ it "should allow timer to be restarted" do
6
+ start = Time.now
7
+ em do
8
+ timer = Larynx::RestartableTimer.new(1) { done }
9
+ EM::Timer.new(0.5) { timer.restart }
10
+ end
11
+ (Time.now-start).should be_close(1.5, 0.2)
12
+ end
13
+
14
+ end
@@ -0,0 +1,194 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Larynx::Fields do
4
+ attr_reader :call, :app
5
+
6
+ before do
7
+ @call = TestCallHandler.new(1)
8
+ @app = Larynx::Application.new(@call)
9
+ end
10
+
11
+ context 'module' do
12
+ include Larynx::Fields
13
+
14
+ it 'should add field class method' do
15
+ self.class.should respond_to(:field)
16
+ end
17
+
18
+ it 'should add instance accessor for field name' do
19
+ self.class.field(:guess) { prompt :speak => 'hello' }
20
+ self.methods.include?(:guess)
21
+ end
22
+ end
23
+
24
+ context 'next_field' do
25
+ include Larynx::Fields
26
+ field(:field1) { prompt :speak => 'hello' }
27
+ field(:field2) { prompt :speak => 'hello' }
28
+ field(:field3) { prompt :speak => 'hello' }
29
+
30
+ it 'should iterate over defined fields' do
31
+ next_field.name.should == :field1
32
+ next_field.name.should == :field2
33
+ next_field.name.should == :field3
34
+ end
35
+
36
+ it 'should jump to field name if supplied' do
37
+ next_field(:field2).name.should == :field2
38
+ end
39
+ end
40
+
41
+ context 'field object' do
42
+ it 'should raise exception if field has no prompt' do
43
+ lambda { field(:guess) {} }.should raise_exception(Larynx::NoPromptDefined)
44
+ end
45
+
46
+ it 'should run setup callback once' do
47
+ call_me = should_be_called
48
+ fld = field(:guess) do
49
+ prompt :speak => 'first'
50
+ setup &call_me
51
+ end
52
+ fld.run app
53
+ end
54
+
55
+ it 'should return same prompt all attempts if single prompt' do
56
+ fld = field(:guess) do
57
+ prompt :speak => 'first'
58
+ end
59
+ fld.run(app)
60
+ fld.current_prompt.message.should == 'first'
61
+ fld.increment_attempts
62
+ fld.current_prompt.message.should == 'first'
63
+ end
64
+
65
+ it 'should return reprompt for subsequent prompts' do
66
+ fld = field(:guess) do
67
+ prompt :speak => 'first'
68
+ reprompt :speak => 'second'
69
+ end
70
+ fld.run(app)
71
+ fld.current_prompt.message.should == 'first'
72
+ fld.increment_attempts
73
+ fld.current_prompt.message.should == 'second'
74
+ end
75
+
76
+ it 'should return prompt for given number of repeats before subsequent prompts' do
77
+ fld = field(:guess) do
78
+ prompt :speak => 'first', :repeats => 2
79
+ reprompt :speak => 'second'
80
+ end
81
+ fld.run(app)
82
+ fld.current_prompt.message.should == 'first'
83
+ fld.increment_attempts
84
+ fld.current_prompt.message.should == 'first'
85
+ fld.increment_attempts
86
+ fld.current_prompt.message.should == 'second'
87
+ end
88
+
89
+ context 'valid?' do
90
+ it 'should be false if input size less than minimum' do
91
+ fld = field(:guess) do
92
+ prompt :speak => 'first'
93
+ end
94
+ fld.run app
95
+ fld.current_prompt.finalise
96
+ fld.valid?.should be_false
97
+ end
98
+
99
+ it 'should run validate callback if input minimum length' do
100
+ call_me = should_be_called
101
+ fld = field(:guess, :min_length => 1) do
102
+ prompt :speak => 'first'
103
+ validate &call_me
104
+ end
105
+ fld.run app
106
+ call.input << '1'
107
+ fld.current_prompt.finalise
108
+ end
109
+ end
110
+
111
+ context 'input evaluation' do
112
+ it 'should run invalid callback if length not valid' do
113
+ call_me = should_be_called
114
+ fld = field(:guess) do
115
+ prompt :speak => 'first'
116
+ invalid &call_me
117
+ end
118
+ fld.run app
119
+ fld.current_prompt.finalise
120
+ end
121
+
122
+ it 'should run invalid callback if validate callback returns false' do
123
+ call_me = should_be_called
124
+ fld = field(:guess, :min_length => 1) do
125
+ prompt :speak => 'first'
126
+ validate { false }
127
+ invalid &call_me
128
+ end
129
+ fld.run app
130
+ call.input << '1'
131
+ fld.current_prompt.finalise
132
+ end
133
+
134
+ it 'should run success callback if length valid and no validate callback' do
135
+ call_me = should_be_called
136
+ fld = field(:guess, :min_length => 1) do
137
+ prompt :speak => 'first'
138
+ success &call_me
139
+ end
140
+ fld.run app
141
+ call.input << '1'
142
+ fld.current_prompt.finalise
143
+ end
144
+
145
+ it 'should run success callback if validate callback returns true' do
146
+ call_me = should_be_called
147
+ fld = field(:guess, :min_length => 1) do
148
+ prompt :speak => 'first'
149
+ validate { true }
150
+ success &call_me
151
+ end
152
+ fld.run app
153
+ call.input << '1'
154
+ fld.current_prompt.finalise
155
+ end
156
+
157
+ it 'should run failure callback if not valid and last attempt' do
158
+ call_me = should_be_called
159
+ fld = field(:guess, :min_length => 1, :attempts => 1) do
160
+ prompt :speak => 'first'
161
+ failure &call_me
162
+ end
163
+ fld.run app
164
+ fld.current_prompt.finalise
165
+ end
166
+
167
+ it 'should increment attempts if not valid' do
168
+ fld = field(:guess) do
169
+ prompt :speak => 'first'
170
+ reprompt :speak => 'second'
171
+ end
172
+ fld.run app
173
+ fld.current_prompt.finalise
174
+ fld.current_prompt.message.should == 'second'
175
+ end
176
+
177
+ it 'should execute next prompt if not valid' do
178
+ fld = field(:guess) do
179
+ prompt :speak => 'first'
180
+ reprompt :speak => 'second'
181
+ end
182
+ fld.run app
183
+ fld.should_receive(:execute_prompt)
184
+ fld.current_prompt.finalise
185
+ end
186
+ end
187
+
188
+ end
189
+
190
+ def field(name, options={}, &block)
191
+ @app.class.class_eval { attr_accessor name }
192
+ Larynx::Fields::Field.new(name, options, &block)
193
+ end
194
+ end