# encoding: utf-8 require 'spec_helper' module Punchblock module Translator class Asterisk module Component describe MRCPPrompt do include HasMockCallbackConnection let(:ami_client) { double('AMI') } let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection } let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection } let :ssml_doc do RubySpeech::SSML.draw do say_as(:interpret_as => :cardinal) { 'FOO' } end end let :voice_grammar do RubySpeech::GRXML.draw :mode => 'voice', :root => 'color' do rule id: 'color' do one_of do item { 'red' } item { 'blue' } item { 'green' } end end end end let :dtmf_grammar do RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do rule id: 'digit' do one_of do 0.upto(9) { |d| item { d.to_s } } end end rule id: 'pin', scope: 'public' do item repeat: '2' do ruleref uri: '#digit' end end end end let(:grammar) { voice_grammar } let(:output_command_opts) { {} } let :output_command_options do { render_document: {value: ssml_doc} }.merge(output_command_opts) end let(:input_command_opts) { {} } let :input_command_options do { grammar: {value: grammar} }.merge(input_command_opts) end let(:command_options) { {} } let :output_command do Punchblock::Component::Output.new output_command_options end let :input_command do Punchblock::Component::Input.new input_command_options end let :original_command do Punchblock::Component::Prompt.new output_command, input_command, command_options end let(:recog_status) { 'OK' } let(:recog_completion_cause) { '000' } let(:recog_result) { "%3C?xml%20version=%221.0%22?%3E%3Cresult%3E%0D%0A%3Cinterpretation%20grammar=%22session:grammar-0%22%20confidence=%220.43%22%3E%3Cinput%20mode=%22speech%22%3EHello%3C/input%3E%3Cinstance%3EHello%3C/instance%3E%3C/interpretation%3E%3C/result%3E" } subject { described_class.new original_command, mock_call } before do original_command.request! { 'RECOG_STATUS' => recog_status, 'RECOG_COMPLETION_CAUSE' => recog_completion_cause, 'RECOG_RESULT' => recog_result }.each do |var, val| allow(mock_call).to receive(:channel_var).with(var).and_return val end end context 'with an invalid recognizer' do let(:input_command_opts) { { recognizer: 'foobar' } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'The recognizer foobar is unsupported.' expect(original_command.response(0.1)).to eq(error) end end [:asterisk].each do |recognizer| context "with a recognizer #{recognizer.inspect}" do let(:input_command_opts) { { recognizer: recognizer } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', "The recognizer #{recognizer} is unsupported." expect(original_command.response(0.1)).to eq(error) end end end def expect_mrcpsynth_with_options(options) expect_app_with_options 'MRCPSynth', options end def expect_synthandrecog_with_options(options) expect_app_with_options 'SynthAndRecog', options end def expect_app_with_options(app, options) expect(mock_call).to receive(:execute_agi_command).once.with { |*args| expect(args[0]).to eq("EXEC #{app}") expect(args[1]).to match options }.and_return code: 200, result: 1 end describe 'Output#document' do context 'with multiple inline documents' do let(:output_command_options) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'Only one document is allowed.' expect(original_command.response(0.1)).to eq(error) end end context 'with multiple documents by URI' do let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}, {url: 'http://example.com/doc2.ssml'}] } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'Only one document is allowed.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:output_command_options) { {} } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'An SSML document is required.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#renderer' do [nil, :unimrcp].each do |renderer| context renderer.to_s do let(:output_command_opts) { { renderer: renderer } } it "should return a ref and execute SynthAndRecog" do param = [ssml_doc.to_doc, grammar.to_doc].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',') expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1 subject.execute expect(original_command.response(0.1)).to be_a Ref end context "when SynthAndRecog completes" do shared_context "with a match" do let :expected_nlsml do RubySpeech::NLSML.draw do interpretation grammar: 'session:grammar-0', confidence: 0.43 do input 'Hello', mode: :speech instance 'Hello' end end end it 'should send a match complete event' do expected_complete_reason = Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason) end end context "with a match cause" do %w{000 008 012}.each do |code| context "when the MRCP recognition response code is #{code}" do let(:recog_completion_cause) { code } it_behaves_like 'with a match' end end end context "with a nomatch cause" do %w{001 003 013 014 015}.each do |code| context "with value #{code}" do let(:recog_completion_cause) { code } it 'should send a nomatch complete event' do expected_complete_reason = Punchblock::Component::Input::Complete::NoMatch.new expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason) end end end end context "with a noinput cause" do %w{002 011}.each do |code| context "with value #{code}" do let(:recog_completion_cause) { code } specify do expected_complete_reason = Punchblock::Component::Input::Complete::NoInput.new expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason) end end end end shared_context 'should send an error complete event' do specify do expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1 subject.execute complete_reason = original_command.complete_event(0.1).reason expect(complete_reason).to be_a Punchblock::Event::Complete::Error end end context 'with an error cause' do %w{004 005 006 007 009 010 016}.each do |code| context "when the MRCP recognition response code is #{code}" do let(:recog_completion_cause) { code } it_behaves_like 'should send an error complete event' end end end context 'with an invalid cause' do let(:recog_completion_cause) { '999' } it_behaves_like 'should send an error complete event' end context "when the RECOG_STATUS variable is set to 'ERROR'" do let(:recog_status) { 'ERROR' } it_behaves_like 'should send an error complete event' end end context "when we get a RubyAMI Error" do it "should send an error complete event" do error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' } expect(mock_call).to receive(:execute_agi_command).and_raise error subject.execute complete_reason = original_command.complete_event(0.1).reason expect(complete_reason).to be_a Punchblock::Event::Complete::Error expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'") end end context "when the channel is gone" do it "should send an error complete event" do error = ChannelGoneError.new 'FooBar' expect(mock_call).to receive(:execute_agi_command).and_raise error subject.execute complete_reason = original_command.complete_event(0.1).reason expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup end end end end [:foobar, :swift, :asterisk].each do |renderer| context renderer do let(:output_command_opts) { { renderer: renderer } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', "The renderer #{renderer} is unsupported." expect(original_command.response(0.1)).to eq(error) end end end end describe 'barge_in' do context 'unset' do let(:command_options) { { barge_in: nil } } it 'should pass the b=1 option to SynthAndRecog' do expect_synthandrecog_with_options(/b=1/) subject.execute end end context 'true' do let(:command_options) { { barge_in: true } } it 'should pass the b=1 option to SynthAndRecog' do expect_synthandrecog_with_options(/b=1/) subject.execute end end context 'false' do let(:command_options) { { barge_in: false } } it 'should pass the b=0 option to SynthAndRecog' do expect_synthandrecog_with_options(/b=0/) subject.execute end end end describe 'Output#voice' do context 'unset' do let(:output_command_opts) { { voice: nil } } it 'should not pass the vn option to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { voice: 'alison' } } it 'should pass the vn option to SynthAndRecog' do expect_synthandrecog_with_options(/vn=alison/) subject.execute end end end describe 'Output#start-offset' do context 'unset' do let(:output_command_opts) { { start_offset: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { start_offset: 10 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#start-paused' do context 'false' do let(:output_command_opts) { { start_paused: false } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'true' do let(:output_command_opts) { { start_paused: true } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#repeat-interval' do context 'unset' do let(:output_command_opts) { { repeat_interval: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { repeat_interval: 10 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#repeat-times' do context 'unset' do let(:output_command_opts) { { repeat_times: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { repeat_times: 2 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#max-time' do context 'unset' do let(:output_command_opts) { { max_time: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { max_time: 30 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#interrupt_on' do context 'unset' do let(:output_command_opts) { { interrupt_on: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'set' do let(:output_command_opts) { { interrupt_on: :dtmf } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A interrupt_on value is unsupported on Asterisk.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Output#grammar' do context 'with multiple inline grammars' do let(:input_command_options) { { grammars: [{value: voice_grammar}, {value: dtmf_grammar}] } } it "should return a ref and execute SynthAndRecog" do param = [ssml_doc.to_doc, [voice_grammar.to_doc.to_s, dtmf_grammar.to_doc.to_s].join(',')].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',') expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1 subject.execute expect(original_command.response(0.1)).to be_a Ref end end context 'with multiple grammars by URI' do let(:input_command_options) { { grammars: [{url: 'http://example.com/grammar1.grxml'}, {url: 'http://example.com/grammar2.grxml'}] } } it "should return a ref and execute SynthAndRecog" do param = [ssml_doc.to_doc, ['http://example.com/grammar1.grxml', 'http://example.com/grammar2.grxml'].join(',')].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',') expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1 subject.execute expect(original_command.response(0.1)).to be_a Ref end end context 'unset' do let(:input_command_options) { {} } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A grammar is required.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Input#initial-timeout' do context 'a positive number' do let(:input_command_opts) { { initial_timeout: 1000 } } it 'should pass the nit option to SynthAndRecog' do expect_synthandrecog_with_options(/nit=1000/) subject.execute end end context '-1' do let(:input_command_opts) { { initial_timeout: -1 } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'unset' do let(:input_command_opts) { { initial_timeout: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'a negative number other than -1' do let(:input_command_opts) { { initial_timeout: -1000 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'An initial-timeout value must be -1 or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Input#recognition-timeout' do context 'a positive number' do let(:input_command_opts) { { recognition_timeout: 1000 } } it 'should pass the t option to SynthAndRecog' do expect_synthandrecog_with_options(/t=1000/) subject.execute end end context '0' do let(:input_command_opts) { { recognition_timeout: 0 } } it 'should pass the t option to SynthAndRecog' do expect_synthandrecog_with_options(/t=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { recognition_timeout: -1000 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A recognition-timeout value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { recognition_timeout: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#inter-digit-timeout' do context 'a positive number' do let(:input_command_opts) { { inter_digit_timeout: 1000 } } it 'should pass the dit option to SynthAndRecog' do expect_synthandrecog_with_options(/dit=1000/) subject.execute end end context '-1' do let(:input_command_opts) { { inter_digit_timeout: -1 } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'unset' do let(:input_command_opts) { { inter_digit_timeout: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end context 'a negative number other than -1' do let(:input_command_opts) { { inter_digit_timeout: -1000 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'An inter-digit-timeout value must be -1 or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end end describe 'Input#max-silence' do context 'a positive number' do let(:input_command_opts) { { max_silence: 1000 } } it 'should pass the sint option to SynthAndRecog' do expect_synthandrecog_with_options(/sint=1000/) subject.execute end end context '0' do let(:input_command_opts) { { max_silence: 0 } } it 'should pass the sint option to SynthAndRecog' do expect_synthandrecog_with_options(/sint=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { max_silence: -1000 } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A max-silence value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { max_silence: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#speech-complete-timeout' do context 'a positive number' do let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => 1000 } } } it 'should pass the sct option to SynthAndRecog' do expect_synthandrecog_with_options(/sct=1000/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => 0 } } } it 'should pass the sct option to SynthAndRecog' do expect_synthandrecog_with_options(/sct=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A speech-complete-timeout value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Speed-Vs-Accuracy)' do context 'a positive number' do let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => 1 } } } it 'should pass the sva option to SynthAndRecog' do expect_synthandrecog_with_options(/sva=1/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => 0 } } } it 'should pass the sva option to SynthAndRecog' do expect_synthandrecog_with_options(/sva=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A speed-vs-accuracy value must be a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(N-Best-List-Length)' do context 'a positive number' do let(:input_command_opts) { { headers: {"N-Best-List-Length" => 5 } } } it 'should pass the nb option to SynthAndRecog' do expect_synthandrecog_with_options(/nb=5/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"N-Best-List-Length" => 0 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'An n-best-list-length value must be a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'a negative number' do let(:input_command_opts) { { headers: {"N-Best-List-Length" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'An n-best-list-length value must be a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"N-Best-List-Length" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Start-Input-Timers)' do context 'true' do let(:input_command_opts) { { headers: {"Start-Input-Timers" => true } } } it 'should pass the sit option to SynthAndRecog' do expect_synthandrecog_with_options(/sit=true/) subject.execute end end context 'false' do let(:input_command_opts) { { headers: {"Start-Input-Timers" => false } } } it 'should pass the sit option to SynthAndRecog' do expect_synthandrecog_with_options(/sit=false/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Start-Input-Timers" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(DTMF-Terminate-Timeout)' do context 'a positive number' do let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => 1000 } } } it 'should pass the dtt option to SynthAndRecog' do expect_synthandrecog_with_options(/dtt=1000/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => 0 } } } it 'should pass the dtt option to SynthAndRecog' do expect_synthandrecog_with_options(/dtt=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A dtmf-terminate-timeout value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Save-Waveform)' do context 'true' do let(:input_command_opts) { { headers: {"Save-Waveform" => true } } } it 'should pass the sw option to SynthAndRecog' do expect_synthandrecog_with_options(/sw=true/) subject.execute end end context 'false' do let(:input_command_opts) { { headers: {"Save-Waveform" => false } } } it 'should pass the sw option to SynthAndRecog' do expect_synthandrecog_with_options(/sw=false/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Save-Waveform" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(New-Audio-Channel)' do context 'true' do let(:input_command_opts) { { headers: {"New-Audio-Channel" => true } } } it 'should pass the nac option to SynthAndRecog' do expect_synthandrecog_with_options(/nac=true/) subject.execute end end context 'false' do let(:input_command_opts) { { headers: {"New-Audio-Channel" => false } } } it 'should pass the nac option to SynthAndRecog' do expect_synthandrecog_with_options(/nac=false/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"New-Audio-Channel" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Recognition-Mode)' do context 'a string' do let(:input_command_opts) { { headers: {"Recognition-Mode" => "hotword" } } } it 'should pass the rm option to SynthAndRecog' do expect_synthandrecog_with_options(/rm=hotword/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Recognition-Mode" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Hotword-Max-Duration)' do context 'a positive number' do let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => 1000 } } } it 'should pass the hmaxd option to SynthAndRecog' do expect_synthandrecog_with_options(/hmaxd=1000/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => 0 } } } it 'should pass the hmaxd option to SynthAndRecog' do expect_synthandrecog_with_options(/hmaxd=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A hotword-max-duration value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Hotword-Min-Duration)' do context 'a positive number' do let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => 1000 } } } it 'should pass the hmind option to SynthAndRecog' do expect_synthandrecog_with_options(/hmind=1000/) subject.execute end end context '0' do let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => 0 } } } it 'should pass the hmind option to SynthAndRecog' do expect_synthandrecog_with_options(/hmind=0/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => -1000 } } } it "should return an error and not execute any actions" do subject.execute error = ProtocolError.new.setup 'option error', 'A hotword-min-duration value must be -1, 0, or a positive integer.' expect(original_command.response(0.1)).to eq(error) end end context 'unset' do let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Clear-DTMF-Buffer)' do context 'true' do let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => true } } } it 'should pass the cdb option to SynthAndRecog' do expect_synthandrecog_with_options(/cdb=true/) subject.execute end end context 'false' do let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => false } } } it 'should pass the cdb option to SynthAndRecog' do expect_synthandrecog_with_options(/cdb=false/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Early-No-Match)' do context 'true' do let(:input_command_opts) { { headers: {"Early-No-Match" => true } } } it 'should pass the enm option to SynthAndRecog' do expect_synthandrecog_with_options(/enm=true/) subject.execute end end context 'a negative number' do let(:input_command_opts) { { headers: {"Early-No-Match" => false } } } it "should return an error and not execute any actions" do expect_synthandrecog_with_options(/enm=false/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Early-No-Match" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Input-Waveform-URI)' do context 'a positive number' do let(:input_command_opts) { { headers: {"Input-Waveform-URI" => 'http://wave.form.com' } } } it 'should pass the iwu option to SynthAndRecog' do expect_synthandrecog_with_options(/iwu=http:\/\/wave\.form\.com/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Input-Waveform-URI" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#headers(Media-Type)' do context 'a string' do let(:input_command_opts) { { headers: {"Media-Type" => "foo/type" } } } it 'should pass the mt option to SynthAndRecog' do expect_synthandrecog_with_options(/mt=foo\/type/) subject.execute end end context 'unset' do let(:input_command_opts) { { headers: {"Media-Type" => nil } } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#mode' do pending end describe 'Input#terminator' do context 'a string' do let(:input_command_opts) { { terminator: '#' } } it 'should pass the dttc option to SynthAndRecog' do expect_synthandrecog_with_options(/dttc=#/) subject.execute end end context 'unset' do let(:input_command_opts) { { terminator: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#recognizer' do pending end describe 'Input#sensitivity' do context 'a string' do let(:input_command_opts) { { sensitivity: '0.2' } } it 'should pass the sl option to SynthAndRecog' do expect_synthandrecog_with_options(/sl=0.2/) subject.execute end end context 'unset' do let(:input_command_opts) { { sensitivity: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#min-confidence' do context 'a string' do let(:input_command_opts) { { min_confidence: '0.5' } } it 'should pass the ct option to SynthAndRecog' do expect_synthandrecog_with_options(/ct=0.5/) subject.execute end end context 'unset' do let(:input_command_opts) { { min_confidence: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe 'Input#max-silence' do pending end describe 'Input#match-content-type' do pending end describe 'Input#language' do context 'a string' do let(:input_command_opts) { { language: 'en-GB' } } it 'should pass the spl option to SynthAndRecog' do expect_synthandrecog_with_options(/spl=en-GB/) subject.execute end end context 'unset' do let(:input_command_opts) { { language: nil } } it 'should not pass any options to SynthAndRecog' do expect_synthandrecog_with_options(//) subject.execute end end end describe "#execute_command" do context "with a command it does not understand" do let(:command) { Punchblock::Component::Output::Pause.new } before { command.request! } it "returns a ProtocolError response" do subject.execute_command command expect(command.response(0.1)).to be_a ProtocolError end end context "with a Stop command" do let(:command) { Punchblock::Component::Stop.new } let(:reason) { original_command.complete_event(5).reason } let(:channel) { "SIP/1234-00000000" } let :ami_event do RubyAMI::Event.new 'AsyncAGI', 'SubEvent' => "Start", 'Channel' => channel, 'Env' => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A" end before do command.request! original_command.execute! end it "sets the command response to true" do expect(mock_call).to receive(:redirect_back) subject.execute_command command expect(command.response(0.1)).to eq(true) end it "sends the correct complete event" do expect(mock_call).to receive(:redirect_back) subject.execute_command command expect(original_command).not_to be_complete mock_call.process_ami_event ami_event expect(reason).to be_a Punchblock::Event::Complete::Stop expect(original_command).to be_complete end it "redirects the call by unjoining it" do expect(mock_call).to receive(:redirect_back) subject.execute_command command end end end end end end end end