punchblock 1.7.0 → 1.7.1
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.
- data/CHANGELOG.md +3 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/call.rb +1 -1
- data/lib/punchblock/version.rb +1 -1
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +309 -309
- data/spec/punchblock/translator/freeswitch/call_spec.rb +11 -5
- metadata +3 -3
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# [develop](https://github.com/adhearsion/punchblock)
|
|
2
2
|
|
|
3
|
+
# [v1.7.1](https://github.com/adhearsion/punchblock/compare/v1.7.0...v1.7.1) - [2012-12-17](https://rubygems.org/gems/punchblock/versions/1.7.1)
|
|
4
|
+
* Bugfix: Deal with nil media engines on FS/* properly
|
|
5
|
+
|
|
3
6
|
# [v1.7.0](https://github.com/adhearsion/punchblock/compare/v1.6.1...v1.7.0) - [2012-12-13](https://rubygems.org/gems/punchblock/versions/1.7.0)
|
|
4
7
|
* Feature: Support for the renderer attribute added to the Output component.
|
|
5
8
|
* Feature: FreeSWITCH and Asterisk translators now use the :renderer attribute on Output
|
|
@@ -27,10 +27,10 @@ module Punchblock
|
|
|
27
27
|
|
|
28
28
|
output_component = current_actor
|
|
29
29
|
|
|
30
|
-
rendering_engine = @component_node.renderer
|
|
30
|
+
rendering_engine = @component_node.renderer || @media_engine || :asterisk
|
|
31
31
|
|
|
32
32
|
case rendering_engine.to_sym
|
|
33
|
-
when :asterisk
|
|
33
|
+
when :asterisk
|
|
34
34
|
raise OptionError, "A voice value is unsupported on Asterisk." if @component_node.voice
|
|
35
35
|
raise OptionError, 'Interrupt digits are not allowed with early media.' if early && @component_node.interrupt_on
|
|
36
36
|
|
|
@@ -198,7 +198,7 @@ module Punchblock
|
|
|
198
198
|
hangup REJECT_TO_HANGUP_REASON[command.reason]
|
|
199
199
|
command.response = true
|
|
200
200
|
when Punchblock::Component::Output
|
|
201
|
-
media_renderer = command.renderer
|
|
201
|
+
media_renderer = command.renderer || media_engine || :freeswitch
|
|
202
202
|
case media_renderer.to_sym
|
|
203
203
|
when :freeswitch, :native, nil
|
|
204
204
|
execute_component Component::Output, command
|
data/lib/punchblock/version.rb
CHANGED
|
@@ -355,407 +355,407 @@ module Punchblock
|
|
|
355
355
|
end
|
|
356
356
|
end
|
|
357
357
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
def expect_playback_noanswer
|
|
366
|
-
mock_call.should_receive(:send_agi_action!).once.with 'EXEC Playback', audio_filename + ',noanswer'
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
358
|
+
[:asterisk, nil].each do |media_engine|
|
|
359
|
+
context "with a media engine of #{media_engine.inspect}" do
|
|
360
|
+
def expect_playback(filename = audio_filename)
|
|
361
|
+
mock_call.should_receive(:send_agi_action!).once.with 'EXEC Playback', filename
|
|
362
|
+
end
|
|
370
363
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
audio :src => audio_filename
|
|
364
|
+
def expect_playback_noanswer
|
|
365
|
+
mock_call.should_receive(:send_agi_action!).once.with 'EXEC Playback', audio_filename + ',noanswer'
|
|
374
366
|
end
|
|
375
|
-
end
|
|
376
367
|
|
|
377
|
-
|
|
368
|
+
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
378
369
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
370
|
+
let :ssml_doc do
|
|
371
|
+
RubySpeech::SSML.draw do
|
|
372
|
+
audio :src => audio_filename
|
|
373
|
+
end
|
|
374
|
+
end
|
|
382
375
|
|
|
383
|
-
|
|
384
|
-
Punchblock::Component::Output.new command_options
|
|
385
|
-
end
|
|
376
|
+
let(:command_opts) { {} }
|
|
386
377
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
let(:command_opts) { { :ssml => nil } }
|
|
390
|
-
it "should return an error and not execute any actions" do
|
|
391
|
-
subject.execute
|
|
392
|
-
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
393
|
-
original_command.response(0.1).should be == error
|
|
394
|
-
end
|
|
378
|
+
let :command_options do
|
|
379
|
+
{ :ssml => ssml_doc }.merge(command_opts)
|
|
395
380
|
end
|
|
396
381
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
{
|
|
401
|
-
:ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
|
|
402
|
-
}
|
|
403
|
-
end
|
|
382
|
+
let :original_command do
|
|
383
|
+
Punchblock::Component::Output.new command_options
|
|
384
|
+
end
|
|
404
385
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
386
|
+
describe 'ssml' do
|
|
387
|
+
context 'unset' do
|
|
388
|
+
let(:command_opts) { { :ssml => nil } }
|
|
389
|
+
it "should return an error and not execute any actions" do
|
|
390
|
+
subject.execute
|
|
391
|
+
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
392
|
+
original_command.response(0.1).should be == error
|
|
393
|
+
end
|
|
409
394
|
end
|
|
410
395
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
396
|
+
context 'with a single audio SSML node' do
|
|
397
|
+
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
398
|
+
let :command_options do
|
|
399
|
+
{
|
|
400
|
+
:ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
|
|
401
|
+
}
|
|
417
402
|
end
|
|
418
|
-
subject.execute
|
|
419
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
403
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
end
|
|
404
|
+
it 'should playback the audio file using Playback' do
|
|
405
|
+
expect_answered
|
|
406
|
+
expect_playback
|
|
407
|
+
subject.execute
|
|
408
|
+
end
|
|
430
409
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
410
|
+
it 'should send a complete event when the file finishes playback' do
|
|
411
|
+
def mock_call.answered?
|
|
412
|
+
true
|
|
413
|
+
end
|
|
414
|
+
def mock_call.send_agi_action!(*args, &block)
|
|
415
|
+
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
416
|
+
end
|
|
417
|
+
subject.execute
|
|
418
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
419
|
+
end
|
|
435
420
|
end
|
|
436
421
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
422
|
+
context 'with a single text node without spaces' do
|
|
423
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
424
|
+
let :command_options do
|
|
425
|
+
{
|
|
426
|
+
:ssml => RubySpeech::SSML.draw { string audio_filename }
|
|
427
|
+
}
|
|
441
428
|
end
|
|
442
|
-
subject.execute
|
|
443
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
444
|
-
end
|
|
445
429
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
expect_playback_noanswer
|
|
450
|
-
mock_call.should_receive(:send_progress)
|
|
430
|
+
it 'should playback the audio file using Playback' do
|
|
431
|
+
expect_answered
|
|
432
|
+
expect_playback
|
|
451
433
|
subject.execute
|
|
452
434
|
end
|
|
453
435
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
:ssml => RubySpeech::SSML.draw { string audio_filename },
|
|
459
|
-
:interrupt_on => :any
|
|
460
|
-
}
|
|
436
|
+
it 'should send a complete event when the file finishes playback' do
|
|
437
|
+
expect_answered
|
|
438
|
+
def mock_call.send_agi_action!(*args, &block)
|
|
439
|
+
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
461
440
|
end
|
|
462
|
-
|
|
441
|
+
subject.execute
|
|
442
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
context "with early media playback" do
|
|
446
|
+
it "should play the file with Playback" do
|
|
463
447
|
expect_answered false
|
|
464
|
-
|
|
448
|
+
expect_playback_noanswer
|
|
449
|
+
mock_call.should_receive(:send_progress)
|
|
465
450
|
subject.execute
|
|
466
|
-
original_command.response(0.1).should be == error
|
|
467
451
|
end
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
452
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
let(:audio_filename2) { 'http://foo.com/baz.mp3' }
|
|
487
|
-
let :command_options do
|
|
488
|
-
{
|
|
489
|
-
:ssml => RubySpeech::SSML.draw do
|
|
490
|
-
audio :src => audio_filename1
|
|
491
|
-
audio :src => audio_filename2
|
|
453
|
+
context "with interrupt_on set to something that is not nil" do
|
|
454
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
455
|
+
let :command_options do
|
|
456
|
+
{
|
|
457
|
+
:ssml => RubySpeech::SSML.draw { string audio_filename },
|
|
458
|
+
:interrupt_on => :any
|
|
459
|
+
}
|
|
460
|
+
end
|
|
461
|
+
it "should return an error when the output is interruptible and it is early media" do
|
|
462
|
+
expect_answered false
|
|
463
|
+
error = ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
|
|
464
|
+
subject.execute
|
|
465
|
+
original_command.response(0.1).should be == error
|
|
466
|
+
end
|
|
492
467
|
end
|
|
493
|
-
|
|
468
|
+
end
|
|
494
469
|
end
|
|
495
470
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
471
|
+
context 'with a string (not SSML)' do
|
|
472
|
+
let :command_options do
|
|
473
|
+
{ :text => 'Foo Bar' }
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
it "should return an unrenderable document error" do
|
|
477
|
+
subject.execute
|
|
478
|
+
error = ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.'
|
|
479
|
+
original_command.response(0.1).should be == error
|
|
480
|
+
end
|
|
503
481
|
end
|
|
504
482
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
483
|
+
context 'with multiple audio SSML nodes' do
|
|
484
|
+
let(:audio_filename1) { 'http://foo.com/bar.mp3' }
|
|
485
|
+
let(:audio_filename2) { 'http://foo.com/baz.mp3' }
|
|
486
|
+
let :command_options do
|
|
487
|
+
{
|
|
488
|
+
:ssml => RubySpeech::SSML.draw do
|
|
489
|
+
audio :src => audio_filename1
|
|
490
|
+
audio :src => audio_filename2
|
|
491
|
+
end
|
|
492
|
+
}
|
|
509
493
|
end
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
494
|
+
|
|
495
|
+
it 'should playback all audio files using Playback' do
|
|
496
|
+
latch = CountDownLatch.new 2
|
|
497
|
+
expect_playback [audio_filename1, audio_filename2].join('&')
|
|
498
|
+
expect_answered
|
|
499
|
+
subject.execute
|
|
500
|
+
latch.wait 2
|
|
501
|
+
sleep 2
|
|
514
502
|
end
|
|
515
|
-
subject.execute
|
|
516
|
-
latch.wait(2).should be_true
|
|
517
|
-
end
|
|
518
|
-
end
|
|
519
503
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
string "Foo Bar"
|
|
504
|
+
it 'should send a complete event after the final file has finished playback' do
|
|
505
|
+
expect_answered
|
|
506
|
+
def mock_call.send_agi_action!(*args, &block)
|
|
507
|
+
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
525
508
|
end
|
|
526
|
-
|
|
509
|
+
latch = CountDownLatch.new 1
|
|
510
|
+
original_command.should_receive(:add_event).once.with do |e|
|
|
511
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
512
|
+
latch.countdown!
|
|
513
|
+
end
|
|
514
|
+
subject.execute
|
|
515
|
+
latch.wait(2).should be_true
|
|
516
|
+
end
|
|
527
517
|
end
|
|
528
518
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
519
|
+
context "with an SSML document containing elements other than <audio/>" do
|
|
520
|
+
let :command_options do
|
|
521
|
+
{
|
|
522
|
+
:ssml => RubySpeech::SSML.draw do
|
|
523
|
+
string "Foo Bar"
|
|
524
|
+
end
|
|
525
|
+
}
|
|
526
|
+
end
|
|
536
527
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
expect_playback
|
|
543
|
-
subject.execute
|
|
528
|
+
it "should return an unrenderable document error" do
|
|
529
|
+
subject.execute
|
|
530
|
+
error = ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.'
|
|
531
|
+
original_command.response(0.1).should be == error
|
|
532
|
+
end
|
|
544
533
|
end
|
|
545
534
|
end
|
|
546
535
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
536
|
+
describe 'start-offset' do
|
|
537
|
+
context 'unset' do
|
|
538
|
+
let(:command_opts) { { :start_offset => nil } }
|
|
539
|
+
it 'should not pass any options to Playback' do
|
|
540
|
+
expect_answered
|
|
541
|
+
expect_playback
|
|
542
|
+
subject.execute
|
|
543
|
+
end
|
|
553
544
|
end
|
|
554
|
-
end
|
|
555
|
-
end
|
|
556
545
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
546
|
+
context 'set' do
|
|
547
|
+
let(:command_opts) { { :start_offset => 10 } }
|
|
548
|
+
it "should return an error and not execute any actions" do
|
|
549
|
+
subject.execute
|
|
550
|
+
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
551
|
+
original_command.response(0.1).should be == error
|
|
552
|
+
end
|
|
564
553
|
end
|
|
565
554
|
end
|
|
566
555
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
556
|
+
describe 'start-paused' do
|
|
557
|
+
context 'false' do
|
|
558
|
+
let(:command_opts) { { :start_paused => false } }
|
|
559
|
+
it 'should not pass any options to Playback' do
|
|
560
|
+
expect_answered
|
|
561
|
+
expect_playback
|
|
562
|
+
subject.execute
|
|
563
|
+
end
|
|
573
564
|
end
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
565
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
566
|
+
context 'true' do
|
|
567
|
+
let(:command_opts) { { :start_paused => true } }
|
|
568
|
+
it "should return an error and not execute any actions" do
|
|
569
|
+
subject.execute
|
|
570
|
+
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
571
|
+
original_command.response(0.1).should be == error
|
|
572
|
+
end
|
|
584
573
|
end
|
|
585
574
|
end
|
|
586
575
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
576
|
+
describe 'repeat-interval' do
|
|
577
|
+
context 'unset' do
|
|
578
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
|
579
|
+
it 'should not pass any options to Playback' do
|
|
580
|
+
expect_answered
|
|
581
|
+
expect_playback
|
|
582
|
+
subject.execute
|
|
583
|
+
end
|
|
593
584
|
end
|
|
594
|
-
end
|
|
595
|
-
end
|
|
596
585
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
586
|
+
context 'set' do
|
|
587
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
|
588
|
+
it "should return an error and not execute any actions" do
|
|
589
|
+
subject.execute
|
|
590
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
591
|
+
original_command.response(0.1).should be == error
|
|
592
|
+
end
|
|
604
593
|
end
|
|
605
594
|
end
|
|
606
595
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
596
|
+
describe 'repeat-times' do
|
|
597
|
+
context 'unset' do
|
|
598
|
+
let(:command_opts) { { :repeat_times => nil } }
|
|
599
|
+
it 'should not pass any options to Playback' do
|
|
600
|
+
expect_answered
|
|
601
|
+
expect_playback
|
|
602
|
+
subject.execute
|
|
603
|
+
end
|
|
613
604
|
end
|
|
614
|
-
end
|
|
615
|
-
end
|
|
616
605
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
606
|
+
context 'set' do
|
|
607
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
|
608
|
+
it "should return an error and not execute any actions" do
|
|
609
|
+
subject.execute
|
|
610
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.'
|
|
611
|
+
original_command.response(0.1).should be == error
|
|
612
|
+
end
|
|
624
613
|
end
|
|
625
614
|
end
|
|
626
615
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
616
|
+
describe 'max-time' do
|
|
617
|
+
context 'unset' do
|
|
618
|
+
let(:command_opts) { { :max_time => nil } }
|
|
619
|
+
it 'should not pass any options to Playback' do
|
|
620
|
+
expect_answered
|
|
621
|
+
expect_playback
|
|
622
|
+
subject.execute
|
|
623
|
+
end
|
|
633
624
|
end
|
|
634
|
-
end
|
|
635
|
-
end
|
|
636
625
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
626
|
+
context 'set' do
|
|
627
|
+
let(:command_opts) { { :max_time => 30 } }
|
|
628
|
+
it "should return an error and not execute any actions" do
|
|
629
|
+
subject.execute
|
|
630
|
+
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
631
|
+
original_command.response(0.1).should be == error
|
|
632
|
+
end
|
|
644
633
|
end
|
|
645
634
|
end
|
|
646
635
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
636
|
+
describe 'voice' do
|
|
637
|
+
context 'unset' do
|
|
638
|
+
let(:command_opts) { { :voice => nil } }
|
|
639
|
+
it 'should not pass the v option to Playback' do
|
|
640
|
+
expect_answered
|
|
641
|
+
expect_playback
|
|
642
|
+
subject.execute
|
|
643
|
+
end
|
|
653
644
|
end
|
|
654
|
-
end
|
|
655
|
-
end
|
|
656
645
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
646
|
+
context 'set' do
|
|
647
|
+
let(:command_opts) { { :voice => 'alison' } }
|
|
648
|
+
it "should return an error and not execute any actions" do
|
|
649
|
+
subject.execute
|
|
650
|
+
error = ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
|
|
651
|
+
original_command.response(0.1).should be == error
|
|
652
|
+
end
|
|
663
653
|
end
|
|
664
654
|
end
|
|
665
655
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
let :ami_event do
|
|
674
|
-
RubyAMI::Event.new('AsyncAGI').tap do |e|
|
|
675
|
-
e['SubEvent'] = "Start"
|
|
676
|
-
e['Channel'] = channel
|
|
677
|
-
e['Env'] = "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
|
|
656
|
+
describe 'interrupt_on' do
|
|
657
|
+
def ami_event_for_dtmf(digit, position)
|
|
658
|
+
RubyAMI::Event.new('DTMF').tap do |e|
|
|
659
|
+
e['Digit'] = digit.to_s
|
|
660
|
+
e['Start'] = position == :start ? 'Yes' : 'No'
|
|
661
|
+
e['End'] = position == :end ? 'Yes' : 'No'
|
|
662
|
+
end
|
|
678
663
|
end
|
|
679
|
-
end
|
|
680
664
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
expect_answered
|
|
685
|
-
expect_playback
|
|
686
|
-
mock_call.should_receive(:redirect_back!).never
|
|
687
|
-
subject.execute
|
|
688
|
-
original_command.response(0.1).should be_a Ref
|
|
689
|
-
send_ami_events_for_dtmf 1
|
|
665
|
+
def send_ami_events_for_dtmf(digit)
|
|
666
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
|
|
667
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
|
|
690
668
|
end
|
|
691
|
-
end
|
|
692
669
|
|
|
693
|
-
|
|
694
|
-
let(:
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
670
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
671
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
672
|
+
let :ami_event do
|
|
673
|
+
RubyAMI::Event.new('AsyncAGI').tap do |e|
|
|
674
|
+
e['SubEvent'] = "Start"
|
|
675
|
+
e['Channel'] = channel
|
|
676
|
+
e['Env'] = "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
|
|
677
|
+
end
|
|
699
678
|
end
|
|
700
679
|
|
|
701
|
-
context "
|
|
702
|
-
|
|
703
|
-
|
|
680
|
+
context "set to nil" do
|
|
681
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
682
|
+
it "does not redirect the call" do
|
|
683
|
+
expect_answered
|
|
684
|
+
expect_playback
|
|
685
|
+
mock_call.should_receive(:redirect_back!).never
|
|
704
686
|
subject.execute
|
|
705
687
|
original_command.response(0.1).should be_a Ref
|
|
706
|
-
original_command.should_not be_complete
|
|
707
688
|
send_ami_events_for_dtmf 1
|
|
708
|
-
mock_call.process_ami_event! ami_event
|
|
709
|
-
sleep 0.2
|
|
710
|
-
original_command.should be_complete
|
|
711
|
-
reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
712
689
|
end
|
|
690
|
+
end
|
|
713
691
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
692
|
+
context "set to :any" do
|
|
693
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
694
|
+
|
|
695
|
+
before do
|
|
696
|
+
expect_answered
|
|
697
|
+
expect_playback
|
|
719
698
|
end
|
|
720
|
-
end
|
|
721
|
-
end
|
|
722
699
|
|
|
723
|
-
|
|
724
|
-
|
|
700
|
+
context "when a DTMF digit is received" do
|
|
701
|
+
it "sends the correct complete event" do
|
|
702
|
+
mock_call.should_receive :redirect_back!
|
|
703
|
+
subject.execute
|
|
704
|
+
original_command.response(0.1).should be_a Ref
|
|
705
|
+
original_command.should_not be_complete
|
|
706
|
+
send_ami_events_for_dtmf 1
|
|
707
|
+
mock_call.process_ami_event! ami_event
|
|
708
|
+
sleep 0.2
|
|
709
|
+
original_command.should be_complete
|
|
710
|
+
reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
711
|
+
end
|
|
725
712
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
713
|
+
it "redirects the call back to async AGI" do
|
|
714
|
+
mock_call.should_receive(:redirect_back!).once
|
|
715
|
+
subject.execute
|
|
716
|
+
original_command.response(0.1).should be_a Ref
|
|
717
|
+
send_ami_events_for_dtmf 1
|
|
718
|
+
end
|
|
719
|
+
end
|
|
729
720
|
end
|
|
730
721
|
|
|
731
|
-
context "
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
send_ami_events_for_dtmf 1
|
|
738
|
-
mock_call.process_ami_event! ami_event
|
|
739
|
-
sleep 0.2
|
|
740
|
-
original_command.should be_complete
|
|
741
|
-
reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
722
|
+
context "set to :dtmf" do
|
|
723
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
724
|
+
|
|
725
|
+
before do
|
|
726
|
+
expect_answered
|
|
727
|
+
expect_playback
|
|
742
728
|
end
|
|
743
729
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
730
|
+
context "when a DTMF digit is received" do
|
|
731
|
+
it "sends the correct complete event" do
|
|
732
|
+
mock_call.should_receive :redirect_back!
|
|
733
|
+
subject.execute
|
|
734
|
+
original_command.response(0.1).should be_a Ref
|
|
735
|
+
original_command.should_not be_complete
|
|
736
|
+
send_ami_events_for_dtmf 1
|
|
737
|
+
mock_call.process_ami_event! ami_event
|
|
738
|
+
sleep 0.2
|
|
739
|
+
original_command.should be_complete
|
|
740
|
+
reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
it "redirects the call back to async AGI" do
|
|
744
|
+
mock_call.should_receive(:redirect_back!).once
|
|
745
|
+
subject.execute
|
|
746
|
+
original_command.response(0.1).should be_a Ref
|
|
747
|
+
send_ami_events_for_dtmf 1
|
|
748
|
+
end
|
|
749
749
|
end
|
|
750
750
|
end
|
|
751
|
-
end
|
|
752
751
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
752
|
+
context "set to :speech" do
|
|
753
|
+
let(:command_opts) { { :interrupt_on => :speech } }
|
|
754
|
+
it "should return an error and not execute any actions" do
|
|
755
|
+
subject.execute
|
|
756
|
+
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
757
|
+
original_command.response(0.1).should be == error
|
|
758
|
+
end
|
|
759
759
|
end
|
|
760
760
|
end
|
|
761
761
|
end
|
|
@@ -754,11 +754,17 @@ module Punchblock
|
|
|
754
754
|
|
|
755
755
|
let(:mock_component) { mock 'Freeswitch::Component::Output', :id => 'foo' }
|
|
756
756
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
757
|
+
['freeswitch', nil].each do |media_engine|
|
|
758
|
+
let(:media_engine) { media_engine }
|
|
759
|
+
|
|
760
|
+
context "with a media engine of #{media_engine}" do
|
|
761
|
+
it 'should create an Output component and execute it asynchronously' do
|
|
762
|
+
Component::Output.should_receive(:new_link).once.with(command, subject).and_return mock_component
|
|
763
|
+
mock_component.should_receive(:execute!).once
|
|
764
|
+
subject.execute_command command
|
|
765
|
+
subject.component_with_id('foo').should be mock_component
|
|
766
|
+
end
|
|
767
|
+
end
|
|
762
768
|
end
|
|
763
769
|
|
|
764
770
|
context 'with the media engine of :flite' do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: punchblock
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.7.
|
|
4
|
+
version: 1.7.1
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -11,7 +11,7 @@ authors:
|
|
|
11
11
|
autorequire:
|
|
12
12
|
bindir: bin
|
|
13
13
|
cert_chain: []
|
|
14
|
-
date: 2012-12-
|
|
14
|
+
date: 2012-12-17 00:00:00.000000000 Z
|
|
15
15
|
dependencies:
|
|
16
16
|
- !ruby/object:Gem::Dependency
|
|
17
17
|
name: niceogiri
|
|
@@ -499,7 +499,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
499
499
|
version: '0'
|
|
500
500
|
segments:
|
|
501
501
|
- 0
|
|
502
|
-
hash: -
|
|
502
|
+
hash: -2704704399660670461
|
|
503
503
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
504
504
|
none: false
|
|
505
505
|
requirements:
|