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

- old
+ new

@@ -5,12 +5,12 @@ module Punchblock module Translator class Asterisk describe Call do let(:channel) { 'SIP/foo' } - let(:ami_client) { stub('AMI Client').as_null_object } - let(:connection) { stub('connection').as_null_object } + let(:ami_client) { double('AMI Client').as_null_object } + let(:connection) { double('connection').as_null_object } let(:translator) { Asterisk.new ami_client, connection } let(:agi_env) do { :agi_request => 'async', :agi_channel => 'SIP/1234-00000000', @@ -35,30 +35,30 @@ } end let :sip_headers do { - :x_agi_request => 'async', - :x_agi_channel => 'SIP/1234-00000000', - :x_agi_language => 'en', - :x_agi_type => 'SIP', - :x_agi_uniqueid => '1320835995.0', - :x_agi_version => '1.8.4.1', - :x_agi_callerid => '5678', - :x_agi_calleridname => 'Jane Smith', - :x_agi_callingpres => '0', - :x_agi_callingani2 => '0', - :x_agi_callington => '0', - :x_agi_callingtns => '0', - :x_agi_dnid => 'unknown', - :x_agi_rdnis => 'unknown', - :x_agi_context => 'default', - :x_agi_extension => '1000', - :x_agi_priority => '1', - :x_agi_enhanced => '0.0', - :x_agi_accountcode => '', - :x_agi_threadid => '4366221312' + 'X-agi_request' => 'async', + 'X-agi_channel' => 'SIP/1234-00000000', + 'X-agi_language' => 'en', + 'X-agi_type' => 'SIP', + 'X-agi_uniqueid' => '1320835995.0', + 'X-agi_version' => '1.8.4.1', + 'X-agi_callerid' => '5678', + 'X-agi_calleridname' => 'Jane Smith', + 'X-agi_callingpres' => '0', + 'X-agi_callingani2' => '0', + 'X-agi_callington' => '0', + 'X-agi_callingtns' => '0', + 'X-agi_dnid' => 'unknown', + 'X-agi_rdnis' => 'unknown', + 'X-agi_context' => 'default', + 'X-agi_extension' => '1000', + 'X-agi_priority' => '1', + 'X-agi_enhanced' => '0.0', + 'X-agi_accountcode' => '', + 'X-agi_threadid' => '4366221312' } end subject { Call.new channel, translator, ami_client, connection, agi_env } @@ -78,11 +78,11 @@ end describe '#register_component' do it 'should make the component accessible by ID' do component_id = 'abc123' - component = mock 'Translator::Asterisk::Component', :id => component_id + component = double 'Translator::Asterisk::Component', :id => component_id subject.register_component component subject.component_with_id(component_id).should be component end end @@ -294,56 +294,76 @@ let(:cause_txt) { 'Normal Clearing' } it "should cause the actor to be terminated" do translator.should_receive(:handle_pb_event).twice subject.process_ami_event ami_event - sleep 5.5 subject.should_not be_alive end it "de-registers the call from the translator" do translator.stub :handle_pb_event - translator.should_receive(:deregister_call).once.with(subject) + translator.should_receive(:deregister_call).once.with(subject.id, subject.channel) subject.process_ami_event ami_event end it "should cause all components to send complete events before sending end event" do subject.stub :send_progress - comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf + comp_command = Punchblock::Component::Input.new :grammar => {:value => RubySpeech::GRXML.draw(root: 'foo') { rule id: 'foo' }}, :mode => :dtmf comp_command.request! component = subject.execute_command comp_command comp_command.response(0.1).should be_a Ref expected_complete_event = Punchblock::Event::Complete.new :target_call_id => subject.id, :component_id => component.id expected_complete_event.reason = Punchblock::Event::Complete::Hangup.new - expected_end_event = Punchblock::Event::End.new :reason => :hangup, :target_call_id => subject.id + expected_end_event = Punchblock::Event::End.new :reason => :hangup, platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with(expected_complete_event).once.ordered translator.should_receive(:handle_pb_event).with(expected_end_event).once.ordered subject.process_ami_event ami_event end it "should not allow commands to be executed while components are shutting down" do + call_id = subject.id + subject.stub :send_progress - comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf + comp_command = Punchblock::Component::Input.new :grammar => {:value => RubySpeech::GRXML.draw(root: 'foo') { rule id: 'foo' }}, :mode => :dtmf comp_command.request! component = subject.execute_command comp_command comp_command.response(0.1).should be_a Ref subject.async.process_ami_event ami_event - comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf + comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar root="foo"><rule id="foo"/></grammar>'}, :mode => :dtmf comp_command.request! subject.execute_command comp_command - comp_command.response(0.1).should == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) + comp_command.response(0.1).should == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id) end + context "after processing a hangup command" do + let(:command) { Command::Hangup.new } + + before do + command.request! + subject.execute_command command + end + + it 'should send an end (hangup_command) event to the translator' do + expected_end_event = Punchblock::Event::End.new :reason => :hangup_command, + platform_code: cause, + :target_call_id => subject.id + translator.should_receive(:handle_pb_event).with expected_end_event + + subject.process_ami_event ami_event + end + end + context "with an undefined cause" do let(:cause) { '0' } let(:cause_txt) { 'Undefined' } it 'should send an end (hangup) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :hangup, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end @@ -352,10 +372,11 @@ let(:cause) { '16' } let(:cause_txt) { 'Normal Clearing' } it 'should send an end (hangup) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :hangup, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end @@ -364,10 +385,11 @@ let(:cause) { '17' } let(:cause_txt) { 'User Busy' } it 'should send an end (busy) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :busy, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end @@ -380,10 +402,11 @@ let(:cause) { cause.to_s } let(:cause_txt) { cause_txt } it 'should send an end (timeout) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :timeout, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end @@ -398,10 +421,11 @@ let(:cause) { cause.to_s } let(:cause_txt) { cause_txt } it 'should send an end (reject) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :reject, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end @@ -450,20 +474,21 @@ let(:cause) { cause.to_s } let(:cause_txt) { cause_txt } it 'should send an end (error) event to the translator' do expected_end_event = Punchblock::Event::End.new :reason => :error, + platform_code: cause, :target_call_id => subject.id translator.should_receive(:handle_pb_event).with expected_end_event subject.process_ami_event ami_event end end end end context 'with an event for a known AGI command component' do - let(:mock_component_node) { mock 'Punchblock::Component::Asterisk::AGI::Command', :name => 'EXEC ANSWER', :params_array => [] } + let(:mock_component_node) { Punchblock::Component::Asterisk::AGI::Command.new name: 'EXEC ANSWER', params: [] } let :component do Component::Asterisk::AGICommand.new mock_component_node, subject end let(:ami_event) do @@ -590,11 +615,11 @@ 'End' => 'No', 'Uniqueid' => "1320842458.8", 'Channel' => "SIP/1234-00000000" end - let(:response) { mock 'Response' } + let(:response) { double 'Response' } it 'should execute the handler' do response.should_receive(:call).once.with ami_event subject.register_handler :ami, :name => 'DTMF' do |event| response.call event @@ -619,11 +644,11 @@ Call.new other_channel, translator, ami_client, connection end let(:other_call_id) { other_call.id } let :command do - Punchblock::Command::Join.new :call_id => other_call_id + Punchblock::Command::Join.new call_uri: other_call_id end before do translator.register_call other_call command.request! @@ -700,14 +725,12 @@ context "of state 'Link'" do let(:state) { 'Link' } let :expected_joined do - Punchblock::Event::Joined.new.tap do |joined| - joined.target_call_id = subject.id - joined.call_id = other_call_id - end + Punchblock::Event::Joined.new target_call_id: subject.id, + call_uri: other_call_id end it 'sends the Joined event when the call is the first channel' do translator.should_receive(:handle_pb_event).with expected_joined subject.process_ami_event ami_event @@ -721,14 +744,12 @@ context "of state 'Unlink'" do let(:state) { 'Unlink' } let :expected_unjoined do - Punchblock::Event::Unjoined.new.tap do |joined| - joined.target_call_id = subject.id - joined.call_id = other_call_id - end + Punchblock::Event::Unjoined.new target_call_id: subject.id, + call_uri: other_call_id end it 'sends the Unjoined event when the call is the first channel' do translator.should_receive(:handle_pb_event).with expected_unjoined subject.process_ami_event ami_event @@ -775,14 +796,12 @@ translator.should_receive(:call_for_channel).with(other_channel).and_return(other_call) other_call.should_receive(:id).and_return other_call_id end let :expected_unjoined do - Punchblock::Event::Unjoined.new.tap do |joined| - joined.target_call_id = subject.id - joined.call_id = other_call_id - end + Punchblock::Event::Unjoined.new target_call_id: subject.id, + call_uri: other_call_id end it 'sends the Unjoined event when the call is the first channel' do translator.should_receive(:handle_pb_event).with expected_unjoined subject.process_ami_event ami_event @@ -819,18 +838,18 @@ 'Cause-txt' => "Unknown", 'Channel' => channel end let :expected_pb_event do - Event::Asterisk::AMI::Event.new :name => 'Foo', - :attributes => { :channel => channel, - :uniqueid => "1320842458.8", - :calleridnum => "5678", - :calleridname => "Jane Smith", - :cause => "0", - :'cause-txt' => "Unknown"}, - :target_call_id => subject.id + Event::Asterisk::AMI::Event.new name: 'Foo', + headers: { 'Channel' => channel, + 'Uniqueid' => "1320842458.8", + 'Calleridnum' => "5678", + 'Calleridname' => "Jane Smith", + 'Cause' => "0", + 'Cause-txt' => "Unknown"}, + target_call_id: subject.id end it 'sends the AMI event to the connection as a PB event' do translator.should_receive(:handle_pb_event).with expected_pb_event subject.process_ami_event ami_event @@ -861,12 +880,12 @@ it "should return an error with the message" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup('error', message, subject.id) end - context "with message 'No such channel'" do - let(:message) { 'No such channel' } + context "because the channel is gone" do + let(:error) { ChannelGoneError } it "should return an :item_not_found event for the call" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) end @@ -882,13 +901,13 @@ subject.wrapped_object.should_receive(:execute_agi_command).with('EXEC Busy').and_return code: 200 subject.execute_command command command.response(0.5).should be true end - it "with a :decline reason should send an EXEC Busy AGI command and set the command's response" do + it "with a :decline reason should send a Hangup AMI command (cause 21) and set the command's response" do command.reason = :decline - subject.wrapped_object.should_receive(:execute_agi_command).with('EXEC Busy').and_return code: 200 + ami_client.should_receive(:send_action).once.with('Hangup', 'Channel' => channel, 'Cause' => 21).and_return RubyAMI::Response.new subject.execute_command command command.response(0.5).should be true end it "with an :error reason should send an EXEC Congestion AGI command and set the command's response" do @@ -907,12 +926,12 @@ it "should return an error with the message" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup('error', message, subject.id) end - context "with message 'No such channel'" do - let(:message) { 'No such channel' } + context "because the channel is gone" do + let(:error) { ChannelGoneError } it "should return an :item_not_found event for the call" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) end @@ -933,11 +952,11 @@ subject.wrapped_object.should_receive(:execute_agi_command) subject.execute_command command subject.should be_answered end - context "when the AMI commannd raises an error" do + context "when the AMI command raises an error" do let(:message) { 'Some error' } let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } } before { subject.wrapped_object.should_receive(:execute_agi_command).and_raise error } @@ -949,12 +968,12 @@ it "should not be answered" do subject.execute_command command subject.should_not be_answered end - context "with message 'No such channel'" do - let(:message) { 'No such channel' } + context "because the channel is gone" do + let(:error) { ChannelGoneError } it "should return an :item_not_found event for the call" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) end @@ -980,21 +999,155 @@ it "should return an error with the message" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup('error', message, subject.id) end - context "with message 'No such channel'" do + context "which is 'No such channel'" do let(:message) { 'No such channel' } it "should return an :item_not_found event for the call" do subject.execute_command command command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) end end + + context "which is 'Channel SIP/nosuchchannel does not exist.'" do + let(:message) { 'Channel SIP/nosuchchannel does not exist.' } + + it "should return an :item_not_found event for the call" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) + end + end end end + context "with a join command" do + let(:other_call_id) { "abc123" } + let(:other_channel) { 'SIP/bar' } + let(:other_translator) { double('Translator::Asterisk').as_null_object } + + let :other_call do + Call.new other_channel, other_translator, ami_client, connection + end + + let :command do + Punchblock::Command::Join.new call_uri: other_call_id + end + + before { translator.should_receive(:call_with_id).with(other_call_id).and_return(other_call) } + + it "executes the proper dialplan Bridge application" do + subject.wrapped_object.should_receive(:execute_agi_command).with('EXEC Bridge', other_channel).and_return code: 200 + subject.execute_command command + end + + context "when the AMI command raises an error" do + let(:message) { 'Some error' } + let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } } + + before { subject.wrapped_object.should_receive(:execute_agi_command).and_raise error } + + it "should return an error with the message" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup('error', message, subject.id) + end + + it "should not be answered" do + subject.execute_command command + subject.should_not be_answered + end + + context "because the channel is gone" do + let(:error) { ChannelGoneError } + + it "should return an :item_not_found event for the call" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) + end + end + end + end + + context "with an unjoin command" do + let(:other_call_id) { "abc123" } + let(:other_channel) { 'SIP/bar' } + + let :other_call do + Call.new other_channel, translator, ami_client, connection + end + + let :command do + Punchblock::Command::Unjoin.new call_uri: other_call_id + end + + it "executes the unjoin through redirection" do + translator.should_receive(:call_with_id).with(other_call_id).and_return(nil) + + ami_client.should_receive(:send_action).once.with("Redirect", + 'Channel' => channel, + 'Exten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, + 'Priority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, + 'Context' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT, + ).and_return RubyAMI::Response.new + + subject.execute_command command + + command.response(1).should be_true + end + + it "executes the unjoin through redirection, on the subject call and the other call" do + translator.should_receive(:call_with_id).with(other_call_id).and_return(other_call) + + ami_client.should_receive(:send_action).once.with("Redirect", + 'Channel' => channel, + 'Exten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, + 'Priority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, + 'Context' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT, + 'ExtraChannel' => other_channel, + 'ExtraExten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, + 'ExtraPriority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, + 'ExtraContext' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT + ).and_return RubyAMI::Response.new + + subject.execute_command command + end + + context "when the AMI commannd raises an error" do + let(:message) { 'Some error' } + let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } } + + before do + translator.should_receive(:call_with_id).with(other_call_id).and_return(nil) + ami_client.should_receive(:send_action).and_raise error + end + + it "should return an error with the message" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup('error', message, subject.id) + end + + context "which is 'No such channel'" do + let(:message) { 'No such channel' } + + it "should return an :item_not_found event for the call" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) + end + end + + context "which is 'Channel SIP/nosuchchannel does not exist.'" do + let(:message) { 'Channel SIP/nosuchchannel does not exist.' } + + it "should return an :item_not_found event for the call" do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id) + end + end + end + end + context 'with an AGI command component' do let :command do Punchblock::Component::Asterisk::AGI::Command.new :name => 'Answer' end @@ -1033,10 +1186,85 @@ mock_action.async.should_receive(:execute).once subject.execute_command command end end + context 'with a Prompt component' do + def grxml_doc(mode = :dtmf) + RubySpeech::GRXML.draw :mode => mode.to_s, :root => 'digits' do + rule id: 'digits' do + one_of do + 0.upto(1) { |d| item { d.to_s } } + end + end + end + end + + let :command do + Punchblock::Component::Prompt.new( + { + render_document: { + content_type: 'text/uri-list', + value: ['http://example.com/hello.mp3'] + }, + renderer: renderer + }, + { + grammar: { + value: grxml_doc, + content_type: 'application/srgs+xml' + }, + recognizer: recognizer + }) + end + + let(:mock_action) { Translator::Asterisk::Component::MRCPPrompt.new(command, subject) } + + context "when the recognizer is unimrcp and the renderer is unimrcp" do + let(:recognizer) { :unimrcp } + let(:renderer) { :unimrcp } + + it 'should create an MRCPPrompt component and execute it asynchronously' do + Component::MRCPPrompt.should_receive(:new_link).once.with(command, subject).and_return mock_action + mock_action.async.should_receive(:execute).once + subject.execute_command command + end + end + + context "when the recognizer is unimrcp and the renderer is asterisk" do + let(:recognizer) { :unimrcp } + let(:renderer) { :asterisk } + + it 'should create an MRCPPrompt component and execute it asynchronously' do + Component::MRCPNativePrompt.should_receive(:new_link).once.with(command, subject).and_return mock_action + mock_action.async.should_receive(:execute).once + subject.execute_command command + end + end + + context "when the recognizer is unimrcp and the renderer is something we can't compose with unimrcp" do + let(:recognizer) { :unimrcp } + let(:renderer) { :swift } + + it 'should return an error' do + subject.execute_command command + command.response(0.5).should be == ProtocolError.new.setup(:invalid_command, "Invalid recognizer/renderer combination", subject.id) + end + end + + context "when the recognizer is something other than unimrcp" do + let(:recognizer) { :asterisk } + let(:renderer) { :unimrcp } + + it 'should create a ComposedPrompt component and execute it asynchronously' do + Component::ComposedPrompt.should_receive(:new_link).once.with(command, subject).and_return mock_action + mock_action.async.should_receive(:execute).once + subject.execute_command command + end + end + end + context 'with a Record component' do let :command do Punchblock::Component::Record.new end @@ -1055,11 +1283,11 @@ let :command do Punchblock::Component::Stop.new :component_id => component_id end let :mock_component do - mock 'Component', :id => component_id + double 'Component', :id => component_id end context "for a known component ID" do before { subject.register_component mock_component } @@ -1072,20 +1300,18 @@ context "for a component which began executing but crashed" do let :component_command do Punchblock::Component::Asterisk::AGI::Command.new :name => 'Wait' end - let(:comp_id) { component_command.response.id } + let(:comp_id) { component_command.response.component_id } let(:subsequent_command) { Punchblock::Component::Stop.new :component_id => comp_id } let :expected_event do - Punchblock::Event::Complete.new.tap do |e| - e.target_call_id = subject.id - e.component_id = comp_id - e.reason = Punchblock::Event::Complete::Error.new - end + Punchblock::Event::Complete.new target_call_id: subject.id, + component_id: comp_id, + reason: Punchblock::Event::Complete::Error.new end before do component_command.request! subject.execute_command component_command @@ -1139,93 +1365,10 @@ it 'sends an error in response to the command' do subject.execute_command command command.response.should be == ProtocolError.new.setup('command-not-acceptable', "Did not understand command for call #{subject.id}", subject.id) end end - - context "with a join command" do - let(:other_call_id) { "abc123" } - let(:other_channel) { 'SIP/bar' } - let(:other_translator) { stub('Translator::Asterisk').as_null_object } - - let :other_call do - Call.new other_channel, other_translator, ami_client, connection - end - - let :command do - Punchblock::Command::Join.new :call_id => other_call_id - end - - it "executes the proper dialplan Bridge application" do - subject.wrapped_object.should_receive(:execute_agi_command).with('EXEC Bridge', other_channel).and_return code: 200 - translator.should_receive(:call_with_id).with(other_call_id).and_return(other_call) - subject.execute_command command - end - end - - context "with an unjoin command" do - let(:other_call_id) { "abc123" } - let(:other_channel) { 'SIP/bar' } - - let :other_call do - Call.new other_channel, translator, ami_client, connection - end - - let :command do - Punchblock::Command::Unjoin.new :call_id => other_call_id - end - - it "executes the unjoin through redirection" do - translator.should_receive(:call_with_id).with(other_call_id).and_return(nil) - - ami_client.should_receive(:send_action).once.with("Redirect", - 'Channel' => channel, - 'Exten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, - 'Priority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, - 'Context' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT, - ).and_return RubyAMI::Response.new - - subject.execute_command command - - command.response(1).should be_true - end - - it "executes the unjoin through redirection, on the subject call and the other call" do - translator.should_receive(:call_with_id).with(other_call_id).and_return(other_call) - - ami_client.should_receive(:send_action).once.with("Redirect", - 'Channel' => channel, - 'Exten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, - 'Priority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, - 'Context' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT, - 'ExtraChannel' => other_channel, - 'ExtraExten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, - 'ExtraPriority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, - 'ExtraContext' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT - ).and_return RubyAMI::Response.new - - subject.execute_command command - end - - it "handles redirect errors" do - translator.should_receive(:call_with_id).with(other_call_id).and_return(nil) - - error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' } - - ami_client.should_receive(:send_action).once.with("Redirect", - 'Channel' => channel, - 'Exten' => Punchblock::Translator::Asterisk::REDIRECT_EXTENSION, - 'Priority' => Punchblock::Translator::Asterisk::REDIRECT_PRIORITY, - 'Context' => Punchblock::Translator::Asterisk::REDIRECT_CONTEXT, - ).and_raise error - - subject.execute_command command - response = command.response(1) - response.should be_a ProtocolError - response.text.should == 'FooBar' - end - end end#execute_command describe '#execute_agi_command' do before { stub_uuids Punchblock.new_uuid } @@ -1249,16 +1392,36 @@ subject.execute_agi_command 'WAIT FOR DIGIT', *params end end context 'with an error' do + let(:message) { 'Action failed' } + let :error do - RubyAMI::Error.new.tap { |e| e.message = 'Action failed' } + RubyAMI::Error.new.tap { |e| e.message = message } end it 'should raise the error' do ami_client.should_receive(:send_action).once.and_raise error expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(RubyAMI::Error, 'Action failed') + end + + context "which is 'No such channel'" do + let(:message) { 'No such channel' } + + it 'should raise ChannelGoneError' do + ami_client.should_receive(:send_action).once.and_raise error + expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(ChannelGoneError, message) + end + end + + context "which is 'Channel SIP/nosuchchannel does not exist.'" do + let(:message) { 'Channel SIP/nosuchchannel does not exist.' } + + it 'should raise ChannelGoneError' do + ami_client.should_receive(:send_action).once.and_raise error + expect { subject.execute_agi_command 'EXEC ANSWER' }.to raise_error(ChannelGoneError, message) + end end end describe 'when receiving an AsyncAGI event' do context 'of type Exec' do