require 'spec_helper' require 'adhearsion/voip/menu_state_machine/menu_class' require 'adhearsion/voip/menu_state_machine/menu_builder' module DialplanCommandTestHelpers def self.included(test_case) test_case.send(:attr_reader, :mock_call, :input, :output) test_case.before do Adhearsion::Configuration.configure { |config| config.enable_asterisk() } @input = MockSocket.new @output = MockSocket.new @mock_call = Object.new @mock_call.metaclass.send(:attr_reader, :call) @mock_call.instance_variable_set(:@call, MockCall.new) mock_call.extend(Adhearsion::VoIP::Asterisk::Commands) flexmock(mock_call) do |call| call.should_receive(:from_pbx).and_return(input) call.should_receive(:to_pbx).and_return(output) end end test_case.after do pbx_output_should_be_empty end end class MockCall attr_accessor :variables def initialize @variables = {} end def with_command_lock yield end end class MockSocket def empty? messages.empty? end def print(message) messages << message end def puts(message) messages << message.chomp + "\n" end def read messages.shift end def gets read end def messages @messages ||= [] end end private def should_pass_control_to_a_context_that_throws(symbol, &block) did_the_rescue_block_get_executed = false begin yield rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe did_the_rescue_block_get_executed = true cpe.target.should throw_symbol symbol rescue => e did_the_rescue_block_get_executed = true raise e ensure did_the_rescue_block_get_executed.should be true end end def should_throw(sym=nil,&block) block.should throw_symbol(*[sym].compact) end def mock_route_calculation_with(*definitions) flexmock(Adhearsion::VoIP::DSL::DialingDSL).should_receive(:calculate_routes_for).and_return(definitions) end def pbx_should_have_been_sent(message) output.gets.chomp.should == message end def pbx_should_respond_with(message) input.print message end def pbx_should_respond_with_digits(string_of_digits) pbx_should_respond_with "200 result=#{string_of_digits}" end def pbx_should_respond_with_digits_and_timeout(string_of_digits) pbx_should_respond_with "200 result=#{string_of_digits} (timeout)" end def pbx_should_respond_to_timeout(timeout) pbx_should_respond_with "200 result=#{timeout}" end def pbx_should_respond_with_value(value) pbx_should_respond_with pbx_value_response value end def pbx_should_respond_with_success(success_code = nil) pbx_should_respond_with pbx_success_response(success_code) end alias does_not_read_data_back pbx_should_respond_with_success def pbx_should_respond_with_failure(failure_code = nil) pbx_should_respond_with(pbx_failure_response(failure_code)) end def pbx_should_respond_with_successful_background_response(digit=0) pbx_should_respond_with_success digit.kind_of?(String) ? digit[0] : digit end def pbx_should_respond_with_playback_success pbx_should_respond_with pbx_raw_response mock_call.should_receive(:get_variable).once.with('PLAYBACKSTATUS').and_return 'SUCCESS' end def pbx_should_respond_with_playback_failure pbx_should_respond_with pbx_raw_response mock_call.should_receive(:get_variable).once.with('PLAYBACKSTATUS').and_return 'FAILED' end def pbx_should_respond_with_stream_file_success(success_code = nil, endpos = '20000') pbx_should_respond_with pbx_raw_stream_file_response(success_code, endpos) end def pbx_should_respond_with_stream_file_failure_on_open(endpos = nil) pbx_should_respond_with pbx_raw_stream_file_response(nil, endpos) end def pbx_should_respond_with_a_wait_for_digit_timeout pbx_should_respond_with_successful_background_response 0 end def pbx_success_response(success_code = nil) "200 result=#{success_code || default_success_code}" end def pbx_raw_response(code = nil) "200 result=#{code || default_code}\n" end def pbx_raw_stream_file_response(code = nil, endpos = nil) "200 result=#{code || default_code} endpos=#{endpos || default_code}\n" end def pbx_value_response(value) "200 result=1 (#{value})" end def pbx_result_response(value) "200 result=#{value.ord}" end def default_success_code '1' end def default_code '0' end def pbx_failure_response(failure_code = nil) "200 result=#{failure_code || default_failure_code}" end def default_failure_code '-1' end def output_stream_matches(pattern) output.gets.should match pattern end module OutputStreamMatchers def pbx_was_asked_to_play(*audio_files) audio_files.flatten.each do |audio_file| output_stream_matches(/playback "#{audio_file}"/) end end def pbx_was_asked_to_play_number(number) output_stream_matches(/saynumber "#{number}"/) end def pbx_was_asked_to_play_time(number) output_stream_matches(/sayunixtime "#{number}"/) end def pbx_was_asked_to_stream(*audio_files) audio_files.flatten.each do |audio_file| output_stream_matches /^STREAM FILE "#{audio_file}" "1234567890\*#"\n$/ end end def pbx_was_asked_to_execute(application, *options) output_stream_matches(/exec saydigits "#{options.join('|')}"/i) end def pbx_output_should_be_empty output.messages.should be_empty, output.messages.inspect end end include OutputStreamMatchers def assert_success(response) response.should == pbx_success_response end end module MenuBuilderTestHelper def builder_should_match_with_these_quantities_of_calculated_matches(checks) checks.each do |check,hash| hash.each_pair do |method_name,intended_quantity| builder.calculate_matches_for(check).send(method_name).should == intended_quantity end end end end module MenuTestHelper def pbx_should_send_digits(*digits) digits.each do |digit| digit = nil if digit == :timeout mock_call.should_receive(:interruptible_play).once.and_return(digit) end end end module ConfirmationManagerTestHelper def encode_hash(hash) Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument(hash) end end describe 'Asterisk VoIP Commands' do include DialplanCommandTestHelpers it "a call can write back to the PBX" do message = 'oh hai' mock_call.write message pbx_should_have_been_sent message end end describe 'hangup command' do include DialplanCommandTestHelpers it "hanging up a call succesfully writes HANGUP back to the PBX and a success resopnse is returned" do pbx_should_respond_with_success response = mock_call.hangup pbx_should_have_been_sent 'HANGUP' response.should == pbx_success_response end end describe 'receiving a hangup' do include DialplanCommandTestHelpers it "should treat a ECONNRESET as a hangup" do pbx_should_respond_with_success def input.gets() raise Errno::ECONNRESET end the_following_code { mock_call.read() }.should raise_error(Adhearsion::Hangup) end end describe "writing a command" do include DialplanCommandTestHelpers it "should strip out excess whitespace" do pbx_should_respond_with_success mock_call.should_receive(:write).with "EXEC Ringing" mock_call.raw_response "EXEC \nRinging\n\n" end end describe 'The #interruptible_play method' do include DialplanCommandTestHelpers it 'should return a string for the digit that was pressed' do digits = %w{0 1 # * 9}.map{|c| c.ord} file = "file_doesnt_matter" digits.each { |digit| pbx_should_respond_with_stream_file_success digit } digits.map { |digit| mock_call.interruptible_play file }.should == digits.map(&:chr) digits.size.times { pbx_was_asked_to_stream file } end it "should return nil if no digit was pressed" do pbx_should_respond_with_stream_file_success 0 file = 'foobar' mock_call.interruptible_play(file).should be nil pbx_was_asked_to_stream file end it 'should return nil if no digit was pressed, even if the sound file is not found' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' mock_call.interruptible_play(file).should be nil pbx_was_asked_to_stream file end it "should play a series of files, stopping the series when a digit is played" do stubbed_keypad_input = [0, 0, ?3.ord] stubbed_keypad_input.each do |digit| pbx_should_respond_with_stream_file_success digit end play_files = (100..105).map &:to_s played_files = (100..102).map &:to_s mock_call.interruptible_play(*play_files).should == '3' pbx_was_asked_to_stream played_files end it 'should play a series of files, stopping the series when a digit is played, even if the sound files cannot be found' do pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_failure_on_open pbx_should_respond_with_stream_file_success ?9.ord play_files = ('sound1'..'sound6').map &:to_s played_files = ('sound1'..'sound4').map &:to_s mock_call.interruptible_play(*play_files).should == '9' pbx_was_asked_to_stream played_files end end describe 'The #interruptible_play! method' do include DialplanCommandTestHelpers it 'should return a string for the digit that was pressed' do digits = %w{0 1 # * 9}.map{|c| c.ord} file = "file_doesnt_matter" digits.each { |digit| pbx_should_respond_with_stream_file_success digit } digits.map { |digit| mock_call.interruptible_play! file }.should == digits.map(&:chr) digits.size.times { pbx_was_asked_to_stream file } end it "should return nil if no digit was pressed" do pbx_should_respond_with_stream_file_success 0 file = 'foobar' mock_call.interruptible_play!(file).should be nil pbx_was_asked_to_stream file end it 'should raise an error when the sound file is not found' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' the_following_code { mock_call.interruptible_play! file }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream file end it "should play a series of files, stopping the series when a digit is played" do stubbed_keypad_input = [0, 0, ?3.ord] stubbed_keypad_input.each do |digit| pbx_should_respond_with_stream_file_success digit end play_files = (100..105).map &:to_s played_files = (100..102).map &:to_s mock_call.interruptible_play!(*play_files).should == '3' pbx_was_asked_to_stream played_files end it 'should play a series of files, raising an error if a sound file cannot be found' do pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_failure_on_open play_files = ('sound1'..'sound6').map &:to_s played_files = ('sound1'..'sound2').map &:to_s the_following_code { mock_call.interruptible_play! *play_files }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream played_files end it 'should raise an error if an audio file cannot be found' do pbx_should_respond_with_stream_file_failure_on_open audio_file = 'nixon-tapes' the_following_code { mock_call.interruptible_play! audio_file }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream audio_file end it 'should raise an error when audio files cannot be found' do pbx_should_respond_with_stream_file_success pbx_should_respond_with_stream_file_failure_on_open # 'paperz' is the only audio that is missing audio_files = ['rock', 'paperz', 'scissors'] the_following_code { mock_call.interruptible_play! audio_files }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream ['rock', 'paperz'] # stop short before playing with scissors! end end describe 'The #wait_for_digit method' do include DialplanCommandTestHelpers it 'should return a string for the digit that was pressed' do digits = %w{0 1 # * 9}.map{|c| c.ord} digits.each { |digit| pbx_should_respond_with_success digit } digits.map { |digit| mock_call.send(:wait_for_digit) }.should == digits.map(&:chr) digits.size.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "-1"' } end it "the timeout given must be converted to milliseconds" do pbx_should_respond_with_success 0 mock_call.send(:wait_for_digit, 1) pbx_should_have_been_sent 'WAIT FOR DIGIT "1000"' end end describe 'The #answer method' do include DialplanCommandTestHelpers it 'should send ANSWER over the AGI socket' do does_not_read_data_back mock_call.answer pbx_should_have_been_sent 'ANSWER' end end describe 'The #execute method' do include DialplanCommandTestHelpers it 'execute writes exec and app name to the PBX' do pbx_should_respond_with_success assert_success mock_call.execute(:foo) pbx_should_have_been_sent 'EXEC foo' end it 'execute returns false if the command was not executed successfully by the PBX' do pbx_should_respond_with_failure mock_call.execute(:foo).should_not be true pbx_should_have_been_sent 'EXEC foo' end it 'execute can accept arguments after the app name which get translated into pipe-delimited arguments to the PBX' do pbx_should_respond_with_success mock_call.execute :foo, 'bar', 'baz', 'hi' pbx_should_have_been_sent 'EXEC foo "bar"|"baz"|"hi"' end it "should raise a Hangup exception when nil is returned when reading a command from Asterisk" do flexmock(input).should_receive(:gets).once.and_return nil the_following_code { mock_call.execute :foo, "bar" }.should raise_error Adhearsion::Hangup pbx_should_have_been_sent 'EXEC foo "bar"' end it "should raise a ArgumentError if given a null byte in the arguments" do the_following_code { mock_call.execute :foo, "bar\0" }.should raise_error ArgumentError end end describe 'The #inline_return_value method' do include DialplanCommandTestHelpers it 'should return nil when given false or nil' do mock_call.inline_return_value(false).should be nil mock_call.inline_return_value(nil).should be nil end it 'should return nil when given an empty AGI value (0)' do mock_call.inline_return_value(pbx_result_response(0)).should be nil end it 'should raise AGIProtocolError with an invalid response' do expect { mock_call.inline_return_value("500 result=foo\n") }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError expect { mock_call.inline_return_value('Hey man, not so loud!') }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError end it 'should parse the return value' do mock_call.inline_return_value(pbx_result_response(5)).should == '5' end end describe 'The #inline_result_with_return_value method' do include DialplanCommandTestHelpers it 'should return nil when given false or nil' do mock_call.inline_result_with_return_value(false).should be nil mock_call.inline_result_with_return_value(nil).should be nil end it 'should return nil when given an empty AGI value (0)' do mock_call.inline_result_with_return_value(pbx_result_response(0)).should be nil end it 'should raise AGIProtocolError with an invalid response' do expect { mock_call.inline_result_with_return_value("500 result=1 (foo)\n") }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError expect { mock_call.inline_result_with_return_value('Hey man, not so loud!') }.to raise_error Adhearsion::VoIP::Asterisk::AGIProtocolError end it 'should parse the return value' do mock_call.inline_result_with_return_value(pbx_value_response(5)).should == '5' end end describe 'The #play_or_speak method' do include DialplanCommandTestHelpers it 'should play a sound file if one exists' do pbx_should_respond_with_playback_success audio_file = "cents-per-minute" mock_call.play_or_speak({audio_file => {}}).should be nil pbx_was_asked_to_play audio_file end it 'should play a sound file via interruptible_play if file exists and interrupbible set and return key pressed and return the key press value' do audio_file = "cents-per-minute" mock_call.should_receive(:interruptible_play!).with(audio_file).once.and_return '#' mock_call.play_or_speak({audio_file => {:interruptible => true}}).should == '#' end it 'should play a sound file via interruptible_play if file exists and interrupbible set' do audio_file = "cents-per-minute" mock_call.should_receive(:interruptible_play!).with(audio_file).once.and_return nil mock_call.play_or_speak({audio_file => {:interruptible => true}}).should == nil end it 'should raise and error if a sound file does not exist and there is not text specified to fall back to' do audio_file = "nixon tapes" mock_call.should_receive(:play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError the_following_code { mock_call.play_or_speak({audio_file => { :engine => :unimrcp}}) }.should raise_error ArgumentError end it 'should not send the command to play if the audio file is blank' do mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil mock_call.play_or_speak({'' => { :text => 'hello', :engine => :unimrcp }}).should be nil end it 'should not send the command to play if the audio file is nil' do mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil mock_call.play_or_speak({nil => { :text => 'hello', :engine => :unimrcp }}).should be nil end it 'should speak the text if a sound file does not exist' do audio_file = "nixon tapes" mock_call.should_receive(:play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp}).once.and_return nil mock_call.play_or_speak({audio_file => { :text => 'hello', :engine => :unimrcp }}).should be nil end it 'should speak the text if a sound file does not exist and pass back the entered text if a key is pressed' do audio_file = "nixon tapes" mock_call.should_receive(:interruptible_play!).with(audio_file).and_raise Adhearsion::VoIP::PlaybackError mock_call.should_receive(:speak).with('hello', {:engine=>:unimrcp, :interruptible => true}).once.and_return '5' mock_call.play_or_speak({audio_file => { :text => 'hello', :engine => :unimrcp, :interruptible => true }}).should == '5' end end describe 'The #play method' do include DialplanCommandTestHelpers it 'passing a single string to play results in the playback application being executed with that file name on the PBX' do pbx_should_respond_with_playback_success audio_file = "cents-per-minute" mock_call.play(audio_file).should be true pbx_was_asked_to_play audio_file end it 'multiple strings can be passed to play, causing multiple playback commands to be issued' do 2.times do pbx_should_respond_with_playback_success end audio_files = ["cents-per-minute", 'o-hai'] mock_call.play(audio_files).should be true pbx_was_asked_to_play audio_files end it 'should return false if an audio file cannot be found' do pbx_should_respond_with_playback_failure audio_file = 'nixon-tapes' mock_call.play(audio_file).should be false pbx_was_asked_to_play audio_file end it 'should return false when audio files cannot be found' do pbx_should_respond_with_playback_success pbx_should_respond_with_playback_failure # 'paperz' is the only audio that is missing pbx_should_respond_with_playback_success audio_files = ['rock', 'paperz', 'scissors'] mock_call.play(audio_files).should be false pbx_was_asked_to_play audio_files end it 'If a number is passed to play(), the saynumber application is executed with the number as an argument' do pbx_should_respond_with_success mock_call.play(123).should be true pbx_was_asked_to_play_number(123) end it 'if a string representation of a number is passed to play(), the saynumber application is executed with the number as an argument' do pbx_should_respond_with_success mock_call.play('123').should be true pbx_was_asked_to_play_number(123) end it 'If a Time is passed to play(), the SayUnixTime application will be executed with the time since the UNIX epoch in seconds as an argument' do time = Time.parse("12/5/2000") pbx_should_respond_with_success mock_call.play(time).should be true pbx_was_asked_to_play_time(time.to_i) end it 'If a Date is passed to play(), the SayUnixTime application will be executed with the date passed in' do date = Date.parse('2011-01-23') mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",'BdY').and_return pbx_raw_response mock_call.play(date).should be true end it 'If a Date or Time is passed to play_time(), the SayUnixTime application will be executed with the date and format passed in' do date, format = Date.parse('2011-01-23'), 'ABdY' mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return "200 result=0\n" mock_call.play_time(date, :format => format).should == pbx_raw_response time, format = Time.at(875121313), 'BdY \'digits/at\' IMp' mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",format).and_return pbx_raw_response mock_call.play_time(time, :format => format).should == pbx_raw_response end it 'If a Time object is passed to play_time, the SayUnixTime application will be executed with the default parameters' do time = Time.at(875121313) mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",'').and_return pbx_raw_response mock_call.play_time(time).should == pbx_raw_response end it 'If an object other than Time, DateTime, or Date is passed to play_time false will be returned' do non_time = 'blah' mock_call.play_time(non_time).should be false end it 'If an array containing a Date/DateTime/Time object and a hash is passed to play(), the SayUnixTime application will be executed with the object passed in with the specified format and timezone' do date, format = Date.parse('2011-01-23'), 'ABdY' mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return pbx_raw_response mock_call.play([date, {:format => format}]).should be true time, timezone = Time.at(1295843084), 'US/Eastern' mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,'').and_return pbx_raw_response mock_call.play([time, {:timezone => timezone}]).should be true time, timezone, format = Time.at(1295843084), 'US/Eastern', 'ABdY \'digits/at\' IMp' mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,format).and_return pbx_raw_response mock_call.play([time, {:timezone => timezone, :format => format}]).should be true end it 'If a string matching dollars and (optionally) cents is passed to play(), a series of command will be executed to read the dollar amount', :ignore => true do #TODO: I think we should not have this be part of play(). Too much functionality in one method. Too much overloading. When we want to support multiple # currencies, it'll be completely unwieldy. I'd suggest play_currency as a separate method. - Chad end end describe 'The #play! method' do include DialplanCommandTestHelpers it 'should accept multiple strings to play, causing multiple playback commands to be issued' do 2.times do pbx_should_respond_with_playback_success end audio_files = ["cents-per-minute", 'o-hai'] mock_call.play!(audio_files).should be true pbx_was_asked_to_play audio_files end it 'should raise an error if an audio file cannot be found' do pbx_should_respond_with_playback_failure audio_file = 'nixon-tapes' the_following_code { mock_call.play! audio_file }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_play audio_file end it 'should raise an error when audio files cannot be found' do pbx_should_respond_with_playback_success pbx_should_respond_with_playback_failure # 'paperz' is the only audio that is missing audio_files = ['rock', 'paperz', 'scissors'] the_following_code { mock_call.play! audio_files }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_play ['rock', 'paperz'] # stop short before playing with scissors! end end describe 'the #record method' do include DialplanCommandTestHelpers it 'should return the recorded file name if the user hangs up during the recording' do mock_call.should_receive(:response).once.with('RECORD FILE', 'foo', 'gsm', '#', -1, 0, 'BEEP').and_return("200 result=-1 (hangup) endpos=167840\n") mock_call.record('foo').should == 'foo.gsm' end it 'create a default filename if no file is specifed and icrement it on subsequent calls' do mock_call.call.variables.delete :recording_counter mock_call.should_receive(:new_guid).once.and_return('2345') mock_call.should_receive(:new_guid).once.and_return('4322') mock_call.should_receive(:response).once.with('RECORD FILE', '/tmp/recording_2345_0', 'gsm', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.should_receive(:response).once.with('RECORD FILE', '/tmp/recording_4322_1', 'gsm', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record(:beep => nil, :escapedigits => '26').should == '/tmp/recording_0.gsm' mock_call.record(:beep => nil, :escapedigits => '26').should == '/tmp/recording_1.gsm' end it 'determine the format from the filename' do mock_call.should_receive(:response).once.with('RECORD FILE', 'foo', 'wav', '26', -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record('foo.wav', :beep => nil, :escapedigits => '26').should == 'foo.wav' end it 'set the format of a file via the :format option' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record('foo', :beep => nil, :maxduration => 2, :format => 'wav').should == 'foo.wav' end it 'set the format of a file via the :format option over-riding a implicit format' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo.wav", "mpeg", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record('foo.wav', :beep => nil, :maxduration => 2, :format => 'mpeg').should == 'foo.wav.mpeg' end end describe 'the #record_to_file method' do include DialplanCommandTestHelpers it 'should return :hangup if the user hangs up during the recording' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (hangup) endpos=167840\n") mock_call.record_to_file('foo').should == :hangup end it 'should return :write error if the recording had a problem writing the file' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (writefile) endpos=167840\n") mock_call.record_to_file('foo').should == :write_error end it 'should return :success_dtmf if the recording was completed successfully with a dtmf tone to end' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=35 (dtmf) endpos=29120\n") mock_call.record_to_file('foo').should == :success_dtmf end it 'should return :success_timeout if the recording was completed successfully by timing out with silence' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo').should == :success_timeout end it 'not send a beep if a :beep=>nil is passed in' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => nil).should == :success_timeout end it 'set the silence if it is passed in' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, 's=2').and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => nil, :silence => 2).should == :success_timeout end it 'set the maxduration if it is passed in' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => nil, :maxduration => 2).should == :success_timeout end it 'set the format of a file via the :format option' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => nil, :maxduration => 2, :format => 'wav').should == :success_timeout end it 'set the format of a file via the :format option over-riding a implicit format' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo.wav", "mpeg", "#", 2000, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo.wav', :beep => nil, :maxduration => 2, :format => 'mpeg').should == :success_timeout end it 'set the escapedigits if it is passed in' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => nil, :escapedigits => '26').should == :success_timeout end it 'play a passed in beep file if it is passed in' do mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(true) pbx_should_respond_with_playback_success pbx_should_respond_with_playback_success mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout end it "should silently fail if the beep file passed in can't be played" do mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(true) pbx_should_respond_with_playback_failure pbx_should_respond_with_playback_failure mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout end it 'determine the format from the filename' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file('foo.wav', :beep => nil, :escapedigits => '26').should == :success_timeout end it 'create a default filename if no file is specifed and icrement it on subsequent calls' do mock_call.should_receive(:new_guid).once.and_return('2345') mock_call.should_receive(:new_guid).once.and_return('4322') mock_call.should_receive(:response).once.with("RECORD FILE", "/tmp/recording_2345_0", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.should_receive(:response).once.with("RECORD FILE", "/tmp/recording_4322_1", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file(:beep => nil, :escapedigits => '26').should == :success_timeout mock_call.record_to_file(:beep => nil, :escapedigits => '26').should == :success_timeout end end describe 'The #record_to_file! method' do include DialplanCommandTestHelpers it "should throw an exception the beep file passed in can't be played" do mock_call.should_receive(:execute).once.with(:playback, 'my_awesome_beep.wav').and_return(false) pbx_should_respond_with_playback_failure pbx_should_respond_with_playback_failure mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") the_following_code { mock_call.record_to_file!('foo', :beep => 'my_awesome_beep.wav', :escapedigits => '26').should == :success_timeout }.should raise_error Adhearsion::VoIP::PlaybackError end it 'should throw RecordError if the recording had a problem writing the file' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "gsm", "#", -1, 0, "BEEP").and_return("200 result=-1 (writefile) endpos=167840\n") the_following_code { mock_call.record_to_file!('foo').should == :write_error }.should raise_error Adhearsion::VoIP::RecordError end it 'should be able get a response from a successfull call' do mock_call.should_receive(:response).once.with("RECORD FILE", "foo", "wav", "26", -1, 0).and_return("200 result=0 (timeout) endpos=21600\n") mock_call.record_to_file!('foo.wav', :beep => nil, :escapedigits => '26').should == :success_timeout end end describe 'The #input method' do include DialplanCommandTestHelpers it 'should raise an error when the number of digits expected is -1 (this is deprecated behavior)' do the_following_code { mock_call.input(-1) }.should raise_error ArgumentError end it 'input() calls wait_for_digit the specified number of times (when no sound files are given)' do mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).times(4).with(-1).and_return('1', '2', '3', '4') mock_call.input(4).should == '1234' end it 'should execute wait_for_digit if no digit is pressed during interruptible_play!' do sound_files = %w[one two three] mock_call.should_receive(:interruptible_play!).once.with('one').and_return nil mock_call.should_receive(:interruptible_play!).once.with('two').and_return nil mock_call.should_receive(:interruptible_play!).once.with('three').and_return nil mock_call.should_receive(:wait_for_digit).once.with(-1).and_throw :digit_request should_throw(:digit_request) { mock_call.input(10, :play => sound_files) } end it 'waits for digits with :initial_timeout and :interdigit_timeout' do initial_timeout = 6.seconds interdigit_timeout = 3.seconds mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(3).with(interdigit_timeout).and_return '2', '3', '4' mock_call.input(4, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '1234' end it 'waits for digits with :initial_timeout when nothing is pressed' do initial_timeout = 6.seconds interdigit_timeout = 3.seconds mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return nil mock_call.input(4, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '' end it 'waits for digits, ignoring :timeout if :initial_timeout and :interdigit_timeout are provided' do timeout = 99.hours initial_timeout = 2.seconds interdigit_timeout = 1.second mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(3).with(interdigit_timeout).and_return '2', '3', '4' mock_call.input(4, :timeout => timeout, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '1234' end it 'waits for digits, and given a :timeout and :initial_timeout, defaults :interdigit_timeout to :timeout' do initial_timeout = 20.seconds timeout = 10.seconds mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(2).with(timeout).and_return '2', '3' mock_call.input(3, :timeout => timeout, :initial_timeout => initial_timeout).should eq '123' end it 'waits for digits, and given a :timeout and :interdigit_timeout, defaults :initial_timeout to :timeout' do timeout = 12.seconds interdigit_timeout = 6.seconds mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input(5, :timeout => timeout, :interdigit_timeout => interdigit_timeout).should eq '12345' end it 'waits for digits for :initial_timeout if sound playback is not interrupted' do initial_timeout = 8.seconds interdigit_timeout = 4.seconds sound_files = %w[ready set go] mock_call.should_receive(:interruptible_play!).once.with('ready').and_return nil mock_call.should_receive(:interruptible_play!).once.with('set').and_return nil mock_call.should_receive(:interruptible_play!).once.with('go').and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input(5, :play => sound_files, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12345' end it 'ignores :initial_timeout if sound playback is interrupted' do initial_timeout = 8.seconds interdigit_timeout = 4.seconds sound_files = %w[ready set go] mock_call.should_receive(:interruptible_play!).once.with('ready').and_return nil mock_call.should_receive(:interruptible_play!).once.with('set').and_return '*' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input(5, :play => sound_files, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '*2345' end it 'waits for digits for :initial_timeout if speech is not interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your area code?" mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(2).with(interdigit_timeout).and_return '2', '0' mock_call.input(3, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :speak => {:text => text}).should eq '120' end it 'ignores :initial_timeout if speech is interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your area code?" mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return '3' mock_call.should_receive(:wait_for_digit).times(2).with(interdigit_timeout).and_return '1', '2' mock_call.input(3, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :speak => {:text => text}).should eq '312' end it 'should default the :accept_key to "#" when unlimited digits are to be collected' do mock_call.should_receive(:wait_for_digit).times(2).with(-1).and_return '*', '#' mock_call.input.should == '*' end it 'should raise an exception when unlimited digits are to be collected and :accept_key => false' do flexstub(mock_call).should_receive(:read).and_return the_following_code { mock_call.input(:accept_key => false) }.should raise_error ArgumentError pbx_should_have_been_sent 'WAIT FOR DIGIT "-1"' end it 'when :accept_key is false and input() is collecting a finite number of digits, it should allow all DTMFs' do all_digits = %w[0 1 2 3 # * 4 5 6 7 8 9] mock_call.should_receive(:wait_for_digit).times(all_digits.size).with(-1).and_return(*all_digits) the_following_code { mock_call.input(all_digits.size, :accept_key => false) }.should_not raise_error ArgumentError end it 'should terminate early when the passed block returns something truthy' do three_digits = %w[9 3 0] mock_call.should_receive(:wait_for_digit).times(2).with(-1).and_return(*three_digits) mock_call.input(3, :accept_key => false) { |buffer| buffer.size == 2 }.should == '93' end it "Input timing out when digits are pressed returns only the collected digits" do timeout = 1.day mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '5', nil mock_call.input(9, :timeout => timeout).should == '5' end it 'passes wait_for_digit the :timeout option when one is given' do timeout = 1.minute mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '1', '2' mock_call.input(2, :timeout => timeout).should == '12' end it 'executes interruptible_play!() with all of the files given to :play' do sound_files = %w[foo bar qaz] mock_call.should_receive(:interruptible_play!).once.with('foo').and_return nil mock_call.should_receive(:interruptible_play!).once.with('bar').and_return nil mock_call.should_receive(:interruptible_play!).once.with('qaz').and_return '#' mock_call.should_receive(:wait_for_digit).once.with(-1).and_return '*' mock_call.input(2, :play => sound_files).should == '#*' end it 'executes #play! when :interruptible is set to false' do sound_files = %w[foo bar qaz] mock_call.should_receive(:play!).once.with('foo').and_return true mock_call.should_receive(:play!).once.with('bar').and_return true mock_call.should_receive(:play!).once.with('qaz').and_return true mock_call.should_receive(:wait_for_digit).once.with(-1).and_return '*' mock_call.input(1, :play => sound_files, :interruptible => false).should == '*' end it 'pressing the terminating key before any other digits returns an empty string' do mock_call.should_receive(:wait_for_digit).once.with(-1).and_return '*' mock_call.input(:accept_key => '*').should == '' end it 'should execute wait_for_digit first if no sound files are given' do mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(-1).and_throw :digit_request should_throw(:digit_request) { mock_call.input(1) } end it "Input timing out when digits are pressed returns only the collected digits" do timeout = 1.day mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '5', nil mock_call.input(9, :timeout => timeout).should == '5' end it 'should execute wait_for_digit, even if some interruptible sound files are not found' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' initial_timeout = 1.hour interdigit_timeout = 1.minute mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '8' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '9' mock_call.input(2, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :play => file).should == '89' pbx_was_asked_to_stream file end it 'should execute wait_for_digit, even if some uninterruptible sound files are not found' do pbx_should_respond_with_playback_failure file = 'foobar' initial_timeout = 1.hour interdigit_timeout = 1.minute mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '8' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '9' mock_call.input(2, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :play => file, :interruptible => false).should == '89' pbx_was_asked_to_play file end it 'should return an empty string if no keys are pressed, even if the sound file is not found' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' timeout = 1.second mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil mock_call.input(5, :timeout => timeout, :play => file).should == '' pbx_was_asked_to_stream file end it 'should play a series of files, collecting digits even if some of the sound files cannot be found' do pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_failure_on_open pbx_should_respond_with_stream_file_success ?1.ord play_files = ('sound1'..'sound6').map &:to_s played_files = ('sound1'..'sound4').map &:to_s timeout = 1.minute mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '2', '3' mock_call.input(3, :timeout => timeout, :play => play_files).should == '123' pbx_was_asked_to_stream played_files end it 'should play a series of 4 interruptible sounds, collecting digits even if some of the sound files cannot be found' do pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_failure_on_open pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_success ?1.ord play_files = ('sound1'..'sound8').map &:to_s played_files = ('sound1'..'sound5').map &:to_s timeout = 1.second mock_call.should_receive(:wait_for_digit).times(3).with(timeout).and_return '2', '3', '4' mock_call.input(4, :timeout => timeout, :play => play_files).should == '1234' pbx_was_asked_to_stream played_files end it 'should not raise an exception if the sound file is unplayable' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' mock_call.should_receive(:wait_for_digit).once.with -1 the_following_code { mock_call.input 1, :play => file }.should_not raise_error pbx_was_asked_to_stream file end it 'should default to playing interruptible prompts' do mock_call.should_receive(:interruptible_play!).once.with('does_not_matter') mock_call.should_receive(:wait_for_digit).once.with -1 mock_call.input(1, :play => 'does_not_matter') end it 'should render uninterruptible prompts' do mock_call.should_receive(:play!).once.with('does_not_matter') mock_call.should_receive(:wait_for_digit).once.with -1 mock_call.input(1, :play => 'does_not_matter', :interruptible => false) end it 'should fall back to speaking TTS if sound file is unplayable' do pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with("The sound file was not available", :interruptible => true) mock_call.should_receive(:wait_for_digit).once.with -1 mock_call.input(1, :play => 'unavailable sound file', :speak => {:text => "The sound file was not available"}) @output.read.should == "STREAM FILE \"unavailable sound file\" \"1234567890*#\"\n" end it 'waits for digits for :initial_timeout if fall back TTS is not interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your sign?" missing_sound_file = 'missing-sound-file' pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '2' mock_call.input(2, :play => missing_sound_file, :speak => {:text => text}, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12' @output.read.should == "STREAM FILE \"#{missing_sound_file}\" \"1234567890*#\"\n" end it 'ignores :initial_timeout if fall back TTS is interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your sign?" missing_sound_file = 'missing-sound-file' pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return '1' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '2' mock_call.input(2, :play => missing_sound_file, :speak => {:text => text}, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12' @output.read.should == "STREAM FILE \"#{missing_sound_file}\" \"1234567890*#\"\n" end it 'should allow uninterruptible TTS prompts' do mock_call.should_receive(:speak).once.with("The sound file was not available", :interruptible => false) mock_call.should_receive(:wait_for_digit).once.with -1 mock_call.input(1, :speak => {:text => "The sound file was not available"}, :interruptible => false) end it 'should play a series of 4 uninterruptible sounds, collecting digits even if some of the sound files cannot be found' do pbx_should_respond_with_playback_success pbx_should_respond_with_playback_failure pbx_should_respond_with_playback_success files = ('sound1'..'sound3').map &:to_s timeout = 1.second mock_call.should_receive(:wait_for_digit).twice.with(timeout).and_return '6', '7' mock_call.input(2, :timeout => timeout, :play => files, :interruptible => false).should == '67' pbx_was_asked_to_play files end end describe 'The #input! method' do include DialplanCommandTestHelpers it 'should raise an error when the number of digits expected is -1 (this is deprecated behavior)' do the_following_code { mock_call.input! -1 }.should raise_error ArgumentError end it 'should execute wait_for_digit if no digit is pressed during interruptible_play!' do sound_files = %w[one two three] mock_call.should_receive(:interruptible_play!).once.with('one').and_return nil mock_call.should_receive(:interruptible_play!).once.with('two').and_return nil mock_call.should_receive(:interruptible_play!).once.with('three').and_return nil mock_call.should_receive(:wait_for_digit).once.with(-1).and_throw :digit_request should_throw(:digit_request) { mock_call.input! 10, :play => sound_files } end it 'waits for digits with :initial_timeout and :interdigit_timeout' do initial_timeout = 6.seconds interdigit_timeout = 3.seconds mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(3).with(interdigit_timeout).and_return '2', '3', '4' mock_call.input!(4, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '1234' end it 'waits for digits with :initial_timeout when nothing is pressed' do initial_timeout = 6.seconds interdigit_timeout = 3.seconds mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return nil mock_call.input!(4, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '' end it 'waits for digits, ignoring :timeout if :initial_timeout and :interdigit_timeout are provided' do timeout = 99.hours initial_timeout = 2.seconds interdigit_timeout = 1.second mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(3).with(interdigit_timeout).and_return '2', '3', '4' mock_call.input!(4, :timeout => timeout, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '1234' end it 'waits for digits, and given a :timeout and :initial_timeout, defaults :interdigit_timeout to :timeout' do initial_timeout = 20.seconds timeout = 10.seconds mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(2).with(timeout).and_return '2', '3' mock_call.input!(3, :timeout => timeout, :initial_timeout => initial_timeout).should eq '123' end it 'waits for digits, and given a :timeout and :interdigit_timeout, defaults :initial_timeout to :timeout' do timeout = 12.seconds interdigit_timeout = 6.seconds mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input!(5, :timeout => timeout, :interdigit_timeout => interdigit_timeout).should eq '12345' end it 'waits for digits for :initial_timeout if sound playback is not interrupted' do initial_timeout = 8.seconds interdigit_timeout = 4.seconds sound_files = %w[ready set go] mock_call.should_receive(:interruptible_play!).once.with('ready').and_return nil mock_call.should_receive(:interruptible_play!).once.with('set').and_return nil mock_call.should_receive(:interruptible_play!).once.with('go').and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input!(5, :play => sound_files, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12345' end it 'ignores :initial_timeout if sound playback is interrupted' do initial_timeout = 8.seconds interdigit_timeout = 4.seconds sound_files = %w[ready set go] mock_call.should_receive(:interruptible_play!).once.with('ready').and_return nil mock_call.should_receive(:interruptible_play!).once.with('set').and_return '*' mock_call.should_receive(:wait_for_digit).times(4).with(interdigit_timeout).and_return '2', '3', '4', '5' mock_call.input!(5, :play => sound_files, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '*2345' end it 'waits for digits for :initial_timeout if speech is not interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your area code?" mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).times(2).with(interdigit_timeout).and_return '2', '0' mock_call.input!(3, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :speak => {:text => text}).should eq '120' end it 'ignores :initial_timeout if speech is interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your area code?" mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return '3' mock_call.should_receive(:wait_for_digit).times(2).with(interdigit_timeout).and_return '1', '2' mock_call.input!(3, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout, :speak => {:text => text}).should eq '312' end it 'executes interruptible_play!() with all of the files given to :play' do sound_files = %w[foo bar qaz] mock_call.should_receive(:interruptible_play!).once.with('foo').and_return nil mock_call.should_receive(:interruptible_play!).once.with('bar').and_return nil mock_call.should_receive(:interruptible_play!).once.with('qaz').and_return '#' mock_call.should_receive(:wait_for_digit).once.with(-1).and_return '*' mock_call.input!(2, :play => sound_files).should == '#*' end it 'executes play!() with all of the files given to :play' do sound_files = %w[foo bar qaz] mock_call.should_receive(:play!).once.with('foo').and_return true mock_call.should_receive(:play!).once.with('bar').and_return true mock_call.should_receive(:play!).once.with('qaz').and_return true mock_call.should_receive(:wait_for_digit).once.with(-1).and_return '*' mock_call.input!(1, :play => sound_files, :interruptible => false).should == '*' end it 'should execute wait_for_digit first if no sound files are given' do mock_call.should_receive(:interruptible_play!).never mock_call.should_receive(:wait_for_digit).once.with(-1).and_throw :digit_request should_throw(:digit_request) { mock_call.input! 1 } end it 'should raise an error when the sound file is not found' do pbx_should_respond_with_stream_file_failure_on_open file = 'foobar' mock_call.should_receive(:wait_for_digit).never the_following_code { mock_call.input! 1, :play => file }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream file end it 'should fall back to speaking TTS if sound file is unplayable' do pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with("The sound file was not available", :interruptible => true) mock_call.should_receive(:wait_for_digit).once.with -1 mock_call.input!(1, :play => 'unavailable sound file', :speak => {:text => "The sound file was not available"}) @output.read.should == "STREAM FILE \"unavailable sound file\" \"1234567890*#\"\n" end it 'waits for digits for :initial_timeout if fall back TTS is not interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your sign?" missing_sound_file = 'missing-sound-file' pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return nil mock_call.should_receive(:wait_for_digit).once.with(initial_timeout).and_return '1' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '2' mock_call.input!(2, :play => missing_sound_file, :speak => {:text => text}, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12' @output.read.should == "STREAM FILE \"#{missing_sound_file}\" \"1234567890*#\"\n" end it 'ignores :initial_timeout if fall back TTS is interrupted' do initial_timeout = 10.seconds interdigit_timeout = 5.seconds text = "What's your sign?" missing_sound_file = 'missing-sound-file' pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:speak).once.with(text, :interruptible => true).and_return '1' mock_call.should_receive(:wait_for_digit).once.with(interdigit_timeout).and_return '2' mock_call.input!(2, :play => missing_sound_file, :speak => {:text => text}, :initial_timeout => initial_timeout, :interdigit_timeout => interdigit_timeout).should eq '12' @output.read.should == "STREAM FILE \"#{missing_sound_file}\" \"1234567890*#\"\n" end it 'should play a series of interruptible files, raising an error if a sound file cannot be found' do pbx_should_respond_with_stream_file_success 0 pbx_should_respond_with_stream_file_failure_on_open mock_call.should_receive(:wait_for_digit).never play_files = ('sound1'..'sound6').map &:to_s played_files = ('sound1'..'sound2').map &:to_s the_following_code { mock_call.input! 10, :play => play_files, :timeout => 5.seconds }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_stream played_files end it 'should play a series of uninterruptible files, raising an error if a sound file cannot be found' do pbx_should_respond_with_playback_success pbx_should_respond_with_playback_failure play_files = ('sound1'..'sound6').map &:to_s played_files = ('sound1'..'sound2').map &:to_s the_following_code { mock_call.input! 10, :play => play_files, :timeout => 5.seconds, :interruptible => false }.should raise_error Adhearsion::VoIP::PlaybackError pbx_was_asked_to_play played_files end end describe "The #variable method" do include DialplanCommandTestHelpers it "should call set_variable for every Hash-key argument given" do args = [:ohai, "ur_home_erly"] mock_call.should_receive(:set_variable).once.with(*args) mock_call.variable Hash[*args] end it "should call set_variable for every Hash-key argument given" do many_args = { :a => :b, :c => :d, :e => :f, :g => :h} mock_call.should_receive(:set_variable).times(many_args.size) mock_call.variable many_args end it "should call get_variable for every String given" do variables = ["foo", "bar", :qaz, :qwerty, :baz] variables.each do |var| mock_call.should_receive(:get_variable).once.with(var).and_return("X") end mock_call.variable(*variables) end it "should NOT return an Array when just one arg is given" do mock_call.should_receive(:get_variable).once.and_return "lol" mock_call.variable(:foo).should_not be_a_kind_of Array end it "should raise an ArgumentError when a Hash and normal args are given" do the_following_code { mock_call.variable 5,4,3,2,1, :foo => :bar }.should raise_error ArgumentError end end describe "The #set_variable method" do include DialplanCommandTestHelpers it "variables and values are properly quoted" do mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "foo" "i can \\" has ruby?"' mock_call.set_variable 'foo', 'i can " has ruby?' end it "to_s() is effectively called on both the key and the value" do mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "QAZ" "QWERTY"' mock_call.set_variable :QAZ, :QWERTY end end describe "The #sip_add_header method" do include DialplanCommandTestHelpers it "values are properly quoted" do mock_call.should_receive(:raw_response).once.with 'EXEC SIPAddHeader "x-ahn-header: rubyrox"' mock_call.sip_add_header "x-ahn-header", "rubyrox" end end describe "The #sip_get_header method" do include DialplanCommandTestHelpers it "properly formats the AGI request" do value = 'jason-was-here' mock_call.should_receive(:raw_response).once.with('GET VARIABLE "SIP_HEADER(x-ahn-header)"').and_return "200 result=1 (#{value})" mock_call.sip_get_header("x-ahn-header").should == value end it "properly formats the AGI request using the method alias" do value = 'jason-was-here' mock_call.should_receive(:raw_response).once.with('GET VARIABLE "SIP_HEADER(x-ahn-header)"').and_return "200 result=1 (#{value})" mock_call.sip_header("x-ahn-header").should == value end end describe 'The #voicemail command' do include DialplanCommandTestHelpers it 'should not send the context name when none is given' do mailbox_number = 123 mock_call.should_receive(:execute).once.with('voicemail', 123, '').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail 123 } end it 'should send the context name when one is given' do mailbox_number, context_name = 333, 'doesntmatter' mock_call.should_receive(:execute).once.with('voicemail', "#{mailbox_number}@#{context_name}", '').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail(context_name => mailbox_number) } end it 'should pass in the s option if :skip => true' do mailbox_number = '012' mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 's').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :skip => true) } end it 'should combine mailbox numbers with the context name given when both are given' do pbx_should_respond_with_value 'SUCCESS' context = "lolcats" mailboxes = [1,2,3,4,5] mailboxes_with_context = mailboxes.map { |mailbox| "#{mailbox}@#{context}"} mock_call.should_receive(:execute).once.with('voicemail', mailboxes_with_context.join('&'), '') mock_call.voicemail context => mailboxes pbx_should_have_been_sent 'GET VARIABLE "VMSTATUS"' end it 'should raise an argument error if the mailbox number is not numerical' do the_following_code { mock_call.voicemail :foo => "bar" }.should raise_error ArgumentError end it 'should raise an argument error if too many arguments are supplied' do the_following_code { mock_call.voicemail "wtfisthisargument", :context_name => 123, :greeting => :busy }.should raise_error ArgumentError end it 'should raise an ArgumentError if multiple context names are given' do the_following_code { mock_call.voicemail :one => [1,2,3], :two => [11,22,33] }.should raise_error ArgumentError end it "should raise an ArgumentError when the :greeting value isn't recognized" do the_following_code { mock_call.voicemail :context_name => 123, :greeting => :zomgz }.should raise_error ArgumentError end it 'should pass in the u option if :greeting => :unavailable' do mailbox_number = '776' mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) } end it 'should pass in both the skip and greeting options if both are supplied' do mailbox_number = '4' mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) } end it 'should raise an ArgumentError if mailbox_number is blank?()' do the_following_code { mock_call.voicemail '' }.should raise_error ArgumentError the_following_code { mock_call.voicemail nil }.should raise_error ArgumentError end it 'should pass in the b option if :gretting => :busy' do mailbox_number = '1' mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'b').and_throw :sent_voicemail! should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :busy) } end it 'should return true if VMSTATUS == "SUCCESS"' do mock_call.should_receive(:execute).once mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "SUCCESS" mock_call.voicemail(3).should be true end it 'should return false if VMSTATUS == "USEREXIT"' do mock_call.should_receive(:execute).once mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "USEREXIT" mock_call.voicemail(2).should be false end it 'should return nil if VMSTATUS == "FAILED"' do mock_call.should_receive(:execute).once mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "FAILED" mock_call.voicemail(2).should be nil end end describe 'The voicemail_main command' do include DialplanCommandTestHelpers #it "should not pass in the context or the delimiting @ sign if you don't supply one" it "the :folder Hash key argument should wrap the value in a()" do folder = "foobar" mailbox = 81 mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","a(#{folder})") mock_call.voicemail_main :mailbox => mailbox, :folder => folder end it ':authenticate should pass in the "s" option if given false' do mailbox = 333 mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","s") mock_call.voicemail_main :mailbox => mailbox, :authenticate => false end it ':authenticate should pass in the s option if given false' do mailbox = 55 mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}") mock_call.voicemail_main :mailbox => mailbox, :authenticate => true end it 'should not pass any flags only a mailbox is given' do mailbox = "1" mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}") mock_call.voicemail_main :mailbox => mailbox end it 'when given no mailbox or context an empty string should be passed to execute as the first argument' do mock_call.should_receive(:execute).once.with("VoiceMailMain", "", "s") mock_call.voicemail_main :authenticate => false end it 'should properly concatenate the options when given multiple ones' do folder = "ohai" mailbox = 9999 mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}", "sa(#{folder})") mock_call.voicemail_main :mailbox => mailbox, :authenticate => false, :folder => folder end it 'should not require any arguments' do mock_call.should_receive(:execute).once.with("VoiceMailMain") mock_call.voicemail_main end it 'should pass in the "@context_name" part in if a :context is given and no mailbox is given' do context_name = "icanhascheezburger" mock_call.should_receive(:execute).once.with("VoiceMailMain", "@#{context_name}") mock_call.voicemail_main :context => context_name end it "should raise an exception if the folder has a space or malformed characters in it" do ["i has a space", "exclaim!", ",", ""].each do |bad_folder_name| the_following_code { mock_call.voicemail_main :mailbox => 123, :folder => bad_folder_name }.should raise_error ArgumentError end end end describe 'the check_voicemail command' do include DialplanCommandTestHelpers it "should simply execute voicemail_main with no arguments after warning" do flexmock(ahn_log.agi).should_receive(:warn).once.with(String) mock_call.should_receive(:voicemail_main).once.and_return :mocked_out mock_call.check_voicemail.should be :mocked_out end end describe "The queue management abstractions" do include DialplanCommandTestHelpers it 'should not create separate objects for queues with basically the same name' do mock_call.queue('foo').should be mock_call.queue('foo') mock_call.queue('bar').should be mock_call.queue(:bar) end it "queue() should return an instance of QueueProxy" do mock_call.queue("foobar").should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy end it "a QueueProxy should respond to join!(), members()" do %w[join! agents].each do |method| mock_call.queue('foobar').should respond_to(method) end end it 'a QueueProxy should return a QueueAgentsListProxy when members() is called' do mock_call.queue('foobar').agents.should be_a_kind_of(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueAgentsListProxy) end it 'join! should properly join a queue' do mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "FULL" mock_call.queue("foobaz").join! end it 'should return a symbol representing the result of joining the queue' do does_not_read_data_back mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "TIMEOUT" mock_call.queue('monkey').join!.should be :timeout pbx_should_have_been_sent 'EXEC queue "monkey"|""|""|""|""|""' end it 'should return :completed after joining the queue and being connected' do does_not_read_data_back mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return nil mock_call.queue('monkey').join!.should be :completed pbx_should_have_been_sent 'EXEC queue "monkey"|""|""|""|""|""' end it 'should join a queue with a timeout properly' do mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '60', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("foobaz").join! :timeout => 1.minute end it 'should join a queue with an announcement file properly' do mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', 'custom_announcement_file_here', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :announce => 'custom_announcement_file_here' end it 'should join a queue with an agi script properly' do mock_call.should_receive(:execute).once.with("queue", 'support', '', '', '', '','agi://localhost/queue_agi_test') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINUNAVAIL" mock_call.queue("support").join! :agi => 'agi://localhost/queue_agi_test' end it 'should join a queue with allow_transfer properly' do mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Tt", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_transfer => :everyone mock_call.should_receive(:execute).once.with("queue", "roflcopter", "T", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_transfer => :caller mock_call.should_receive(:execute).once.with("queue", "roflcopter", "t", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_transfer => :agent end it 'should join a queue with allow_hangup properly' do mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Hh", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_hangup => :everyone mock_call.should_receive(:execute).once.with("queue", "roflcopter", "H", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_hangup => :caller mock_call.should_receive(:execute).once.with("queue", "roflcopter", "h", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :allow_hangup => :agent end it 'should join a queue properly with the :play argument' do mock_call.should_receive(:execute).once.with("queue", "roflcopter", "r", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :play => :ringing mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', '', '', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue("roflcopter").join! :play => :music end it 'joining a queue with many options specified' do mock_call.should_receive(:execute).once.with("queue", "q", "rtHh", '', '', '120', '') mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY" mock_call.queue('q').join! :allow_transfer => :agent, :timeout => 2.minutes, :play => :ringing, :allow_hangup => :everyone end it 'join!() should raise an ArgumentError when unrecognized Hash key arguments are given' do the_following_code { mock_call.queue('iwearmysunglassesatnight').join! :misspelled => true }.should raise_error ArgumentError end it 'should fetch the members with the name given to queue()' do mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(jay)").and_return 5 mock_call.queue('jay').agents.size.should == 5 end it 'should not fetch a QUEUE_MEMBER_COUNT each time count() is called when caching is enabled' do mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(sales)").and_return 0 10.times do mock_call.queue('sales').agents(:cache => true).size end end it 'should raise an argument error if the members() method receives an unrecognized symbol' do the_following_code { mock_call.queue('foobarz').agents(:cached => true) # common typo }.should raise_error ArgumentError end it 'when fetching agents, it should properly split by the supported delimiters' do queue_name = "doesnt_matter" mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2') mock_call.queue(queue_name).agents(:cache => true).to_a.size.should == 3 end it 'when fetching agents, each array index should be an instance of AgentProxy' do queue_name = 'doesnt_matter' mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2') agents = mock_call.queue(queue_name).agents(:cache => true).to_a agents.size.should > 0 agents.each do |agent| agent.should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy end end it 'should properly retrieve metadata for an AgentProxy instance' do agent_id, metadata_name = '22', 'status' mock_env = flexmock "a mock ExecutionEnvironment" mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter" mock_env.should_receive(:variable).once.with("AGENT(#{agent_id}:#{metadata_name})") agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{agent_id}", mock_queue) agent.send(:agent_metadata, metadata_name) end it 'AgentProxy#logged_in? should return true if the "state" of an agent == LOGGEDIN' do mock_env = flexmock "a mock ExecutionEnvironment" mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter" agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new('Agent/123', mock_queue) flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDIN' agent.logged_in?.should be true flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDOUT' agent.logged_in?.should_not be true end it 'the AgentProxy should populate its own "id" property to the numerical ID of the "interface" with which it was constructed' do mock_queue = flexmock :name => "doesntmatter" id = '123' agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{id}", mock_queue) agent.id.should == id agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new(id, mock_queue) agent.id.should == id end it 'QueueAgentsListProxy#<<() should new the channel driver given as the argument to the system' do queue_name, agent_channel = "metasyntacticvariablesftw", "Agent/123" mock_call.should_receive('execute').once.with("AddQueueMember", queue_name, agent_channel, "", "", "", "") mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new agent_channel end it 'when a queue agent is dynamically added and the queue does not exist, a QueueDoesNotExistError should be raised' do does_not_read_data_back mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('NOSUCHQUEUE') the_following_code { mock_call.queue('this_should_not_exist').agents.new 'Agent/911' }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError pbx_should_have_been_sent 'EXEC AddQueueMember "this_should_not_exist"|"Agent/911"|""|""|""|""' end it 'when a queue agent is dynamiaclly added and the adding was successful, an AgentProxy should be returned' do mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("ADDED") mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "") mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lalala)").and_return "Agent/007,SIP/2302,Local/2510@from-internal" return_value = mock_call.queue('lalala').agents.new "Agent/007" return_value.kind_of?(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy).should be true end it 'when a queue agent is dynamiaclly added and the adding was unsuccessful, a false should be returned' do mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("MEMBERALREADY") mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "") return_value = mock_call.queue('lalala').agents.new "Agent/007" return_value.should be false end it 'should raise an argument when an unrecognized key is given to add()' do the_following_code { mock_call.queue('q').agents.new :foo => "bar" }.should raise_error ArgumentError end it 'should execute AddQueueMember with the penalty properly' do queue_name = 'name_does_not_matter' mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', 10, '', '','') mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new 'Agent/007', :penalty => 10 end it 'should execute AddQueueMember with the state_interface properly' do queue_name = 'name_does_not_matter' mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', '', '', '','SIP/2302') mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new 'Agent/007', :state_interface => 'SIP/2302' end it 'should execute AddQueueMember properly when the name is given' do queue_name, agent_name = 'name_does_not_matter', 'Jay Phillips' mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agents/007', '', '', agent_name,'') mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new 'Agents/007', :name => agent_name end it 'should execute AddQueueMember properly when the name, penalty, and interface is given' do queue_name, agent_name, interface, penalty = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4 mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name,'') mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty end it 'should execute AddQueueMember properly when the name, penalty, interface, and state_interface is given' do queue_name, agent_name, interface, penalty, state_interface = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4, 'SIP/2302' mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name, state_interface) mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED') mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal" mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty, :state_interface => state_interface end it 'should return a correct boolean for exists?()' do mock_call.should_receive(:execute).once.with("RemoveQueueMember", "kablamm", "SIP/AdhearsionQueueExistenceCheck") mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE" mock_call.queue("kablamm").exists?.should be true mock_call.should_receive(:execute).once.with("RemoveQueueMember", "monkey", "SIP/AdhearsionQueueExistenceCheck") mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE" mock_call.queue("monkey").exists?.should be false end it 'should pause an agent properly from a certain queue' do does_not_read_data_back mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008" mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED" agents = mock_call.queue('lolcats').agents :cache => true agents.last.pause!.should be true pbx_should_have_been_sent 'EXEC PauseQueueMember "lolcats"|"Agent/008"' end it 'should pause an agent properly from a certain queue and return false when the agent did not exist' do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008" mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "NOTFOUND" mock_call.should_receive(:execute).once.with("PauseQueueMember", 'lolcats', "Agent/008") agents = mock_call.queue('lolcats').agents :cache => true agents.last.pause!.should be false end it 'should pause an agent globally properly' do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(family)").and_return "Agent/Jay" mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED" mock_call.should_receive(:execute).once.with("PauseQueueMember", nil, "Agent/Jay") mock_call.queue('family').agents.first.pause! :everywhere => true end it 'should unpause an agent properly' do queue_name = "name with spaces" mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/Jay" mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED" mock_call.should_receive(:execute).once.with("UnpauseQueueMember", queue_name, "Agent/Jay") mock_call.queue(queue_name).agents.first.unpause!.should be true end it 'should unpause an agent globally properly' do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom" mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED" mock_call.should_receive(:execute).once.with("UnpauseQueueMember", nil, "Agent/Tom") mock_call.queue('FOO').agents.first.unpause!(:everywhere => true).should be true end it 'waiting_count for a queue that does exist' do mock_call.should_receive(:get_variable).once.with("QUEUE_WAITING_COUNT(q)").and_return "50" flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return true mock_call.queue('q').waiting_count.should == 50 end it 'waiting_count for a queue that does not exist' do the_following_code { flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return false mock_call.queue('q').waiting_count }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError end it 'empty? should call waiting_count' do queue = mock_call.queue 'testing_empty' flexmock(queue).should_receive(:waiting_count).once.and_return 0 queue.empty?.should be true queue = mock_call.queue 'testing_empty' flexmock(queue).should_receive(:waiting_count).once.and_return 99 queue.empty?.should_not be true end it 'any? should call waiting_count' do queue = mock_call.queue 'testing_empty' flexmock(queue).should_receive(:waiting_count).once.and_return 0 queue.any?.should be false queue = mock_call.queue 'testing_empty' flexmock(queue).should_receive(:waiting_count).once.and_return 99 queue.any?.should be true end it 'should remove an agent properly' do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom" mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom') mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "REMOVED" mock_call.queue('FOO').agents.first.remove!.should be true end it 'should remove an agent properly' do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom" mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom') mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE" mock_call.queue('FOO').agents.first.remove!.should be false end it "should raise a QueueDoesNotExistError when removing an agent from a queue that doesn't exist" do mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(cool_people)").and_return "Agent/ZeroCool" mock_call.should_receive(:execute).once.with("RemoveQueueMember", "cool_people", "Agent/ZeroCool") mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE" the_following_code { mock_call.queue("cool_people").agents.first.remove! }.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError end it "should log an agent in properly with no agent id given" do mock_call.should_receive(:execute).once.with('AgentLogin', nil, 's') mock_call.queue('barrel_o_agents').agents.login! end it 'should remove "Agent/" before the agent ID given if necessary when logging an agent in' do mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's') mock_call.queue('barrel_o_agents').agents.login! 'Agent/007' mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's') mock_call.queue('barrel_o_agents').agents.login! '007' end it 'should add an agent silently properly' do mock_call.should_receive(:execute).once.with('AgentLogin', '007', '') mock_call.queue('barrel_o_agents').agents.login! 'Agent/007', :silent => false mock_call.should_receive(:execute).once.with('AgentLogin', '008', 's') mock_call.queue('barrel_o_agents').agents.login! 'Agent/008', :silent => true end it 'logging an agent in should raise an ArgumentError is unrecognized arguments are given' do the_following_code { mock_call.queue('ohai').agents.login! 1,2,3,4,5 }.should raise_error ArgumentError the_following_code { mock_call.queue('lols').agents.login! 1337, :sssssilent => false }.should raise_error ArgumentError the_following_code { mock_call.queue('qwerty').agents.login! 777, 6,5,4,3,2,1, :wee => :wee }.should raise_error ArgumentError end end describe 'the menu() method' do include DialplanCommandTestHelpers it "should instantiate a new Menu object with only the Hash given as menu() options" do args = [1,2,3,4,5, {:timeout => 1.year, :tries => (1.0/0.0)}] flexmock(Adhearsion::VoIP::Menu).should_receive(:new).once. with(args.last).and_throw(:instantiating_menu!) should_throw(:instantiating_menu!) { mock_call.menu(*args) } end it "should jump to a context when a timeout is encountered and there is at least one exact match" do pbx_should_respond_with_successful_background_response ?5.ord pbx_should_respond_with_successful_background_response ?4.ord pbx_should_respond_with_a_wait_for_digit_timeout context_named_main = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :inside_main! } context_named_other = Adhearsion::DialPlan::DialplanContextProc.new(:other) { throw :inside_other! } flexmock(mock_call).should_receive(:main).once.and_return(context_named_main) flexmock(mock_call).should_receive(:other).never should_pass_control_to_a_context_that_throws :inside_main! do mock_call.menu do |link| link.main 54 link.other 543 end end 3.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' } end it "when the 'extension' variable is changed, it should be an instance of PhoneNumber" do pbx_should_respond_with_successful_background_response ?5.ord foobar_context = Adhearsion::DialPlan::DialplanContextProc.new(:foobar) { throw :foobar! } mock_call.should_receive(:foobar).once.and_return foobar_context should_pass_control_to_a_context_that_throws :foobar! do mock_call.menu do |link| link.foobar 5 end end 5.should === mock_call.extension mock_call.extension.__real_string.should == "5" pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' end end describe 'the Menu class' do include DialplanCommandTestHelpers it "should yield a MenuBuilder when instantiated" do lambda { Adhearsion::VoIP::Menu.new do |block_argument| block_argument.should be_a_kind_of Adhearsion::VoIP::MenuBuilder throw :inside_block end }.should throw_symbol :inside_block end it "should invoke wait_for_digit instead of interruptible_play when no sound files are given" do mock_call.should_receive(:wait_for_digit).once.with(5).and_return '#' mock_call.menu { |link| link.does_not_match 3 } end it 'should invoke interruptible_play when sound files are given only for the first digit' do sound_files = %w[i like big butts and i cannot lie] timeout = 1337 mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil mock_call.menu(sound_files, :timeout => timeout) { |link| link.qwerty 12345 } end it 'if the call to interruptible_play receives a timeout, it should execute wait_for_digit with the timeout given' do sound_files = %w[i like big butts and i cannot lie] timeout = 987 mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil mock_call.should_receive(:wait_for_digit).with(timeout).and_return mock_call.menu(sound_files, :timeout => timeout) { |link| link.foobar 911 } end it "should work when no files are given to be played and a timeout is reached on the first digit" do timeout = 12 [:on_premature_timeout, :on_failure].each do |usage_case| should_throw :got_here! do mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil # Simulates timeout mock_call.menu :timeout => timeout do |link| link.foobar 0 link.__send__(usage_case) { throw :got_here! } end end end end it "should default the timeout to five seconds" do mock_call.should_receive(:wait_for_digit).once.with(5).and_return nil mock_call.menu { |link| link.foobar 22 } end it "when matches fail due to timeouts, the menu should repeat :tries times" do tries, times_timed_out = 10, 0 tries.times do pbx_should_respond_with_successful_background_response ?4.ord pbx_should_respond_with_successful_background_response ?0.ord pbx_should_respond_with_a_wait_for_digit_timeout end should_throw :inside_failure_callback do mock_call.menu :tries => tries do |link| link.pattern_longer_than_our_test_input 400 link.on_premature_timeout { times_timed_out += 1 } link.on_invalid { raise "should never get here!" } link.on_failure { throw :inside_failure_callback } end end times_timed_out.should be tries 30.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' } end it "when matches fail due to invalid input, the menu should repeat :tries times" do tries = 10 times_invalid = 0 tries.times do pbx_should_respond_with_successful_background_response ?0.ord end should_throw :inside_failure_callback do mock_call.menu :tries => tries do |link| link.be_leet 1337 link.on_premature_timeout { raise "should never get here!" } link.on_invalid { times_invalid += 1 } link.on_failure { throw :inside_failure_callback } end end times_invalid.should be tries 10.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' } end it "invoke on_invalid callback when an invalid extension was entered" do pbx_should_respond_with_successful_background_response ?5.ord pbx_should_respond_with_successful_background_response ?5.ord pbx_should_respond_with_successful_background_response ?5.ord should_throw :inside_invalid_callback do mock_call.menu do |link| link.onetwothree 123 link.on_invalid { throw :inside_invalid_callback } end end pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' end it "invoke on_premature_timeout when a timeout is encountered" do pbx_should_respond_with_successful_background_response ?9.ord pbx_should_respond_with_a_wait_for_digit_timeout should_throw :inside_timeout do mock_call.menu :timeout => 1 do |link| link.something 999 link.on_premature_timeout { throw :inside_timeout } end end 2.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "1000"' } end end describe "the Menu class's high-level judgment" do include DialplanCommandTestHelpers it "should match things in ambiguous ranges properly" do pbx_should_respond_with_successful_background_response ?1.ord pbx_should_respond_with_successful_background_response ?1.ord pbx_should_respond_with_successful_background_response ?1.ord pbx_should_respond_with_a_wait_for_digit_timeout main_context = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :got_here! } mock_call.should_receive(:main).and_return main_context should_pass_control_to_a_context_that_throws :got_here! do mock_call.menu do |link| link.blah 1 link.main 11..11111 end end 111.should === mock_call.extension 4.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' } end it 'should match things in a range when there are many other non-matching patterns' do pbx_should_respond_with_successful_background_response ?9.ord pbx_should_respond_with_successful_background_response ?9.ord pbx_should_respond_with_successful_background_response ?5.ord conferences_context = Adhearsion::DialPlan::DialplanContextProc.new(:conferences) { throw :got_here! } mock_call.should_receive(:conferences).and_return conferences_context should_pass_control_to_a_context_that_throws :got_here! do mock_call.menu do |link| link.sales 1 link.tech_support 2 link.finance 3 link.conferences 900..999 end end 3.times { pbx_should_have_been_sent 'WAIT FOR DIGIT "5000"' } end end describe 'the MenuBuilder' do include MenuBuilderTestHelper attr_reader :builder before(:each) do @builder = Adhearsion::VoIP::MenuBuilder.new end it "should convert each pattern given to it into a MatchCalculator instance" do builder.tap do |link| link.foo 1,2,3 link.bar "4", "5", 6 end builder.weighted_match_calculators.size.should == 6 builder.weighted_match_calculators.each do |match_calculator| match_calculator.should be_a_kind_of Adhearsion::VoIP::MatchCalculator end end it "conflicting ranges" do builder.tap do |link| link.hundreds 100...200 link.thousands 1_000...2_000 link.tenthousands 10_000...20_000 end builder_should_match_with_these_quantities_of_calculated_matches \ 1 => { :exact_match_count => 0, :potential_match_count => 11100 }, 10 => { :exact_match_count => 0, :potential_match_count => 1110 }, 100 => { :exact_match_count => 1, :potential_match_count => 110 }, 1_000 => { :exact_match_count => 1, :potential_match_count => 10 }, 10_000 => { :exact_match_count => 1, :potential_match_count => 0 }, 100_000 => { :exact_match_count => 0, :potential_match_count => 0 } end it 'a String query ran against multiple Numeric patterns and a range' do builder.tap do |link| link.sales 1 link.tech_support 2 link.finance 3 link.conferences 900..999 end match = builder.calculate_matches_for "995" require 'pp' # pp match match.potential_match?.should_not be true match.exact_match?.should be true match.actual_exact_matches.should == ["995"] end it "multiple patterns given at once" do builder.tap do |link| link.multiple_patterns 1,2,3,4,5,6,7,8,9 link.multiple_patterns 100..199, 200..299, 300..399, 400..499, 500..599, 600..699, 700..799, 800..899, 900..999 end 1.upto 9 do |num| builder.calculate_matches_for(num).tap do |matches_of_num| matches_of_num.potential_match_count.should == 100 matches_of_num.exact_match_count.should == 1 end builder.calculate_matches_for((num * 100) + 5).tap do |matches_of_num| matches_of_num.potential_match_count.should == 0 matches_of_num.exact_match_count.should == 1 end end end it "numeric literals that don't match but ultimately would" do builder.tap do |link| link.nineninenine 999 link.shouldnt_match 4444 end builder.calculate_matches_for(9).potential_match_count.should == 1 end it "three fixnums that obviously don't conflict" do builder.tap do |link| link.one 1 link.two 2 link.three 3 end [[1,2,3,4,'#'], [1,1,1,0,0]].transpose.each do |(input,expected_matches)| matches = builder.calculate_matches_for input matches.exact_match_count.should be expected_matches end end it "numerical digits mixed with special digits" do builder.tap do |link| link.one '5*11#3' link.two '5***' link.three '###' end builder_should_match_with_these_quantities_of_calculated_matches \ '5' => { :potential_match_count => 2, :exact_match_count => 0 }, '*' => { :potential_match_count => 0, :exact_match_count => 0 }, '5**' => { :potential_match_count => 1, :exact_match_count => 0 }, '5*1' => { :potential_match_count => 1, :exact_match_count => 0 }, '5*11#3' => { :potential_match_count => 0, :exact_match_count => 1 }, '5*11#4' => { :potential_match_count => 0, :exact_match_count => 0 }, '5***' => { :potential_match_count => 0, :exact_match_count => 1 }, '###' => { :potential_match_count => 0, :exact_match_count => 1 }, '##*' => { :potential_match_count => 0, :exact_match_count => 0 } end it 'a Fixnum exact match conflicting with a Range that would ultimately match' do builder.tap do |link| link.single_digit 1 link.range 100..200 end matches = builder.calculate_matches_for 1 matches.potential_match_count.should == 100 end end describe 'say_digits command' do include DialplanCommandTestHelpers it 'Can execute the saydigits application using say_digits' do digits = "12345" pbx_should_respond_with_success mock_call.say_digits digits pbx_was_asked_to_execute "saydigits", digits end it 'Digits that include pound, star, and minus are considered valid' do digits = "1#2*3-4" mock_call.should_receive(:execute).once.with("saydigits", digits) mock_call.say_digits digits end it 'Cannot pass non-integers into say_digits. Will raise an ArgumentError' do the_following_code { mock_call.say_digits 'abc' }.should raise_error(ArgumentError) the_following_code { mock_call.say_digits '1.20' }.should raise_error(ArgumentError) end it 'Digits that start with a 0 are considered valid and parsed properly' do digits = "0123" mock_call.should_receive(:execute).once.with("saydigits", digits) mock_call.say_digits digits end end describe 'the enable_feature command' do include DialplanCommandTestHelpers it 'it should fetch the variable for DYNAMIC_FEATURES at first' do mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_throw :got_variable should_throw :got_variable do mock_call.enable_feature :foobar end end it 'should check Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS mapping for configuration setters' do feature_name = :attended_transfer assertion = lambda do |arg| arg.should == :this_is_the_right_arg throw :inside_assertion! end # I had to do this ugly hack because of a bug in Flexmock which prevented me from mocking out Hash#[] :( old_hash_feature_extension = Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] begin Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = assertion # should_throw :inside_assertion! do mock_call.enable_feature(feature_name, :this_is_the_right_arg) end ensure Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = old_hash_feature_extension end end it 'should separate enabled features with a "#"' do mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return("one") mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'one#bar') mock_call.enable_feature "bar" end it 'should not add duplicate enabled dynamic features' do mock_call.should_receive(:variable).once.and_return('eins#zwei') mock_call.enable_feature "eins" end it 'should raise an ArgumentError if optional options are given when DYNAMIC_FEATURE_EXTENSIONS does not have a key for the feature name' do the_following_code { mock_call.enable_feature :this_is_not_recognized, :these_features => "are not going to be recognized" }.should raise_error ArgumentError end it 'enabling :attended_transfer should actually enable the atxfer feature' do mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return '' mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer') mock_call.enable_feature :attended_transfer end it 'the :context optional option when enabling :attended_transfer should set the TRANSFER_CONTEXT variable to the String supplied as a Hash value' do context_name = "direct_dial" mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return '' mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer') mock_call.should_receive(:variable).once.with("TRANSFER_CONTEXT" => context_name) mock_call.enable_feature :attended_transfer, :context => context_name end it 'enabling :attended_transfer should not add a duplicate if atxfer has been enabled, but it should still set the TRANSFER_CONTEXT variable' do context_name = 'blah' mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer' mock_call.should_receive(:variable).once.with('TRANSFER_CONTEXT' => context_name) mock_call.enable_feature :attended_transfer, :context => context_name end end describe 'the disable_feature command' do include DialplanCommandTestHelpers it "should properly remove the feature from the DYNAMIC_FEATURES variable" do mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'foobar#qaz' mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES' => 'qaz') mock_call.disable_feature "foobar" end it "should not re-set the variable if the feature wasn't enabled in the first place" do mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer' mock_call.should_receive(:variable).never mock_call.disable_feature "jay" end end describe 'jump_to command' do include DialplanCommandTestHelpers it 'when given a DialplanContextProc as the only argument, it should raise a ControlPassingException with that as the target' do # Having to do this ugly hack because I can't do anything with the exception once I set up an expectation with it normally. dialplan_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {} should_throw :finishing_the_rescue_block do begin mock_call.jump_to dialplan_context rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe cpe.target.should be dialplan_context throw :finishing_the_rescue_block end end end it 'when given a String, it should perform a lookup of the context name' do context_name = 'cool_context' mock_call.should_receive(context_name).once.and_throw :found_context! should_throw :found_context! do mock_call.jump_to context_name end end it 'when given a Symbol, it should perform a lookup of the context name' do context_name = :cool_context mock_call.should_receive(context_name).once.and_throw :found_context! should_throw :found_context! do mock_call.jump_to context_name end end it "a clearly invalid context name should raise a ContextNotFoundException" do bad_context_name = ' ZOMGS this is A REALLY! STUPID context name . wtf were you thinking?!' the_following_code { mock_call.jump_to bad_context_name }.should raise_error Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException end it 'when given an :extension override, the new value should be boxed in a PhoneNumber' do my_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {} begin mock_call.jump_to my_context, :extension => 1337 rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException # Eating this exception end mock_call.extension.__real_num.should == 1337 end it 'other overrides should be simply metadef()d' do test_context = Adhearsion::DialPlan::DialplanContextProc.new("test_context") {} begin mock_call.jump_to test_context, :caller_id => 1_444_555_6666 rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException # Eating this exception end mock_call.caller_id.should == 1_444_555_6666 end end describe "get variable command" do include DialplanCommandTestHelpers it "Getting a variable that isn't set returns nothing" do pbx_should_respond_with "200 result=0" mock_call.get_variable('OMGURFACE').should be nil pbx_should_have_been_sent 'GET VARIABLE "OMGURFACE"' end it 'An empty variable should return an empty String' do pbx_should_respond_with_value "" mock_call.get_variable('kablamm').should == "" pbx_should_have_been_sent 'GET VARIABLE "kablamm"' end it "Getting a variable that is set returns its value" do unique_id = "1192470850.1" pbx_should_respond_with_value unique_id variable_value_returned = mock_call.get_variable('UNIQUEID') variable_value_returned.should == unique_id pbx_should_have_been_sent 'GET VARIABLE "UNIQUEID"' end end describe "duration_of command" do include DialplanCommandTestHelpers it "Duration of must take a block" do the_following_code { mock_call.duration_of }.should raise_error(LocalJumpError) end it "Passed block to duration of is actually executed" do the_following_code { mock_call.duration_of { throw :inside_duration_of } }.should throw_symbol :inside_duration_of end it "Duration of block is returned" do start_time = Time.parse('9:25:00') end_time = Time.parse('9:25:05') expected_duration = end_time - start_time flexmock(Time).should_receive(:now).twice.and_return(start_time, end_time) duration = mock_call.duration_of { # This doesn't matter } duration.should == expected_duration end end describe "Dial command" do include DialplanCommandTestHelpers it "should set the caller id if the caller_id option is specified" do does_not_read_data_back mock_call.should_receive(:set_caller_id_number).once mock_call.dial 123, :caller_id => "1234678901" pbx_should_have_been_sent 'EXEC Dial "123"|""|""' end it 'should raise an exception when unknown hash key arguments are given to it' do the_following_code { mock_call.dial 123, :asjndfhasndfahsbdfbhasbdfhabsd => "asbdfhabshdfbajhshfbajsf" }.should raise_error ArgumentError end it 'should set the caller ID name when given the :name hash key argument' do does_not_read_data_back name = "Jay Phillips" mock_call.should_receive(:set_caller_id_name).once.with(name) mock_call.dial "BlahBlahBlah", :name => name pbx_should_have_been_sent 'EXEC Dial "BlahBlahBlah"|""|""' end it "should raise an exception when a non-numerical caller_id is specified" do the_following_code { mock_call.dial 911, :caller_id => "zomgz" }.should raise_error ArgumentError end it "should not raise an exception when a caller_id is specified in E.164 format (with '+' sign)" do the_following_code { mock_call.dial 911, :caller_id => "+123456789" }.should_not raise_error ArgumentError pbx_should_have_been_sent 'SET VARIABLE "CALLERID(num)" "+123456789"' end it 'should pass the value of the :confirm key to dial_macro_option_compiler()' do does_not_read_data_back value_of_confirm_key = {:play => "ohai", :timeout => 30} mock_call.should_receive(:dial_macro_option_compiler).once.with value_of_confirm_key mock_call.dial 123, :confirm => value_of_confirm_key pbx_should_have_been_sent 'EXEC Dial "123"|""|""' end it "should add the return value of dial_macro_option_compiler to the :options key's value given to the dial command" do channel = "SIP/1337" macro_arg = "THISSHOULDGETPASSEDTOASTERISK" timeout = 10 options = 'hH' mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg) mock_call.dial channel, :for => timeout, :confirm => true, :options => options end it 'should add the return value of dial_macro_option_compiler to the options field when NO :options are given' do channel = "SIP/1337" macro_arg = "THISSHOULDGETPASSEDTOASTERISK" timeout = 10 options = 'hH' mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg) mock_call.dial channel, :for => timeout, :confirm => true, :options => options end end describe "The Dial command's :confirm option setting builder" do include DialplanCommandTestHelpers attr_reader :formatter before :each do @formatter = mock_call.method :dial_macro_option_compiler end it 'should allow passing in the :confirm named argument with true' do the_following_code { formatter.call true }.should_not raise_error ArgumentError end it 'should separate :play options with "++"' do sound_files = *1..10 formatter.call(:play => sound_files).should include sound_files.join('++') end it 'should raise an ArgumentError if an invalid Hash key is given' do the_following_code { formatter.call :this_symbol_is_not_valid => 123 }.should raise_error ArgumentError end it "should raise an ArgumentError if the argument's class is not recognized" do the_following_code { formatter.call Time.now # Time is an example strange case }.should raise_error ArgumentError end it 'should return the contents within a M() Dial argument' do formatter.call(true).should =~ /^M\(.+\)$/ end it 'should replace the default macro name when given the :macro options' do macro_name = "ivegotalovelybunchofcoconuts" formatter.call(:macro => macro_name).starts_with?("M(#{macro_name}").should be true end it 'should allow a symbol macro name' do the_following_code { formatter.call(:macro => :foo) }.should_not raise_error ArgumentError end it 'should only allow alphanumeric and underscores in the macro name' do bad_options = ["this has a space", "foo,bar", 'exists?', 'x^z', '', "!&@&*^!@"] bad_options.each do |bad_option| the_following_code { formatter.call(:macro => bad_option) }.should raise_error ArgumentError end end it 'should confirm :timeout => :none to 0' do formatter.call(:timeout => :none).should include "timeout:0" end it 'should separate the macro name and the arguments with a caret (^)' do formatter.call(:macro => "jay").should =~ /M\(jay\^.+/ end it 'should raise an ArgumentError if a caret existed anywhere in the resulting String' do # FIXME: Duplicate hash key bad_options = [{:play => "foo^bar", :key => "^", :play => ["hello-world", 'lol^cats']}] bad_options.each do |bad_option| the_following_code { formatter.call(bad_option) }.should raise_error ArgumentError end end it 'should raise an ArgumentError if the :key is not [0-9#*]' do bad_options = %w[& A $ @ . )] bad_options.each do |bad_option| the_following_code { formatter.call :key => bad_option }.should raise_error ArgumentError end end it 'should raise an ArgumentError if the key is longer than one digit' do the_following_code { formatter.call :key => "55" }.should raise_error ArgumentError end it 'should raise an ArgumentError if the timeout is not numeric and not :none' do bad_options = [:nonee, Time.now, method(:inspect)] bad_options.each do |bad_option| the_following_code { formatter.call bad_option }.should raise_error ArgumentError end end it 'should support passing a String argument as a timeout' do the_following_code { formatter.call :timeout => "123" }.should_not raise_error ArgumentError end it 'should raise an ArgumentError if given a Float' do the_following_code { formatter.call :timeout => 100.0012 }.should raise_error ArgumentError end it 'should allow passing a ActiveSupport::Duration to :timeout' do the_following_code { formatter.call :timeout => 3.minutes }.should_not raise_error ArgumentError end end describe 'the dtmf command' do include DialplanCommandTestHelpers it 'should send the proper AGI command' do does_not_read_data_back digits = '8404#4*' mock_call.dtmf digits pbx_should_have_been_sent "EXEC SendDTMF \"#{digits}\"" end end describe "the last_dial_status command and family" do include DialplanCommandTestHelpers it 'should convert common DIALSTATUS variables to their appropriate symbols' do mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER') mock_call.last_dial_status.should be :answered mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CONGESTION') mock_call.last_dial_status.should be :congested mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("BUSY") mock_call.last_dial_status.should be :busy mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CANCEL") mock_call.last_dial_status.should be :cancelled mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("NOANSWER") mock_call.last_dial_status.should be :unanswered mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CHANUNAVAIL") mock_call.last_dial_status.should be :channel_unavailable mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("THISISNOTVALID") mock_call.last_dial_status.should be :unknown end it 'last_dial_successful? should return true if last_dial_status == :answered' do mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER') mock_call.last_dial_successful?.should be true mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL') mock_call.last_dial_successful?.should be false end it 'last_dial_unsuccessful? should be the opposite of last_dial_successful?' do mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER') mock_call.last_dial_unsuccessful?.should be false mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL') mock_call.last_dial_unsuccessful?.should be true end it 'last_dial_status should not blow up if variable() returns nil. it should return :cancelled' do the_following_code { mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil mock_call.last_dial_status.should be :cancelled mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil mock_call.last_dial_successful?.should be false }.should_not raise_error end end describe "set_caller_id_number command" do include DialplanCommandTestHelpers it "should encapsulate the number with quotes" do caller_id = "14445556666" mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(num)" "#{caller_id}")).and_return true mock_call.send(:set_caller_id_number, caller_id) end end describe 'set_caller_id_name command' do include DialplanCommandTestHelpers it "should wrap the name in quotes" do name = "Jay Phillips" mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(name)" "#{name}")).and_return true mock_call.send(:set_caller_id_name, name) end end describe "speak command" do include DialplanCommandTestHelpers before :all do @speech_engines = Adhearsion::VoIP::Asterisk::Commands::SpeechEngines end it "executes the command SpeechEngine gives it based on the engine name" do pbx_should_respond_with_success flexmock(@speech_engines).should_receive(:cepstral).once mock_call.speak "Spoken text doesn't matter", :engine => :cepstral end it "raises an InvalidSpeechEngine exception when the engine is 'none'" do the_following_code { mock_call.speak("o hai!", :engine => :none) }.should raise_error @speech_engines::InvalidSpeechEngine end it "should default its engine to :none" do the_following_code { flexmock(@speech_engines).should_receive(:none).once. and_raise(@speech_engines::InvalidSpeechEngine) mock_call.speak "ruby ruby ruby ruby!" }.should raise_error @speech_engines::InvalidSpeechEngine end it 'should default to a configured TTS engine' do Adhearsion::Configuration.configure {|c| c.asterisk.speech_engine = :unimrcp } flexmock(@speech_engines).should_receive(:unimrcp).once mock_call.speak 'What say you, sir?' end it 'should allow the caller to override the default configured TTS engine' do Adhearsion::Configuration.configure {|c| c.asterisk.speech_engine = :unimrcp } flexmock(@speech_engines).should_receive(:cepstral).once mock_call.speak 'What say you now, sir?', :engine => :cepstral end it 'should default to uninterruptible TTS rendering' do flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => false}) mock_call.speak 'hello', :engine => :cepstral end it 'should allow setting TTS rendering interruptible' do flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => true}) mock_call.speak 'hello', :engine => :cepstral, :interruptible => true end it "should stringify the text" do flexmock(@speech_engines).should_receive(:cepstral).once.with(mock_call, 'hello', {:interruptible => false}) mock_call.speak :hello, :engine => :cepstral end context "with the engine :cepstral" do it "should execute Swift"do pbx_should_respond_with_value 0 mock_call.should_receive(:execute).with('Swift', 'hello') @speech_engines.cepstral(mock_call, 'hello') @output.read.should == "GET VARIABLE \"SWIFT_DTMF\"\n" end it "should properly escape commas in the TTS string" do pbx_should_respond_with_value 0 mock_call.should_receive(:execute).with('Swift', 'Once\, a long\, long time ago\, ...') @speech_engines.cepstral(mock_call, 'Once, a long, long time ago, ...') @output.read.should == "GET VARIABLE \"SWIFT_DTMF\"\n" end it "should properly escape double-quotes (for XML) in the TTS string" do mock_call.should_receive(:raw_response).once.with('EXEC MRCPSynth " Howdy, stranger. How are you today? "').and_return pbx_success_response @speech_engines.unimrcp(mock_call, ' Howdy, stranger. How are you today? ') end context "with barge in digits set" do it "should return the digit when :interruptible = true" do mock_call.should_receive(:execute).once.with('Swift', 'hello', 1, 1).and_return pbx_success_response mock_call.should_receive(:get_variable).once.with('SWIFT_DTMF').and_return ?1 @speech_engines.cepstral(mock_call, 'hello', :interruptible => true).should == ?1 end end end context "with the engine :unimrcp" do it "should execute MRCPSynth" do pbx_should_respond_with_success mock_call.should_receive(:execute).with('MRCPSynth', 'hello').once.and_return pbx_success_response @speech_engines.unimrcp(mock_call, 'hello') end context "with barge in digits set" do it "should pass the i option for MRCPSynth" do mock_call.should_receive(:execute).with('MRCPSynth', 'hello', 'i=any').once.and_return pbx_result_response 0 @speech_engines.unimrcp(mock_call, 'hello', :interrupt_digits => 'any') end end end context "with the engine :tropo" do it "should execute tropo" do pbx_should_respond_with_success response = '200 result=' + {:interpretation => '1'}.to_json mock_call.should_receive(:raw_response).with(/Ask/i, 'hello').once.and_return response @speech_engines.tropo(mock_call, 'hello').should == "1" end context "with :interruptible set to false"do it "should pass the :bargein => false option for Tropo Ask" do response = '200 result=' + {:interpretation => '1'}.to_json mock_call.should_receive(:raw_response).with(/Ask/i, 'hello', {:bargein => false}.to_json).once.and_return response @speech_engines.tropo(mock_call, 'hello', :interruptible => false) end end end it "properly escapes spoken text" do pending 'What are the escaping needs?' end end describe 'The join command' do include DialplanCommandTestHelpers it "should pass the 'd' flag when no options are given" do conference_id = "123" mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "d", nil) mock_call.join conference_id end it "should pass through any given flags with 'd' appended to it if necessary" do conference_id, flags = "1000", "zomgs" mock_call.should_receive(:execute).once.with("MeetMe", conference_id, flags + "d", nil) mock_call.join conference_id, :options => flags end it "should NOT pass the 'd' flag when requiring static conferences" do conference_id, options = "1000", {:use_static_conf => true} mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "", nil) mock_call.join conference_id, options end it "should raise an ArgumentError when the pin is not numerical" do the_following_code { mock_call.should_receive(:execute).never mock_call.join 3333, :pin => "letters are bad, mkay?!1" }.should raise_error ArgumentError end it "should strip out illegal characters from a conference name" do bizarre_conference_name = "a- bc!d&&e--`" normal_conference_name = "abcde" mock_call.should_receive(:execute).twice.with("MeetMe", normal_conference_name, "d", nil) mock_call.join bizarre_conference_name mock_call.join normal_conference_name end it "should allow textual conference names" do the_following_code { mock_call.should_receive(:execute).once.with_any_args mock_call.join "david bowie's pants" }.should_not raise_error end end describe 'the DialPlan::ConfirmationManager' do include ConfirmationManagerTestHelper include DialplanCommandTestHelpers attr_reader :example_encoded_hash, :example_encoded_hash_without_macro_name before :each do @example_encoded_hash_without_macro_name = 'timeout:20!play:foo-bar++qaz_qwerty.gsm!key:#' @example_encoded_hash = 'confirm!' + @example_encoded_hash_without_macro_name end it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash).should == {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'} end it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash_without_macro_name).should == {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'} end it '::decode_hash() should split the sound files in the :play key to an array by splitting by "++"' do decoded_sound_files = Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash)[:play] decoded_sound_files.should be_a_kind_of Array decoded_sound_files.size.should == 2 end it 'a call to a party which is acknowledged with the proper key during the call to interruptible_play' do variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'} encoded_variables = {:network_script => encode_hash(variables)} io_mock = StringIO.new mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk mock_call.should_receive(:variables).once.and_return encoded_variables sound_files = variables[:play] manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call) flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord flexstub(manager).should_receive(:raw_response).and_return nil flexmock(manager).should_receive(:answer).once flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#' manager.handle end it 'when an timeout is encountered, it should set the MACRO_RESULT variable to CONTINUE' do variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'} encoded_variables = {:network_script => encode_hash(variables)} io_mock = StringIO.new mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk mock_call.should_receive(:variables).once.and_return encoded_variables sound_files = variables[:play] manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call) flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord flexstub(manager).should_receive(:raw_response).and_return nil flexmock(manager).should_receive(:answer).once flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return nil flexmock(manager).should_receive(:variable).once.with("MACRO_RESULT" => 'CONTINUE') manager.handle end it 'should wait the :timeout number of seconds if no digit was received when playing the files and continue when the right key is pressed' do variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'} encoded_variables = {:network_script => encode_hash(variables)} io_mock = StringIO.new mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk mock_call.should_receive(:variables).once.and_return encoded_variables sound_files = variables[:play] manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call) flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord flexstub(manager).should_receive(:raw_response).and_return nil flexmock(manager).should_receive(:answer).once flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return '#' manager.handle end it 'should restart playback if the key received was not recognized' do variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '2', :macro => 'confirmer'} encoded_variables = {:network_script => encode_hash(variables)} io_mock = StringIO.new mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk mock_call.should_receive(:variables).once.and_return encoded_variables sound_files = variables[:play] manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call) flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord flexstub(manager).should_receive(:raw_response).and_return nil flexmock(manager).should_receive(:answer).once flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '3' # not :key flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#' # not :key flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '1' # not :key flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '2' # matches :key flexmock(manager).should_receive(:wait_for_digit).never # We never let it get to the point where it may timeout flexmock(manager).should_receive(:variable).never # We succeed by not setting the MACRO_RESULT variable manager.handle end end describe 'say_phonetic command' do include DialplanCommandTestHelpers it 'Can execute the sayphonetic application using say_phonetic' do text = 'Say This' mock_call.should_receive(:execute).once.with("sayphonetic", text) mock_call.say_phonetic text end it 'Can use special characters with say_phonetic' do text = '*Say This!*' mock_call.should_receive(:execute).once.with("sayphonetic", text) mock_call.say_phonetic text end end describe 'say_chars command' do include DialplanCommandTestHelpers it 'Can execute the sayalpha application using say_chars' do text = 'ha124d9' mock_call.should_receive(:execute).once.with("sayalpha", text) mock_call.say_chars text end it 'Can use special characters with say_chars' do text = "1a2.#" mock_call.should_receive(:execute).once.with("sayalpha", text) mock_call.say_chars text end end