larynx 0.1.0

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