punchblock 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,491 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module Component
7
+ describe Output do
8
+ let(:connection) do
9
+ mock_connection_with_event_handler do |event|
10
+ command.add_event event
11
+ end
12
+ end
13
+ let(:media_engine) { nil }
14
+ let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
15
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
16
+
17
+ let :command do
18
+ Punchblock::Component::Output.new command_options
19
+ end
20
+
21
+ let :ssml_doc do
22
+ RubySpeech::SSML.draw do
23
+ say_as(:interpret_as => :cardinal) { 'FOO' }
24
+ end
25
+ end
26
+
27
+ let :command_options do
28
+ { :ssml => ssml_doc }
29
+ end
30
+
31
+ subject { Output.new command, mock_call }
32
+
33
+ describe '#execute' do
34
+ before { command.request! }
35
+
36
+ context 'with a media engine of :unimrcp' do
37
+ let(:media_engine) { :unimrcp }
38
+
39
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
40
+
41
+ let :ssml_doc do
42
+ RubySpeech::SSML.draw do
43
+ audio :src => audio_filename
44
+ say_as(:interpret_as => :cardinal) { 'FOO' }
45
+ end
46
+ end
47
+
48
+ let(:command_opts) { {} }
49
+
50
+ let :command_options do
51
+ { :ssml => ssml_doc }.merge(command_opts)
52
+ end
53
+
54
+ def expect_mrcpsynth_with_options(options)
55
+ mock_call.expects(:send_agi_action!).once.with do |*args|
56
+ args[0].should == 'EXEC MRCPSynth'
57
+ args[2].should match options
58
+ end
59
+ end
60
+
61
+ it "should execute MRCPSynth" do
62
+ mock_call.expects(:send_agi_action!).once.with 'EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, ''
63
+ subject.execute
64
+ end
65
+
66
+ it 'should send a complete event when MRCPSynth completes' do
67
+ def mock_call.send_agi_action!(*args, &block)
68
+ block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
69
+ end
70
+ subject.execute
71
+ command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
72
+ end
73
+
74
+ describe 'ssml' do
75
+ context 'unset' do
76
+ let(:command_opts) { { :ssml => nil } }
77
+ it "should return an error and not execute any actions" do
78
+ subject.execute
79
+ error = ProtocolError.new 'option error', 'An SSML document is required.'
80
+ command.response(0.1).should == error
81
+ end
82
+ end
83
+ end
84
+
85
+ describe 'start-offset' do
86
+ context 'unset' do
87
+ let(:command_opts) { { :start_offset => nil } }
88
+ it 'should not pass any options to MRCPSynth' do
89
+ expect_mrcpsynth_with_options(//)
90
+ subject.execute
91
+ end
92
+ end
93
+
94
+ context 'set' do
95
+ let(:command_opts) { { :start_offset => 10 } }
96
+ it "should return an error and not execute any actions" do
97
+ subject.execute
98
+ error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
99
+ command.response(0.1).should == error
100
+ end
101
+ end
102
+ end
103
+
104
+ describe 'start-paused' do
105
+ context 'false' do
106
+ let(:command_opts) { { :start_paused => false } }
107
+ it 'should not pass any options to MRCPSynth' do
108
+ expect_mrcpsynth_with_options(//)
109
+ subject.execute
110
+ end
111
+ end
112
+
113
+ context 'true' do
114
+ let(:command_opts) { { :start_paused => true } }
115
+ it "should return an error and not execute any actions" do
116
+ subject.execute
117
+ error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
118
+ command.response(0.1).should == error
119
+ end
120
+ end
121
+ end
122
+
123
+ describe 'repeat-interval' do
124
+ context 'unset' do
125
+ let(:command_opts) { { :repeat_interval => nil } }
126
+ it 'should not pass any options to MRCPSynth' do
127
+ expect_mrcpsynth_with_options(//)
128
+ subject.execute
129
+ end
130
+ end
131
+
132
+ context 'set' do
133
+ let(:command_opts) { { :repeat_interval => 10 } }
134
+ it "should return an error and not execute any actions" do
135
+ subject.execute
136
+ error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
137
+ command.response(0.1).should == error
138
+ end
139
+ end
140
+ end
141
+
142
+ describe 'repeat-times' do
143
+ context 'unset' do
144
+ let(:command_opts) { { :repeat_times => nil } }
145
+ it 'should not pass any options to MRCPSynth' do
146
+ expect_mrcpsynth_with_options(//)
147
+ subject.execute
148
+ end
149
+ end
150
+
151
+ context 'set' do
152
+ let(:command_opts) { { :repeat_times => 2 } }
153
+ it "should return an error and not execute any actions" do
154
+ subject.execute
155
+ error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
156
+ command.response(0.1).should == error
157
+ end
158
+ end
159
+ end
160
+
161
+ describe 'max-time' do
162
+ context 'unset' do
163
+ let(:command_opts) { { :max_time => nil } }
164
+ it 'should not pass any options to MRCPSynth' do
165
+ expect_mrcpsynth_with_options(//)
166
+ subject.execute
167
+ end
168
+ end
169
+
170
+ context 'set' do
171
+ let(:command_opts) { { :max_time => 30 } }
172
+ it "should return an error and not execute any actions" do
173
+ subject.execute
174
+ error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
175
+ command.response(0.1).should == error
176
+ end
177
+ end
178
+ end
179
+
180
+ describe 'voice' do
181
+ context 'unset' do
182
+ let(:command_opts) { { :voice => nil } }
183
+ it 'should not pass the v option to MRCPSynth' do
184
+ expect_mrcpsynth_with_options(//)
185
+ subject.execute
186
+ end
187
+ end
188
+
189
+ context 'set' do
190
+ let(:command_opts) { { :voice => 'alison' } }
191
+ it 'should pass the v option to MRCPSynth' do
192
+ expect_mrcpsynth_with_options(/v=alison/)
193
+ subject.execute
194
+ end
195
+ end
196
+ end
197
+
198
+ describe 'interrupt_on' do
199
+ context "set to nil" do
200
+ let(:command_opts) { { :interrupt_on => nil } }
201
+ it "should not pass the i option to MRCPSynth" do
202
+ expect_mrcpsynth_with_options(//)
203
+ subject.execute
204
+ end
205
+ end
206
+
207
+ context "set to :any" do
208
+ let(:command_opts) { { :interrupt_on => :any } }
209
+ it "should pass the i option to MRCPSynth" do
210
+ expect_mrcpsynth_with_options(/i=any/)
211
+ subject.execute
212
+ end
213
+ end
214
+
215
+ context "set to :dtmf" do
216
+ let(:command_opts) { { :interrupt_on => :dtmf } }
217
+ it "should pass the i option to MRCPSynth" do
218
+ expect_mrcpsynth_with_options(/i=any/)
219
+ subject.execute
220
+ end
221
+ end
222
+
223
+ context "set to :speech" do
224
+ let(:command_opts) { { :interrupt_on => :speech } }
225
+ it "should return an error and not execute any actions" do
226
+ subject.execute
227
+ error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
228
+ command.response(0.1).should == error
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ context 'with a media engine of :asterisk' do
235
+ let(:media_engine) { :asterisk }
236
+
237
+ def expect_stream_file_with_options(options = nil)
238
+ mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename, options do |*args|
239
+ args[2].should == options
240
+ subject.continue!
241
+ true
242
+ end
243
+ end
244
+
245
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
246
+
247
+ let :ssml_doc do
248
+ RubySpeech::SSML.draw do
249
+ audio :src => audio_filename
250
+ say_as(:interpret_as => :cardinal) { 'FOO' }
251
+ end
252
+ end
253
+
254
+ let(:command_opts) { {} }
255
+
256
+ let :command_options do
257
+ { :ssml => ssml_doc }.merge(command_opts)
258
+ end
259
+
260
+ let :command do
261
+ Punchblock::Component::Output.new command_options
262
+ end
263
+
264
+ describe 'ssml' do
265
+ context 'unset' do
266
+ let(:command_opts) { { :ssml => nil } }
267
+ it "should return an error and not execute any actions" do
268
+ subject.execute
269
+ error = ProtocolError.new 'option error', 'An SSML document is required.'
270
+ command.response(0.1).should == error
271
+ end
272
+ end
273
+
274
+ context 'with a single audio SSML node' do
275
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
276
+ let :command_options do
277
+ {
278
+ :ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
279
+ }
280
+ end
281
+
282
+ it 'should playback the audio file using STREAM FILE' do
283
+ expect_stream_file_with_options
284
+ subject.execute
285
+ end
286
+
287
+ it 'should send a complete event when the file finishes playback' do
288
+ def mock_call.send_agi_action!(*args, &block)
289
+ block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
290
+ end
291
+ subject.execute
292
+ command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
293
+ end
294
+ end
295
+
296
+ context 'with multiple audio SSML nodes' do
297
+ let(:audio_filename1) { 'http://foo.com/bar.mp3' }
298
+ let(:audio_filename2) { 'http://foo.com/baz.mp3' }
299
+ let :command_options do
300
+ {
301
+ :ssml => RubySpeech::SSML.draw do
302
+ audio :src => audio_filename1
303
+ audio :src => audio_filename2
304
+ end
305
+ }
306
+ end
307
+
308
+ it 'should playback each audio file using STREAM FILE' do
309
+ latch = CountDownLatch.new 2
310
+ mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename1, nil do
311
+ subject.continue
312
+ true
313
+ latch.countdown!
314
+ end
315
+ mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename2, nil do
316
+ subject.continue
317
+ true
318
+ latch.countdown!
319
+ end
320
+ subject.execute
321
+ latch.wait 2
322
+ sleep 2
323
+ end
324
+
325
+ it 'should send a complete event after the final file has finished playback' do
326
+ def mock_call.send_agi_action!(*args, &block)
327
+ block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
328
+ end
329
+ command.expects(:add_event).once.with do |e|
330
+ e.reason.should be_a Punchblock::Component::Output::Complete::Success
331
+ end
332
+ subject.execute
333
+ end
334
+ end
335
+ end
336
+
337
+ describe 'start-offset' do
338
+ context 'unset' do
339
+ let(:command_opts) { { :start_offset => nil } }
340
+ it 'should not pass any options to STREAM FILE' do
341
+ expect_stream_file_with_options
342
+ subject.execute
343
+ end
344
+ end
345
+
346
+ context 'set' do
347
+ let(:command_opts) { { :start_offset => 10 } }
348
+ it "should return an error and not execute any actions" do
349
+ subject.execute
350
+ error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
351
+ command.response(0.1).should == error
352
+ end
353
+ end
354
+ end
355
+
356
+ describe 'start-paused' do
357
+ context 'false' do
358
+ let(:command_opts) { { :start_paused => false } }
359
+ it 'should not pass any options to STREAM FILE' do
360
+ expect_stream_file_with_options
361
+ subject.execute
362
+ end
363
+ end
364
+
365
+ context 'true' do
366
+ let(:command_opts) { { :start_paused => true } }
367
+ it "should return an error and not execute any actions" do
368
+ subject.execute
369
+ error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
370
+ command.response(0.1).should == error
371
+ end
372
+ end
373
+ end
374
+
375
+ describe 'repeat-interval' do
376
+ context 'unset' do
377
+ let(:command_opts) { { :repeat_interval => nil } }
378
+ it 'should not pass any options to STREAM FILE' do
379
+ expect_stream_file_with_options
380
+ subject.execute
381
+ end
382
+ end
383
+
384
+ context 'set' do
385
+ let(:command_opts) { { :repeat_interval => 10 } }
386
+ it "should return an error and not execute any actions" do
387
+ subject.execute
388
+ error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
389
+ command.response(0.1).should == error
390
+ end
391
+ end
392
+ end
393
+
394
+ describe 'repeat-times' do
395
+ context 'unset' do
396
+ let(:command_opts) { { :repeat_times => nil } }
397
+ it 'should not pass any options to STREAM FILE' do
398
+ expect_stream_file_with_options
399
+ subject.execute
400
+ end
401
+ end
402
+
403
+ context 'set' do
404
+ let(:command_opts) { { :repeat_times => 2 } }
405
+ it "should return an error and not execute any actions" do
406
+ subject.execute
407
+ error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
408
+ command.response(0.1).should == error
409
+ end
410
+ end
411
+ end
412
+
413
+ describe 'max-time' do
414
+ context 'unset' do
415
+ let(:command_opts) { { :max_time => nil } }
416
+ it 'should not pass any options to STREAM FILE' do
417
+ expect_stream_file_with_options
418
+ subject.execute
419
+ end
420
+ end
421
+
422
+ context 'set' do
423
+ let(:command_opts) { { :max_time => 30 } }
424
+ it "should return an error and not execute any actions" do
425
+ subject.execute
426
+ error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
427
+ command.response(0.1).should == error
428
+ end
429
+ end
430
+ end
431
+
432
+ describe 'voice' do
433
+ context 'unset' do
434
+ let(:command_opts) { { :voice => nil } }
435
+ it 'should not pass the v option to STREAM FILE' do
436
+ expect_stream_file_with_options
437
+ subject.execute
438
+ end
439
+ end
440
+
441
+ context 'set' do
442
+ let(:command_opts) { { :voice => 'alison' } }
443
+ it "should return an error and not execute any actions" do
444
+ subject.execute
445
+ error = ProtocolError.new 'option error', 'A voice value is unsupported on Asterisk.'
446
+ command.response(0.1).should == error
447
+ end
448
+ end
449
+ end
450
+
451
+ describe 'interrupt_on' do
452
+ context "set to nil" do
453
+ let(:command_opts) { { :interrupt_on => nil } }
454
+ it "should not pass any digits to STREAM FILE" do
455
+ expect_stream_file_with_options
456
+ subject.execute
457
+ end
458
+ end
459
+
460
+ context "set to :any" do
461
+ let(:command_opts) { { :interrupt_on => :any } }
462
+ it "should pass all digits to STREAM FILE" do
463
+ expect_stream_file_with_options '0123456789*#'
464
+ subject.execute
465
+ end
466
+ end
467
+
468
+ context "set to :dtmf" do
469
+ let(:command_opts) { { :interrupt_on => :dtmf } }
470
+ it "should pass all digits to STREAM FILE" do
471
+ expect_stream_file_with_options '0123456789*#'
472
+ subject.execute
473
+ end
474
+ end
475
+
476
+ context "set to :speech" do
477
+ let(:command_opts) { { :interrupt_on => :speech } }
478
+ it "should return an error and not execute any actions" do
479
+ subject.execute
480
+ error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
481
+ command.response(0.1).should == error
482
+ end
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end
491
+ end