punchblock 0.8.4 → 0.9.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,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