spec/punchblock/translator/asterisk/component/output_spec.rb in punchblock-1.9.4 vs spec/punchblock/translator/asterisk/component/output_spec.rb in punchblock-2.0.0.beta1

- old
+ new

@@ -8,11 +8,11 @@ module Component describe Output do include HasMockCallbackConnection let(:media_engine) { nil } - let(:ami_client) { mock('AMI') } + let(:ami_client) { double('AMI') } let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine } let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection } let :original_command do Punchblock::Component::Output.new command_options @@ -23,11 +23,11 @@ say_as(:interpret_as => :cardinal) { 'FOO' } end end let :command_options do - { :ssml => ssml_doc } + { :render_document => {:value => ssml_doc} } end subject { Output.new original_command, mock_call } def expect_answered(value = true) @@ -60,11 +60,11 @@ end let(:command_opts) { {} } let :command_options do - { :ssml => ssml_doc }.merge(command_opts) + { :render_document => {:value => ssml_doc} }.merge(command_opts) end def ssml_with_options(prefix = '', postfix = '') base_doc = ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" } prefix + base_doc + postfix @@ -78,11 +78,11 @@ end it 'should send a complete event when Swift completes' do mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute - original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success + original_command.complete_event(0.1).reason.should 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' } @@ -92,10 +92,20 @@ complete_reason.should be_a Punchblock::Event::Complete::Error complete_reason.details.should == "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' + mock_call.should_receive(:execute_agi_command).and_raise error + subject.execute + complete_reason = original_command.complete_event(0.1).reason + complete_reason.should 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 mock_call.should_receive(:send_progress) @@ -127,12 +137,12 @@ mock_call.should_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 :speech" do - let(:command_opts) { { :interrupt_on => :speech } } + 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.' original_command.response(0.1).should be == error end @@ -172,47 +182,34 @@ end let(:command_opts) { {} } let :command_options do - { :ssml => ssml_doc }.merge(command_opts) + { :render_document => {:value => ssml_doc} }.merge(command_opts) end + let(:synthstatus) { 'OK' } + before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus } + def expect_mrcpsynth_with_options(options) mock_call.should_receive(:execute_agi_command).once.with do |*args| args[0].should be == 'EXEC MRCPSynth' - args[2].should match options + args[1].should match options end.and_return code: 200, result: 1 end before { expect_answered } it "should execute MRCPSynth" do - mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, '').and_return code: 200, result: 1 + mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1 subject.execute end - context "when the SSML document contains commas" do - let :ssml_doc do - RubySpeech::SSML.draw do - string "this, here, is a test" - end - end - - it 'should escape TTS strings containing a comma' do - mock_call.should_receive(:execute_agi_command).once.with do |*args| - args[0].should be == 'EXEC MRCPSynth' - args[1].should match(/this\\, here\\, is a test/) - end.and_return code: 200, result: 1 - subject.execute - end - end - it 'should send a complete event when MRCPSynth completes' do mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute - original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success + original_command.complete_event(0.1).reason.should 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' } @@ -222,29 +219,60 @@ complete_reason.should be_a Punchblock::Event::Complete::Error complete_reason.details.should == "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' + mock_call.should_receive(:execute_agi_command).and_raise error + subject.execute + complete_reason = original_command.complete_event(0.1).reason + complete_reason.should 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 mock_call.should_receive(:send_progress) mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute end end - describe 'ssml' do + context "when the SYNTHSTATUS variable is set to 'ERROR'" do + let(:synthstatus) { 'ERROR' } + + it "should send an error complete event" do + mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1 + subject.execute + complete_reason = original_command.complete_event(0.1).reason + complete_reason.should be_a Punchblock::Event::Complete::Error + complete_reason.details.should == "Terminated due to UniMRCP error" + end + end + + describe 'document' do context 'unset' do - let(:command_opts) { { :ssml => nil } } + 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.' original_command.response(0.1).should be == error end end + + context 'with multiple documents' do + let(:command_opts) { { :render_documents => [{:value => ssml_doc}, {:value => ssml_doc}] } } + it "should return an error and not execute any actions" do + subject.execute + error = ProtocolError.new.setup 'option error', 'Only a single document is supported.' + original_command.response(0.1).should be == error + end + end end describe 'start-offset' do context 'unset' do let(:command_opts) { { :start_offset => nil } } @@ -381,12 +409,12 @@ expect_mrcpsynth_with_options(/i=any/) subject.execute end end - context "set to :speech" do - let(:command_opts) { { :interrupt_on => :speech } } + 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.' original_command.response(0.1).should be == error end @@ -413,33 +441,34 @@ end let(:command_opts) { {} } let :command_options do - { :ssml => ssml_doc }.merge(command_opts) + { :render_document => {:value => ssml_doc} }.merge(command_opts) end let :original_command do Punchblock::Component::Output.new command_options end + let(:playbackstatus) { 'SUCCESS' } + before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus } + describe 'ssml' do context 'unset' do - let(:command_opts) { { :ssml => nil } } + 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.' original_command.response(0.1).should be == error end end context 'with a single audio SSML node' do - let(:audio_filename) { 'http://foo.com/bar.mp3' } - let :command_options do - { - :ssml => RubySpeech::SSML.draw { audio :src => audio_filename } - } + 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 @@ -450,13 +479,23 @@ def mock_call.answered? true end expect_playback subject.execute - original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success + original_command.complete_event(0.1).reason.should 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 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' } mock_call.should_receive(:execute_agi_command).and_raise error @@ -464,18 +503,40 @@ complete_reason = original_command.complete_event(0.1).reason complete_reason.should be_a Punchblock::Event::Complete::Error complete_reason.details.should == "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' + mock_call.should_receive(:execute_agi_command).and_raise error + subject.execute + complete_reason = original_command.complete_event(0.1).reason + complete_reason.should 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 + mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1 + subject.execute + complete_reason = original_command.complete_event(0.1).reason + complete_reason.should be_a Punchblock::Event::Complete::Error + complete_reason.details.should == "Terminated due to playback error" + end + end end context 'with a single text node without spaces' do let(:audio_filename) { 'tt-monkeys' } - let :command_options do - { - :ssml => RubySpeech::SSML.draw { string audio_filename } - } + let :ssml_doc do + RubySpeech::SSML.draw { string audio_filename } end it 'should playback the audio file using Playback' do expect_answered expect_playback @@ -484,11 +545,11 @@ it 'should send a complete event when the file finishes playback' do expect_answered expect_playback subject.execute - original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success + original_command.complete_event(0.1).reason.should 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 @@ -511,11 +572,13 @@ context "with interrupt_on set to something that is not nil" do let(:audio_filename) { 'tt-monkeys' } let :command_options do { - :ssml => RubySpeech::SSML.draw { string audio_filename }, + :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 @@ -525,32 +588,18 @@ end end end end - context 'with a string (not SSML)' do - let :command_options do - { :text => 'Foo Bar' } - 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.' - original_command.response(0.1).should be == error - end - end - context 'with multiple audio SSML nodes' do let(:audio_filename1) { 'http://foo.com/bar.mp3' } let(:audio_filename2) { 'http://foo.com/baz.mp3' } - let :command_options do - { - :ssml => RubySpeech::SSML.draw do - audio :src => audio_filename1 - audio :src => audio_filename2 - end - } + 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('&') @@ -563,25 +612,23 @@ 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 original_command.should_receive(:add_event).once.with do |e| - e.reason.should be_a Punchblock::Component::Output::Complete::Success + e.reason.should be_a Punchblock::Component::Output::Complete::Finish latch.countdown! end subject.execute latch.wait(2).should be_true end end context "with an SSML document containing elements other than <audio/>" do - let :command_options do - { - :ssml => RubySpeech::SSML.draw do - string "Foo Bar" - end - } + 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.' @@ -748,11 +795,11 @@ let(:command_opts) { { :interrupt_on => :any } } before do expect_answered mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename) - subject.wrapped_object.should_receive(:send_success).and_return nil + subject.wrapped_object.should_receive(:send_finish).and_return nil end context "when a DTMF digit is received" do it "sends the correct complete event" do mock_call.async.should_receive :redirect_back @@ -761,11 +808,11 @@ original_command.should_not be_complete send_ami_events_for_dtmf 1 mock_call.async.process_ami_event ami_event sleep 0.2 original_command.should be_complete - reason.should be_a Punchblock::Component::Output::Complete::Success + reason.should be_a Punchblock::Component::Output::Complete::Finish end it "redirects the call back to async AGI" do mock_call.async.should_receive(:redirect_back).once subject.execute @@ -779,11 +826,11 @@ let(:command_opts) { { :interrupt_on => :dtmf } } before do expect_answered mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename) - subject.wrapped_object.should_receive(:send_success).and_return nil + subject.wrapped_object.should_receive(:send_finish).and_return nil end context "when a DTMF digit is received" do it "sends the correct complete event" do mock_call.async.should_receive :redirect_back @@ -792,11 +839,11 @@ original_command.should_not be_complete send_ami_events_for_dtmf 1 mock_call.async.process_ami_event ami_event sleep 0.2 original_command.should be_complete - reason.should be_a Punchblock::Component::Output::Complete::Success + reason.should be_a Punchblock::Component::Output::Complete::Finish end it "redirects the call back to async AGI" do mock_call.async.should_receive(:redirect_back).once subject.execute @@ -804,12 +851,12 @@ send_ami_events_for_dtmf 1 end end end - context "set to :speech" do - let(:command_opts) { { :interrupt_on => :speech } } + 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.' original_command.response(0.1).should be == error end @@ -828,15 +875,18 @@ end let(:command_opts) { {:renderer => :asterisk} } let :command_options do - { :ssml => ssml_doc }.merge(command_opts) + { :render_document => {:value => ssml_doc} }.merge(command_opts) end let :original_command do Punchblock::Component::Output.new command_options end + + let(:playbackstatus) { 'SUCCESS' } + before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus } it "should use the media renderer set and not the platform default" do expect_answered mock_call.should_receive(:execute_agi_command).once.with 'EXEC Playback', audio_filename subject.execute