# encoding: utf-8
require 'spec_helper'
module Punchblock
module Translator
class Asterisk
module Component
describe Output do
include HasMockCallbackConnection
let(:renderer) { nil }
let(:ami_client) { double('AMI') }
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
let :original_command do
Punchblock::Component::Output.new command_options
end
let :ssml_doc do
RubySpeech::SSML.draw do
say_as(:interpret_as => :cardinal) { 'FOO' }
end
end
let(:command_opts) { {} }
let :command_options do
{ :render_document => {:value => ssml_doc}, renderer: renderer }
end
subject { Output.new original_command, mock_call }
def expect_answered(value = true)
allow(mock_call).to receive(:answered?).and_return(value)
end
def expect_mrcpsynth_with_options(options)
expect(mock_call).to receive(:execute_agi_command).once.with { |*args|
expect(args[0]).to eq('EXEC MRCPSynth')
expect(args[1]).to match options
}.and_return code: 200, result: 1
end
describe '#execute' do
before { original_command.request! }
context 'with an invalid renderer' do
let(:renderer) { 'foobar' }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'The renderer foobar is unsupported.'
expect(original_command.response(0.1)).to eq(error)
end
end
context 'with a renderer of :swift' do
let(:renderer) { 'swift' }
let(:audio_filename) { 'http://foo.com/bar.mp3' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename
say_as(:interpret_as => :cardinal) { 'FOO' }
end
end
let :command_options do
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
end
def ssml_with_options(prefix = '', postfix = '')
base_doc = ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
prefix + base_doc + postfix
end
before { expect_answered }
it "should execute Swift" do
expect(mock_call).to receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
subject.execute
end
it 'should send a complete event when Swift completes' do
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "when the channel is gone" do
it "should send an error complete event" do
error = ChannelGoneError.new 'FooBar'
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup
end
end
context "when the call is not answered" do
before { expect_answered false }
it "should send progress" do
expect(mock_call).to receive(:send_progress)
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
end
end
describe 'interrupt_on' do
context "set to nil" do
let(:command_opts) { { :interrupt_on => nil } }
it "should not add interrupt arguments" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
subject.execute
end
end
context "set to :any" do
let(:command_opts) { { :interrupt_on => :any } }
it "should add the interrupt options to the argument" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
subject.execute
end
end
context "set to :dtmf" do
let(:command_opts) { { :interrupt_on => :dtmf } }
it "should add the interrupt options to the argument" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
subject.execute
end
end
context "set to :voice" do
let(:command_opts) { { :interrupt_on => :voice } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'voice' do
context "set to nil" do
let(:command_opts) { { :voice => nil } }
it "should not add a voice at the beginning of the argument" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
subject.execute
end
end
context "set to Leonard" do
let(:command_opts) { { :voice => "Leonard" } }
it "should add a voice at the beginning of the argument" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('Leonard^', '')).and_return code: 200, result: 1
subject.execute
end
end
end
describe "with multiple documents" do
let :first_ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename
end
end
let :second_ssml_doc do
RubySpeech::SSML.draw do
say_as(:interpret_as => :cardinal) { 'FOO' }
end
end
let(:command_opts) { { render_documents: [{value: first_ssml_doc}, {value: second_ssml_doc}] } }
it "executes Swift with a concatenated version of the documents" do
expect(mock_call).to receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
subject.execute
end
end
end
context 'with a renderer of :unimrcp' do
let(:renderer) { :unimrcp }
let(:audio_filename) { 'http://foo.com/bar.mp3' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename
say_as(:interpret_as => :cardinal) { 'FOO' }
end
end
let(:command_opts) { {} }
let :command_options do
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
end
let(:synthstatus) { 'OK' }
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
before { expect_answered }
it "should execute MRCPSynth" do
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
subject.execute
end
it 'should send a complete event when MRCPSynth completes' do
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "when the channel is gone" do
it "should send an error complete event" do
error = ChannelGoneError.new 'FooBar'
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup
end
end
context "when the call is not answered" do
before { expect_answered false }
it "should send progress" do
expect(mock_call).to receive(:send_progress)
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
end
end
context "when the SYNTHSTATUS variable is set to 'ERROR'" do
let(:synthstatus) { 'ERROR' }
it "should send an error complete event" do
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
end
end
describe 'document' do
context 'unset' do
let(:ssml_doc) { nil }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
expect(original_command.response(0.1)).to eq(error)
end
end
context 'with multiple documents' do
let(:command_opts) { { :render_documents => [{:value => ssml_doc}, {:value => ssml_doc}] } }
it "should execute MRCPSynth once with each document" do
param = ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
subject.execute
end
it 'should not execute further output after a stop command' do
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.5
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.2
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
end
end
describe 'start-offset' do
context 'unset' do
let(:command_opts) { { :start_offset => nil } }
it 'should not pass any options to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'set' do
let(:command_opts) { { :start_offset => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'start-paused' do
context 'false' do
let(:command_opts) { { :start_paused => false } }
it 'should not pass any options to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'true' do
let(:command_opts) { { :start_paused => true } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-interval' do
context 'unset' do
let(:command_opts) { { :repeat_interval => nil } }
it 'should not pass any options to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_interval => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-times' do
context 'unset' do
let(:command_opts) { { :repeat_times => nil } }
it 'should not pass any options to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_times => 2 } }
it "should render the specified number of times" do
2.times { expect_mrcpsynth_with_options(//) }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context 'to 0' do
let(:command_opts) { { :repeat_times => 0 } }
it "should render 10,000 the specified number of times" do
expect_answered
1000.times { expect_mrcpsynth_with_options(//) }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
end
it 'should not execute further output after a stop command' do
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
end
end
describe 'max-time' do
context 'unset' do
let(:command_opts) { { :max_time => nil } }
it 'should not pass any options to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'set' do
let(:command_opts) { { :max_time => 30 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'voice' do
context 'unset' do
let(:command_opts) { { :voice => nil } }
it 'should not pass the v option to MRCPSynth' do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context 'set' do
let(:command_opts) { { :voice => 'alison' } }
it 'should pass the v option to MRCPSynth' do
expect_mrcpsynth_with_options(/v=alison/)
subject.execute
end
end
end
describe 'interrupt_on' do
context "set to nil" do
let(:command_opts) { { :interrupt_on => nil } }
it "should not pass the i option to MRCPSynth" do
expect_mrcpsynth_with_options(//)
subject.execute
end
end
context "set to :any" do
let(:command_opts) { { :interrupt_on => :any } }
it "should pass the i option to MRCPSynth" do
expect_mrcpsynth_with_options(/i=any/)
subject.execute
end
end
context "set to :dtmf" do
let(:command_opts) { { :interrupt_on => :dtmf } }
it "should pass the i option to MRCPSynth" do
expect_mrcpsynth_with_options(/i=any/)
subject.execute
end
end
context "set to :voice" do
let(:command_opts) { { :interrupt_on => :voice } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
end
[:asterisk, nil].each do |renderer|
context "with a renderer of #{renderer.inspect}" do
def expect_playback(filename = audio_filename)
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', filename).and_return code: 200
end
def expect_playback_noanswer
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
end
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename
end
end
let(:command_opts) { {} }
let :command_options do
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
end
let :original_command do
Punchblock::Component::Output.new command_options
end
let(:playbackstatus) { 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
describe 'ssml' do
context 'unset' do
let(:ssml_doc) { nil }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
expect(original_command.response(0.1)).to eq(error)
end
end
context 'with a single audio SSML node' do
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw { audio :src => audio_filename }
end
it 'should playback the audio file using Playback' do
expect_answered
expect_playback
subject.execute
end
it 'should send a complete event when the file finishes playback' do
def mock_call.answered?
true
end
expect_playback
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when the audio filename is prefixed by file://" do
let(:audio_filename) { 'file://tt-monkeys' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'tt-monkeys'
subject.execute
end
end
context "when the audio filename has an extension" do
let(:audio_filename) { 'tt-monkeys.wav' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'tt-monkeys'
subject.execute
end
context "when there are other dots in the filename" do
let(:audio_filename) { 'blue.tt-monkeys.wav' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'blue.tt-monkeys'
subject.execute
end
context "and no file extension" do
let(:audio_filename) { '/var/lib/gems/1.9.1/gems/myapp-1.0.0/prompts/greeting' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback '/var/lib/gems/1.9.1/gems/myapp-1.0.0/prompts/greeting'
subject.execute
end
end
end
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
expect_answered
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "when the channel is gone" do
it "should send an error complete event" do
expect_answered
error = ChannelGoneError.new 'FooBar'
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup
end
end
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
let(:playbackstatus) { 'FAILED' }
it "should send an error complete event" do
expect_answered
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to playback error")
end
end
end
context 'with a single text node without spaces' do
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw { string audio_filename }
end
it 'should playback the audio file using Playback' do
expect_answered
expect_playback
subject.execute
end
it 'should send a complete event when the file finishes playback' do
expect_answered
expect_playback
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
expect_answered
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "with early media playback" do
it "should play the file with Playback" do
expect_answered false
expect_playback_noanswer
expect(mock_call).to receive(:send_progress)
subject.execute
end
context "with interrupt_on set to something that is not nil" do
let(:audio_filename) { 'tt-monkeys' }
let :command_options do
{
:render_document => {
:value => RubySpeech::SSML.draw { string audio_filename },
},
:interrupt_on => :any
}
end
it "should return an error when the output is interruptible and it is early media" do
expect_answered false
error = ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
subject.execute
expect(original_command.response(0.1)).to eq(error)
end
end
end
end
context 'with multiple audio SSML nodes' do
let(:audio_filename1) { 'foo' }
let(:audio_filename2) { 'bar' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename1
audio :src => audio_filename2
end
end
it 'should playback all audio files using Playback' do
latch = CountDownLatch.new 2
expect_playback [audio_filename1, audio_filename2].join('&')
expect_answered
subject.execute
latch.wait 2
sleep 2
end
it 'should send a complete event after the final file has finished playback' do
expect_answered
expect_playback [audio_filename1, audio_filename2].join('&')
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
subject.execute
expect(latch.wait(2)).to be_true
end
end
context "with an SSML document containing elements other than " do
let :ssml_doc do
RubySpeech::SSML.draw do
string "Foo Bar"
end
end
it "should return an unrenderable document error" do
subject.execute
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.'
expect(original_command.response(0.1)).to eq(error)
end
end
context 'with multiple documents' do
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
it "should render each document in turn using a Playback per document" do
expect_answered
2.times { expect_playback }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it 'should not execute further output after a stop command' do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
let(:playbackstatus) { 'FAILED' }
it "should terminate playback and send an error complete event" do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.and_return code: 200, result: 1
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to playback error")
end
end
end
end
describe 'start-offset' do
context 'unset' do
let(:command_opts) { { :start_offset => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :start_offset => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'start-paused' do
context 'false' do
let(:command_opts) { { :start_paused => false } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'true' do
let(:command_opts) { { :start_paused => true } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-interval' do
context 'unset' do
let(:command_opts) { { :repeat_interval => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_interval => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-times' do
context 'unset' do
let(:command_opts) { { :repeat_times => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_times => 2 } }
it "should render the specified number of times" do
expect_answered
2.times { expect_playback }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context 'to 0' do
let(:command_opts) { { :repeat_times => 0 } }
it "should render 10,000 the specified number of times" do
expect_answered
1000.times { expect_playback }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
end
it 'should not execute further output after a stop command' do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
end
end
describe 'max-time' do
context 'unset' do
let(:command_opts) { { :max_time => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :max_time => 30 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'voice' do
context 'unset' do
let(:command_opts) { { :voice => nil } }
it 'should not pass the v option to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :voice => 'alison' } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'interrupt_on' do
def ami_event_for_dtmf(digit, position)
RubyAMI::Event.new 'DTMF',
'Digit' => digit.to_s,
'Start' => position == :start ? 'Yes' : 'No',
'End' => position == :end ? 'Yes' : 'No'
end
def send_ami_events_for_dtmf(digit)
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
end
let(:reason) { original_command.complete_event(5).reason }
let(:channel) { "SIP/1234-00000000" }
let :ami_event do
RubyAMI::Event.new 'AsyncAGI',
'SubEvent' => "Start",
'Channel' => channel,
'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"
end
context "set to nil" do
let(:command_opts) { { :interrupt_on => nil } }
it "does not redirect the call" do
expect_answered
expect_playback
expect(mock_call).to receive(:redirect_back).never
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
context "set to :any" do
let(:command_opts) { { :interrupt_on => :any } }
before do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
expect(subject).to receive(:send_finish).and_return nil
end
context "when a DTMF digit is received" do
it "sends the correct complete event" do
expect(mock_call).to receive :redirect_back
subject.execute
expect(original_command.response(0.1)).to be_a Ref
expect(original_command).not_to be_complete
send_ami_events_for_dtmf 1
mock_call.process_ami_event ami_event
sleep 0.2
expect(original_command).to be_complete
expect(reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it "redirects the call back to async AGI" do
expect(mock_call).to receive(:redirect_back).once
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
end
context "set to :dtmf" do
let(:command_opts) { { :interrupt_on => :dtmf } }
before do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
expect(subject).to receive(:send_finish).and_return nil
end
context "when a DTMF digit is received" do
it "sends the correct complete event" do
expect(mock_call).to receive :redirect_back
subject.execute
expect(original_command.response(0.1)).to be_a Ref
expect(original_command).not_to be_complete
send_ami_events_for_dtmf 1
mock_call.process_ami_event ami_event
sleep 0.2
expect(original_command).to be_complete
expect(reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it "redirects the call back to async AGI" do
expect(mock_call).to receive(:redirect_back).once
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
end
context "set to :voice" do
let(:command_opts) { { :interrupt_on => :voice } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
end
end
context "with a renderer of :native_or_unimrcp" do
def expect_playback(filename = audio_filename)
expect(mock_call).to receive(:execute_agi_command).ordered.once.with('EXEC Playback', filename).and_return code: 200
end
def expect_playback_noanswer
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
end
def expect_mrcpsynth(doc = ssml_doc)
expect(mock_call).to receive(:execute_agi_command).ordered.once.with('EXEC MRCPSynth', ["\"#{doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
end
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename do
string "Foobar"
end
end
end
let(:command_opts) { {} }
let :command_options do
{ :render_document => {:value => ssml_doc}, renderer: :native_or_unimrcp }.merge(command_opts)
end
let :original_command do
Punchblock::Component::Output.new command_options
end
let(:playbackstatus) { 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
describe 'ssml' do
context 'unset' do
let(:ssml_doc) { nil }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
expect(original_command.response(0.1)).to eq(error)
end
end
context 'with a single audio SSML node' do
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw language: 'pt-BR' do
audio :src => audio_filename do
voice name: 'frank' do
string "Hello world"
end
end
end
end
it 'should playback the audio file using Playback' do
expect_answered
expect_playback
subject.execute
end
it 'should send a complete event when the file finishes playback' do
def mock_call.answered?
true
end
expect_playback
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when the audio filename is prefixed by file://" do
let(:audio_filename) { 'file://tt-monkeys' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'tt-monkeys'
subject.execute
end
end
context "when the audio filename has an extension" do
let(:audio_filename) { 'tt-monkeys.wav' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'tt-monkeys'
subject.execute
end
context "when there are other dots in the filename" do
let(:audio_filename) { 'blue.tt-monkeys.wav' }
it 'should playback the audio file using Playback' do
expect_answered
expect_playback 'blue.tt-monkeys'
subject.execute
end
end
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
expect_answered
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "when the channel is gone" do
it "should send an error complete event" do
expect_answered
error = ChannelGoneError.new 'FooBar'
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup
end
end
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
let(:playbackstatus) { 'FAILED' }
let(:synthstatus) { 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
let :fallback_doc do
RubySpeech::SSML.draw language: 'pt-BR' do
voice name: 'frank' do
string "Hello world"
end
end
end
it "should attempt to render the children of the audio tag via MRCP and then send a complete event" do
expect_answered
expect_playback
expect_mrcpsynth fallback_doc
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
let(:synthstatus) { 'ERROR' }
it "should send an error complete event" do
expect_answered
expect_playback
expect_mrcpsynth fallback_doc
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
end
end
end
end
context 'with a single text node without spaces' do
let(:audio_filename) { 'tt-monkeys' }
let :ssml_doc do
RubySpeech::SSML.draw { string audio_filename }
end
it 'should playback the audio file using Playback' do
expect_answered
expect_playback
subject.execute
end
it 'should send a complete event when the file finishes playback' do
expect_answered
expect_playback
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "when we get a RubyAMI Error" do
it "should send an error complete event" do
expect_answered
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
expect(mock_call).to receive(:execute_agi_command).and_raise error
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
end
end
context "with early media playback" do
it "should play the file with Playback" do
expect_answered false
expect_playback_noanswer
expect(mock_call).to receive(:send_progress)
subject.execute
end
context "with interrupt_on set to something that is not nil" do
let(:audio_filename) { 'tt-monkeys' }
let :command_options do
{
:render_document => {
:value => RubySpeech::SSML.draw { string audio_filename },
},
:interrupt_on => :any
}
end
it "should return an error when the output is interruptible and it is early media" do
expect_answered false
error = ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
subject.execute
expect(original_command.response(0.1)).to eq(error)
end
end
end
end
context 'with multiple audio SSML nodes' do
let(:audio_filename1) { 'foo' }
let(:audio_filename2) { 'bar' }
let(:audio_filename3) { 'baz' }
let :ssml_doc do
RubySpeech::SSML.draw do
audio :src => audio_filename1 do
string "Fallback 1"
end
audio :src => audio_filename2 do
string "Fallback 2"
end
audio :src => audio_filename3 do
string "Fallback 3"
end
end
end
it 'should playback all audio files using Playback' do
latch = CountDownLatch.new 2
expect_playback audio_filename1
expect_playback audio_filename2
expect_playback audio_filename3
expect_answered
subject.execute
latch.wait 2
sleep 2
end
it 'should send a complete event after the final file has finished playback' do
expect_answered
expect_playback audio_filename1
expect_playback audio_filename2
expect_playback audio_filename3
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
subject.execute
expect(latch.wait(2)).to be_true
end
it 'should not execute further output after a stop command' do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
let(:synthstatus) { 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
let :fallback_doc do
RubySpeech::SSML.draw do
string "Fallback 2"
end
end
it "should attempt to render the document via MRCP and then send a complete event" do
expect_answered
expect_playback audio_filename1
expect_playback audio_filename2
expect_mrcpsynth fallback_doc
expect_playback audio_filename3
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
let(:synthstatus) { 'ERROR' }
it "should terminate playback and send an error complete event" do
expect_answered
expect_playback audio_filename1
expect_playback audio_filename2
expect_mrcpsynth fallback_doc
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
end
end
end
end
context "with an SSML document containing top-level elements other than " do
let :ssml_doc do
RubySpeech::SSML.draw do
voice name: 'Paul' do
string "Foo Bar"
end
end
end
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
it "should attempt to render the document via MRCP and then send a complete event" do
expect_answered
expect_mrcpsynth ssml_doc
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
end
context "with mixed TTS and audio tags" do
let :ssml_doc do
RubySpeech::SSML.draw do
voice name: 'Paul' do
string "Foo Bar"
end
audio src: 'tt-monkeys'
voice name: 'Frank' do
string "Doo Dah"
end
string 'tt-weasels'
end
end
let :first_doc do
RubySpeech::SSML.draw do
voice name: 'Paul' do
string "Foo Bar"
end
end
end
let :second_doc do
RubySpeech::SSML.draw do
voice name: 'Frank' do
string "Doo Dah"
end
end
end
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
it "should attempt to render the document via MRCP and then send a complete event" do
expect_answered
expect_mrcpsynth first_doc
expect_playback 'tt-monkeys'
expect_mrcpsynth second_doc
expect_playback 'tt-weasels'
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
end
context 'with multiple documents' do
let :second_ssml_doc do
RubySpeech::SSML.draw do
audio :src => 'two.wav' do
string "Bazzz"
end
end
end
let :third_ssml_doc do
RubySpeech::SSML.draw do
audio :src => 'three.wav' do
string "Barrrr"
end
end
end
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: second_ssml_doc}, {value: third_ssml_doc}] } }
it "should render each document in turn using a Playback per document" do
expect_answered
expect_playback
expect_playback 'two'
expect_playback 'three'
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it 'should not execute further output after a stop command' do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
let(:synthstatus) { 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
let :fallback_doc do
RubySpeech::SSML.draw do
string "Bazzz"
end
end
it "should attempt to render the document via MRCP and then send a complete event" do
expect_answered
expect_playback
expect_playback 'two'
expect_mrcpsynth fallback_doc
expect_playback 'three'
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
let(:synthstatus) { 'ERROR' }
it "should terminate playback and send an error complete event" do
expect_answered
expect_playback
expect_playback 'two'
expect_mrcpsynth fallback_doc
subject.execute
complete_reason = original_command.complete_event(0.1).reason
expect(complete_reason).to be_a Punchblock::Event::Complete::Error
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
end
end
end
end
end
describe 'start-offset' do
context 'unset' do
let(:command_opts) { { :start_offset => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :start_offset => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'start-paused' do
context 'false' do
let(:command_opts) { { :start_paused => false } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'true' do
let(:command_opts) { { :start_paused => true } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-interval' do
context 'unset' do
let(:command_opts) { { :repeat_interval => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_interval => 10 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'repeat-times' do
context 'unset' do
let(:command_opts) { { :repeat_times => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :repeat_times => 2 } }
it "should render the specified number of times" do
expect_answered
2.times { expect_playback }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
context 'to 0' do
let(:command_opts) { { :repeat_times => 0 } }
it "should render 10,000 the specified number of times" do
expect_answered
1000.times { expect_playback }
subject.execute
expect(original_command.complete_event(0.1).reason).to be_a Punchblock::Component::Output::Complete::Finish
end
end
it 'should not execute further output after a stop command' do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.ordered do
sleep 0.2
end
latch = CountDownLatch.new 1
expect(original_command).to receive(:add_event).once.with { |e|
expect(e.reason).to be_a Punchblock::Component::Output::Complete::Finish
latch.countdown!
}
Celluloid::Future.new { subject.execute }
sleep 0.1
expect(mock_call).to receive(:redirect_back).ordered
stop_command = Punchblock::Component::Stop.new
stop_command.request!
subject.execute_command stop_command
expect(latch.wait(2)).to be_true
end
end
end
describe 'max-time' do
context 'unset' do
let(:command_opts) { { :max_time => nil } }
it 'should not pass any options to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :max_time => 30 } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'voice' do
context 'unset' do
let(:command_opts) { { :voice => nil } }
it 'should not pass the v option to Playback' do
expect_answered
expect_playback
subject.execute
end
end
context 'set' do
let(:command_opts) { { :voice => 'alison' } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
describe 'interrupt_on' do
def ami_event_for_dtmf(digit, position)
RubyAMI::Event.new 'DTMF',
'Digit' => digit.to_s,
'Start' => position == :start ? 'Yes' : 'No',
'End' => position == :end ? 'Yes' : 'No'
end
def send_ami_events_for_dtmf(digit)
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
end
let(:reason) { original_command.complete_event(5).reason }
let(:channel) { "SIP/1234-00000000" }
let :ami_event do
RubyAMI::Event.new 'AsyncAGI',
'SubEvent' => "Start",
'Channel' => channel,
'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"
end
context "set to nil" do
let(:command_opts) { { :interrupt_on => nil } }
it "does not redirect the call" do
expect_answered
expect_playback
expect(mock_call).to receive(:redirect_back).never
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
context "set to :any" do
let(:command_opts) { { :interrupt_on => :any } }
before do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
expect(subject).to receive(:send_finish).and_return nil
end
context "when a DTMF digit is received" do
it "sends the correct complete event" do
expect(mock_call).to receive :redirect_back
subject.execute
expect(original_command.response(0.1)).to be_a Ref
expect(original_command).not_to be_complete
send_ami_events_for_dtmf 1
mock_call.process_ami_event ami_event
sleep 0.2
expect(original_command).to be_complete
expect(reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it "redirects the call back to async AGI" do
expect(mock_call).to receive(:redirect_back).once
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
end
context "set to :dtmf" do
let(:command_opts) { { :interrupt_on => :dtmf } }
before do
expect_answered
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
expect(subject).to receive(:send_finish).and_return nil
end
context "when a DTMF digit is received" do
it "sends the correct complete event" do
expect(mock_call).to receive :redirect_back
subject.execute
expect(original_command.response(0.1)).to be_a Ref
expect(original_command).not_to be_complete
send_ami_events_for_dtmf 1
mock_call.process_ami_event ami_event
sleep 0.2
expect(original_command).to be_complete
expect(reason).to be_a Punchblock::Component::Output::Complete::Finish
end
it "redirects the call back to async AGI" do
expect(mock_call).to receive(:redirect_back).once
subject.execute
expect(original_command.response(0.1)).to be_a Ref
send_ami_events_for_dtmf 1
end
end
end
context "set to :voice" do
let(:command_opts) { { :interrupt_on => :voice } }
it "should return an error and not execute any actions" do
subject.execute
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
expect(original_command.response(0.1)).to eq(error)
end
end
end
end
end
describe "#execute_command" do
context "with a command it does not understand" do
let(:command) { Punchblock::Component::Output::Pause.new }
before { command.request! }
it "returns a ProtocolError response" do
subject.execute_command command
expect(command.response(0.1)).to be_a ProtocolError
end
end
context "with a Stop command" do
let(:command) { Punchblock::Component::Stop.new }
let(:reason) { original_command.complete_event(5).reason }
let(:channel) { "SIP/1234-00000000" }
let :ami_event do
RubyAMI::Event.new 'AsyncAGI',
'SubEvent' => "Start",
'Channel' => channel,
'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"
end
before do
command.request!
original_command.request!
original_command.execute!
end
it "sets the command response to true" do
expect(mock_call).to receive(:redirect_back)
subject.execute_command command
expect(command.response(0.1)).to eq(true)
end
it "sends the correct complete event" do
expect(mock_call).to receive(:redirect_back)
subject.execute_command command
expect(original_command).not_to be_complete
mock_call.process_ami_event ami_event
expect(reason).to be_a Punchblock::Event::Complete::Stop
expect(original_command).to be_complete
end
it "redirects the call by unjoining it" do
expect(mock_call).to receive(:redirect_back)
subject.execute_command command
end
end
end
end
end
end
end
end