PageRenderTime 138ms CodeModel.GetById 2ms app.highlight 128ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb

http://github.com/adhearsion/punchblock
Ruby | 1244 lines | 1040 code | 203 blank | 1 comment | 0 complexity | b3c81ee0f152e187bdf14ac3948a786c MD5 | raw file
   1# encoding: utf-8
   2
   3require 'spec_helper'
   4
   5module Punchblock
   6  module Translator
   7    class Asterisk
   8      module Component
   9        describe MRCPPrompt do
  10          include HasMockCallbackConnection
  11
  12          let(:ami_client)    { double('AMI') }
  13          let(:translator)    { Punchblock::Translator::Asterisk.new ami_client, connection }
  14          let(:mock_call)     { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
  15
  16          let :ssml_doc do
  17            RubySpeech::SSML.draw do
  18              say_as(:interpret_as => :cardinal) { 'FOO' }
  19            end
  20          end
  21
  22          let :voice_grammar do
  23            RubySpeech::GRXML.draw :mode => 'voice', :root => 'color' do
  24              rule id: 'color' do
  25                one_of do
  26                  item { 'red' }
  27                  item { 'blue' }
  28                  item { 'green' }
  29                end
  30              end
  31            end
  32          end
  33
  34          let :dtmf_grammar do
  35            RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
  36              rule id: 'digit' do
  37                one_of do
  38                  0.upto(9) { |d| item { d.to_s } }
  39                end
  40              end
  41
  42              rule id: 'pin', scope: 'public' do
  43                item repeat: '2' do
  44                  ruleref uri: '#digit'
  45                end
  46              end
  47            end
  48          end
  49
  50          let(:grammar) { voice_grammar }
  51
  52          let(:output_command_opts) { {} }
  53
  54          let :output_command_options do
  55            { render_document: {value: ssml_doc} }.merge(output_command_opts)
  56          end
  57
  58          let(:input_command_opts) { {} }
  59
  60          let :input_command_options do
  61            { grammar: {value: grammar} }.merge(input_command_opts)
  62          end
  63
  64          let(:command_options) { {} }
  65
  66          let :output_command do
  67            Punchblock::Component::Output.new output_command_options
  68          end
  69
  70          let :input_command do
  71            Punchblock::Component::Input.new input_command_options
  72          end
  73
  74          let :original_command do
  75            Punchblock::Component::Prompt.new output_command, input_command, command_options
  76          end
  77
  78          let(:recog_status)            { 'OK' }
  79          let(:recog_completion_cause)  { '000' }
  80          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" }
  81
  82          subject { described_class.new original_command, mock_call }
  83
  84          before do
  85            original_command.request!
  86            {
  87              'RECOG_STATUS' => recog_status,
  88              'RECOG_COMPLETION_CAUSE' => recog_completion_cause,
  89              'RECOG_RESULT' => recog_result
  90            }.each do |var, val|
  91              allow(mock_call).to receive(:channel_var).with(var).and_return val
  92            end
  93          end
  94
  95          context 'with an invalid recognizer' do
  96            let(:input_command_opts) { { recognizer: 'foobar' } }
  97
  98            it "should return an error and not execute any actions" do
  99              subject.execute
 100              error = ProtocolError.new.setup 'option error', 'The recognizer foobar is unsupported.'
 101              expect(original_command.response(0.1)).to eq(error)
 102            end
 103          end
 104
 105          [:asterisk].each do |recognizer|
 106            context "with a recognizer #{recognizer.inspect}" do
 107              let(:input_command_opts) { { recognizer: recognizer } }
 108
 109              it "should return an error and not execute any actions" do
 110                subject.execute
 111                error = ProtocolError.new.setup 'option error', "The recognizer #{recognizer} is unsupported."
 112                expect(original_command.response(0.1)).to eq(error)
 113              end
 114            end
 115          end
 116
 117          def expect_mrcpsynth_with_options(options)
 118            expect_app_with_options 'MRCPSynth', options
 119          end
 120
 121          def expect_synthandrecog_with_options(options)
 122            expect_app_with_options 'SynthAndRecog', options
 123          end
 124
 125          def expect_app_with_options(app, options)
 126            expect(mock_call).to receive(:execute_agi_command).once { |*args|
 127              expect(args[0]).to eq("EXEC #{app}")
 128              expect(args[1]).to match options
 129              {code: 200, result: 1}
 130            }
 131          end
 132
 133          describe 'Output#document' do
 134            context 'with multiple inline documents' do
 135              let(:output_command_options) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
 136
 137              it "should return an error and not execute any actions" do
 138                subject.execute
 139                error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
 140                expect(original_command.response(0.1)).to eq(error)
 141              end
 142            end
 143
 144            context 'with multiple documents by URI' do
 145              let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}, {url: 'http://example.com/doc2.ssml'}] } }
 146
 147              it "should return an error and not execute any actions" do
 148                subject.execute
 149                error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
 150                expect(original_command.response(0.1)).to eq(error)
 151              end
 152            end
 153
 154            context 'unset' do
 155              let(:output_command_options) { {} }
 156
 157              it "should return an error and not execute any actions" do
 158                subject.execute
 159                error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
 160                expect(original_command.response(0.1)).to eq(error)
 161              end
 162            end
 163          end
 164
 165          describe 'Output#renderer' do
 166            [nil, :unimrcp].each do |renderer|
 167              context renderer.to_s do
 168                let(:output_command_opts) { { renderer: renderer } }
 169
 170                it "should return a ref and execute SynthAndRecog" do
 171                  param = [ssml_doc.to_doc, grammar.to_doc].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',')
 172                  expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
 173                  subject.execute
 174                  expect(original_command.response(0.1)).to be_a Ref
 175                end
 176
 177                context "when SynthAndRecog completes" do
 178                  shared_context "with a match" do
 179                    let :expected_nlsml do
 180                      RubySpeech::NLSML.draw do
 181                        interpretation grammar: 'session:grammar-0', confidence: 0.43 do
 182                          input 'Hello', mode: :speech
 183                          instance 'Hello'
 184                        end
 185                      end
 186                    end
 187
 188                    it 'should send a match complete event' do
 189                      expected_complete_reason = Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
 190
 191                      expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
 192                      subject.execute
 193                      expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason)
 194                    end
 195                  end
 196
 197                  context "with a  match cause" do
 198                    %w{000 008 012}.each do |code|
 199                      context "when the MRCP recognition response code is #{code}" do
 200                        let(:recog_completion_cause) { code }
 201
 202                        it_behaves_like 'with a match'
 203                      end
 204                    end
 205                  end
 206
 207                  context "with a nomatch cause" do
 208                    %w{001 003 013 014 015}.each do |code|
 209                      context "with value #{code}" do
 210                        let(:recog_completion_cause) { code }
 211
 212                        it 'should send a nomatch complete event' do
 213                          expected_complete_reason = Punchblock::Component::Input::Complete::NoMatch.new
 214                          expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
 215                          subject.execute
 216                          expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason)
 217                        end
 218                      end
 219                    end
 220                  end
 221
 222                  context "with a noinput cause" do
 223                    %w{002 011}.each do |code|
 224                      context "with value #{code}" do
 225                        let(:recog_completion_cause) { code }
 226
 227                        specify do
 228                          expected_complete_reason = Punchblock::Component::Input::Complete::NoInput.new
 229                          expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
 230                          subject.execute
 231                          expect(original_command.complete_event(0.1).reason).to eq(expected_complete_reason)
 232                        end
 233                      end
 234                    end
 235                  end
 236
 237                  shared_context 'should send an error complete event' do
 238                    specify do
 239                      expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
 240                      subject.execute
 241                      complete_reason = original_command.complete_event(0.1).reason
 242                      expect(complete_reason).to be_a Punchblock::Event::Complete::Error
 243                    end
 244                  end
 245
 246                  context 'with an error cause' do
 247                    %w{004 005 006 007 009 010 016}.each do |code|
 248                      context "when the MRCP recognition response code is #{code}" do
 249                        let(:recog_completion_cause) { code }
 250                        it_behaves_like 'should send an error complete event'
 251                      end
 252                    end
 253                  end
 254
 255                  context 'with an invalid cause' do
 256                    let(:recog_completion_cause) { '999' }
 257                    it_behaves_like 'should send an error complete event'
 258                  end
 259
 260                  context "when the RECOG_STATUS variable is set to 'ERROR'" do
 261                    let(:recog_status) { 'ERROR' }
 262                    it_behaves_like 'should send an error complete event'
 263                  end
 264                end
 265
 266                context "when we get a RubyAMI Error" do
 267                  it "should send an error complete event" do
 268                    error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
 269                    expect(mock_call).to receive(:execute_agi_command).and_raise error
 270                    subject.execute
 271                    complete_reason = original_command.complete_event(0.1).reason
 272                    expect(complete_reason).to be_a Punchblock::Event::Complete::Error
 273                    expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
 274                  end
 275                end
 276
 277                context "when the channel is gone" do
 278                  it "should send an error complete event" do
 279                    error = ChannelGoneError.new 'FooBar'
 280                    expect(mock_call).to receive(:execute_agi_command).and_raise error
 281                    subject.execute
 282                    complete_reason = original_command.complete_event(0.1).reason
 283                    expect(complete_reason).to be_a Punchblock::Event::Complete::Hangup
 284                  end
 285                end
 286              end
 287            end
 288
 289            [:foobar, :swift, :asterisk].each do |renderer|
 290              context renderer.to_s do
 291                let(:output_command_opts) { { renderer: renderer } }
 292
 293                it "should return an error and not execute any actions" do
 294                  subject.execute
 295                  error = ProtocolError.new.setup 'option error', "The renderer #{renderer} is unsupported."
 296                  expect(original_command.response(0.1)).to eq(error)
 297                end
 298              end
 299            end
 300          end
 301
 302          describe 'barge_in' do
 303            context 'unset' do
 304              let(:command_options) { { barge_in: nil } }
 305
 306              it 'should pass the b=1 option to SynthAndRecog' do
 307                expect_synthandrecog_with_options(/b=1/)
 308                subject.execute
 309              end
 310            end
 311
 312            context 'true' do
 313              let(:command_options) { { barge_in: true } }
 314
 315              it 'should pass the b=1 option to SynthAndRecog' do
 316                expect_synthandrecog_with_options(/b=1/)
 317                subject.execute
 318              end
 319            end
 320
 321            context 'false' do
 322              let(:command_options) { { barge_in: false } }
 323
 324              it 'should pass the b=0 option to SynthAndRecog' do
 325                expect_synthandrecog_with_options(/b=0/)
 326                subject.execute
 327              end
 328            end
 329          end
 330
 331          describe 'Output#voice' do
 332            context 'unset' do
 333              let(:output_command_opts) { { voice: nil } }
 334
 335              it 'should not pass the vn option to SynthAndRecog' do
 336                expect_synthandrecog_with_options(//)
 337                subject.execute
 338              end
 339            end
 340
 341            context 'set' do
 342              let(:output_command_opts) { { voice: 'alison' } }
 343
 344              it 'should pass the vn option to SynthAndRecog' do
 345                expect_synthandrecog_with_options(/vn=alison/)
 346                subject.execute
 347              end
 348            end
 349          end
 350
 351          describe 'Output#start-offset' do
 352            context 'unset' do
 353              let(:output_command_opts) { { start_offset: nil } }
 354              it 'should not pass any options to SynthAndRecog' do
 355                expect_synthandrecog_with_options(//)
 356                subject.execute
 357              end
 358            end
 359
 360            context 'set' do
 361              let(:output_command_opts) { { start_offset: 10 } }
 362              it "should return an error and not execute any actions" do
 363                subject.execute
 364                error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
 365                expect(original_command.response(0.1)).to eq(error)
 366              end
 367            end
 368          end
 369
 370          describe 'Output#start-paused' do
 371            context 'false' do
 372              let(:output_command_opts) { { start_paused: false } }
 373              it 'should not pass any options to SynthAndRecog' do
 374                expect_synthandrecog_with_options(//)
 375                subject.execute
 376              end
 377            end
 378
 379            context 'true' do
 380              let(:output_command_opts) { { start_paused: true } }
 381              it "should return an error and not execute any actions" do
 382                subject.execute
 383                error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
 384                expect(original_command.response(0.1)).to eq(error)
 385              end
 386            end
 387          end
 388
 389          describe 'Output#repeat-interval' do
 390            context 'unset' do
 391              let(:output_command_opts) { { repeat_interval: nil } }
 392              it 'should not pass any options to SynthAndRecog' do
 393                expect_synthandrecog_with_options(//)
 394                subject.execute
 395              end
 396            end
 397
 398            context 'set' do
 399              let(:output_command_opts) { { repeat_interval: 10 } }
 400              it "should return an error and not execute any actions" do
 401                subject.execute
 402                error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
 403                expect(original_command.response(0.1)).to eq(error)
 404              end
 405            end
 406          end
 407
 408          describe 'Output#repeat-times' do
 409            context 'unset' do
 410              let(:output_command_opts) { { repeat_times: nil } }
 411              it 'should not pass any options to SynthAndRecog' do
 412                expect_synthandrecog_with_options(//)
 413                subject.execute
 414              end
 415            end
 416
 417            context 'set' do
 418              let(:output_command_opts) { { repeat_times: 2 } }
 419              it "should return an error and not execute any actions" do
 420                subject.execute
 421                error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.'
 422                expect(original_command.response(0.1)).to eq(error)
 423              end
 424            end
 425          end
 426
 427          describe 'Output#max-time' do
 428            context 'unset' do
 429              let(:output_command_opts) { { max_time: nil } }
 430              it 'should not pass any options to SynthAndRecog' do
 431                expect_synthandrecog_with_options(//)
 432                subject.execute
 433              end
 434            end
 435
 436            context 'set' do
 437              let(:output_command_opts) { { max_time: 30 } }
 438              it "should return an error and not execute any actions" do
 439                subject.execute
 440                error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
 441                expect(original_command.response(0.1)).to eq(error)
 442              end
 443            end
 444          end
 445
 446          describe 'Output#interrupt_on' do
 447            context 'unset' do
 448              let(:output_command_opts) { { interrupt_on: nil } }
 449              it 'should not pass any options to SynthAndRecog' do
 450                expect_synthandrecog_with_options(//)
 451                subject.execute
 452              end
 453            end
 454
 455            context 'set' do
 456              let(:output_command_opts) { { interrupt_on: :dtmf } }
 457              it "should return an error and not execute any actions" do
 458                subject.execute
 459                error = ProtocolError.new.setup 'option error', 'A interrupt_on value is unsupported on Asterisk.'
 460                expect(original_command.response(0.1)).to eq(error)
 461              end
 462            end
 463          end
 464
 465          describe 'Output#grammar' do
 466            context 'with multiple inline grammars' do
 467              let(:input_command_options) { { grammars: [{value: voice_grammar}, {value: dtmf_grammar}] } }
 468
 469              it "should return a ref and execute SynthAndRecog" do
 470                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(',')
 471                expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
 472                subject.execute
 473                expect(original_command.response(0.1)).to be_a Ref
 474              end
 475            end
 476
 477            context 'with multiple grammars by URI' do
 478              let(:input_command_options) { { grammars: [{url: 'http://example.com/grammar1.grxml'}, {url: 'http://example.com/grammar2.grxml'}] } }
 479
 480              it "should return a ref and execute SynthAndRecog" do
 481                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(',')
 482                expect(mock_call).to receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
 483                subject.execute
 484                expect(original_command.response(0.1)).to be_a Ref
 485              end
 486            end
 487
 488            context 'unset' do
 489              let(:input_command_options) { {} }
 490
 491              it "should return an error and not execute any actions" do
 492                subject.execute
 493                error = ProtocolError.new.setup 'option error', 'A grammar is required.'
 494                expect(original_command.response(0.1)).to eq(error)
 495              end
 496            end
 497          end
 498
 499          describe 'Input#initial-timeout' do
 500            context 'a positive number' do
 501              let(:input_command_opts) { { initial_timeout: 1000 } }
 502
 503              it 'should pass the nit option to SynthAndRecog' do
 504                expect_synthandrecog_with_options(/nit=1000/)
 505                subject.execute
 506              end
 507            end
 508
 509            context '-1' do
 510              let(:input_command_opts) { { initial_timeout: -1 } }
 511
 512              it 'should not pass any options to SynthAndRecog' do
 513                expect_synthandrecog_with_options(//)
 514                subject.execute
 515              end
 516            end
 517
 518            context 'unset' do
 519              let(:input_command_opts) { { initial_timeout: nil } }
 520
 521              it 'should not pass any options to SynthAndRecog' do
 522                expect_synthandrecog_with_options(//)
 523                subject.execute
 524              end
 525            end
 526
 527            context 'a negative number other than -1' do
 528              let(:input_command_opts) { { initial_timeout: -1000 } }
 529
 530              it "should return an error and not execute any actions" do
 531                subject.execute
 532                error = ProtocolError.new.setup 'option error', 'An initial-timeout value must be -1 or a positive integer.'
 533                expect(original_command.response(0.1)).to eq(error)
 534              end
 535            end
 536          end
 537
 538          describe 'Input#recognition-timeout' do
 539            context 'a positive number' do
 540              let(:input_command_opts) { { recognition_timeout: 1000 } }
 541
 542              it 'should pass the t option to SynthAndRecog' do
 543                expect_synthandrecog_with_options(/t=1000/)
 544                subject.execute
 545              end
 546            end
 547
 548            context '0' do
 549              let(:input_command_opts) { { recognition_timeout: 0 } }
 550
 551              it 'should pass the t option to SynthAndRecog' do
 552                expect_synthandrecog_with_options(/t=0/)
 553                subject.execute
 554              end
 555            end
 556
 557            context 'a negative number' do
 558              let(:input_command_opts) { { recognition_timeout: -1000 } }
 559
 560              it "should return an error and not execute any actions" do
 561                subject.execute
 562                error = ProtocolError.new.setup 'option error', 'A recognition-timeout value must be -1, 0, or a positive integer.'
 563                expect(original_command.response(0.1)).to eq(error)
 564              end
 565            end
 566
 567            context 'unset' do
 568              let(:input_command_opts) { { recognition_timeout: nil } }
 569
 570              it 'should not pass any options to SynthAndRecog' do
 571                expect_synthandrecog_with_options(//)
 572                subject.execute
 573              end
 574            end
 575          end
 576
 577          describe 'Input#inter-digit-timeout' do
 578            context 'a positive number' do
 579              let(:input_command_opts) { { inter_digit_timeout: 1000 } }
 580
 581              it 'should pass the dit option to SynthAndRecog' do
 582                expect_synthandrecog_with_options(/dit=1000/)
 583                subject.execute
 584              end
 585            end
 586
 587            context '-1' do
 588              let(:input_command_opts) { { inter_digit_timeout: -1 } }
 589
 590              it 'should not pass any options to SynthAndRecog' do
 591                expect_synthandrecog_with_options(//)
 592                subject.execute
 593              end
 594            end
 595
 596            context 'unset' do
 597              let(:input_command_opts) { { inter_digit_timeout: nil } }
 598
 599              it 'should not pass any options to SynthAndRecog' do
 600                expect_synthandrecog_with_options(//)
 601                subject.execute
 602              end
 603            end
 604
 605            context 'a negative number other than -1' do
 606              let(:input_command_opts) { { inter_digit_timeout: -1000 } }
 607
 608              it "should return an error and not execute any actions" do
 609                subject.execute
 610                error = ProtocolError.new.setup 'option error', 'An inter-digit-timeout value must be -1 or a positive integer.'
 611                expect(original_command.response(0.1)).to eq(error)
 612              end
 613            end
 614          end
 615
 616          describe 'Input#max-silence' do
 617            context 'a positive number' do
 618              let(:input_command_opts) { { max_silence: 1000 } }
 619
 620              it 'should pass the sint option to SynthAndRecog' do
 621                expect_synthandrecog_with_options(/sint=1000/)
 622                subject.execute
 623              end
 624            end
 625
 626            context '0' do
 627              let(:input_command_opts) { { max_silence: 0 } }
 628
 629              it 'should pass the sint option to SynthAndRecog' do
 630                expect_synthandrecog_with_options(/sint=0/)
 631                subject.execute
 632              end
 633            end
 634
 635            context 'a negative number' do
 636              let(:input_command_opts) { { max_silence: -1000 } }
 637
 638              it "should return an error and not execute any actions" do
 639                subject.execute
 640                error = ProtocolError.new.setup 'option error', 'A max-silence value must be -1, 0, or a positive integer.'
 641                expect(original_command.response(0.1)).to eq(error)
 642              end
 643            end
 644
 645            context 'unset' do
 646              let(:input_command_opts) { { max_silence: nil } }
 647
 648              it 'should not pass any options to SynthAndRecog' do
 649                expect_synthandrecog_with_options(//)
 650                subject.execute
 651              end
 652            end
 653          end
 654
 655          describe 'Input#speech-complete-timeout' do
 656            context 'a positive number' do
 657              let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => 1000 } } }
 658
 659              it 'should pass the sct option to SynthAndRecog' do
 660                expect_synthandrecog_with_options(/sct=1000/)
 661                subject.execute
 662              end
 663            end
 664
 665            context '0' do
 666              let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => 0 } } }
 667
 668              it 'should pass the sct option to SynthAndRecog' do
 669                expect_synthandrecog_with_options(/sct=0/)
 670                subject.execute
 671              end
 672            end
 673
 674            context 'a negative number' do
 675              let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => -1000 } } }
 676
 677              it "should return an error and not execute any actions" do
 678                subject.execute
 679                error = ProtocolError.new.setup 'option error', 'A speech-complete-timeout value must be -1, 0, or a positive integer.'
 680                expect(original_command.response(0.1)).to eq(error)
 681              end
 682            end
 683
 684            context 'unset' do
 685              let(:input_command_opts) { { headers: {"Speech-Complete-Timeout" => nil } } }
 686
 687              it 'should not pass any options to SynthAndRecog' do
 688                expect_synthandrecog_with_options(//)
 689                subject.execute
 690              end
 691            end
 692          end
 693
 694          describe 'Input#headers(Speed-Vs-Accuracy)' do
 695            context 'a positive number' do
 696              let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => 1 } } }
 697
 698              it 'should pass the sva option to SynthAndRecog' do
 699                expect_synthandrecog_with_options(/sva=1/)
 700                subject.execute
 701              end
 702            end
 703
 704            context '0' do
 705              let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => 0 } } }
 706
 707              it 'should pass the sva option to SynthAndRecog' do
 708                expect_synthandrecog_with_options(/sva=0/)
 709                subject.execute
 710              end
 711            end
 712
 713            context 'a negative number' do
 714              let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => -1000 } } }
 715
 716              it "should return an error and not execute any actions" do
 717                subject.execute
 718                error = ProtocolError.new.setup 'option error', 'A speed-vs-accuracy value must be a positive integer.'
 719                expect(original_command.response(0.1)).to eq(error)
 720              end
 721            end
 722
 723            context 'unset' do
 724              let(:input_command_opts) { { headers: {"Speed-Vs-Accuracy" => nil } } }
 725
 726              it 'should not pass any options to SynthAndRecog' do
 727                expect_synthandrecog_with_options(//)
 728                subject.execute
 729              end
 730            end
 731          end
 732
 733          describe 'Input#headers(N-Best-List-Length)' do
 734            context 'a positive number' do
 735              let(:input_command_opts) { { headers: {"N-Best-List-Length" => 5 } } }
 736
 737              it 'should pass the nb option to SynthAndRecog' do
 738                expect_synthandrecog_with_options(/nb=5/)
 739                subject.execute
 740              end
 741            end
 742
 743            context '0' do
 744              let(:input_command_opts) { { headers: {"N-Best-List-Length" => 0 } } }
 745
 746              it "should return an error and not execute any actions" do
 747                subject.execute
 748                error = ProtocolError.new.setup 'option error', 'An n-best-list-length value must be a positive integer.'
 749                expect(original_command.response(0.1)).to eq(error)
 750              end
 751            end
 752
 753            context 'a negative number' do
 754              let(:input_command_opts) { { headers: {"N-Best-List-Length" => -1000 } } }
 755
 756              it "should return an error and not execute any actions" do
 757                subject.execute
 758                error = ProtocolError.new.setup 'option error', 'An n-best-list-length value must be a positive integer.'
 759                expect(original_command.response(0.1)).to eq(error)
 760              end
 761            end
 762
 763            context 'unset' do
 764              let(:input_command_opts) { { headers: {"N-Best-List-Length" => nil } } }
 765
 766              it 'should not pass any options to SynthAndRecog' do
 767                expect_synthandrecog_with_options(//)
 768                subject.execute
 769              end
 770            end
 771          end
 772
 773          describe 'Input#headers(Start-Input-Timers)' do
 774            context 'true' do
 775              let(:input_command_opts) { { headers: {"Start-Input-Timers" => true } } }
 776
 777              it 'should pass the sit option to SynthAndRecog' do
 778                expect_synthandrecog_with_options(/sit=true/)
 779                subject.execute
 780              end
 781            end
 782
 783            context 'false' do
 784              let(:input_command_opts) { { headers: {"Start-Input-Timers" => false } } }
 785
 786              it 'should pass the sit option to SynthAndRecog' do
 787                expect_synthandrecog_with_options(/sit=false/)
 788                subject.execute
 789              end
 790            end
 791
 792            context 'unset' do
 793              let(:input_command_opts) { { headers: {"Start-Input-Timers" => nil } } }
 794
 795              it 'should not pass any options to SynthAndRecog' do
 796                expect_synthandrecog_with_options(//)
 797                subject.execute
 798              end
 799            end
 800          end
 801
 802          describe 'Input#headers(DTMF-Terminate-Timeout)' do
 803            context 'a positive number' do
 804              let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => 1000 } } }
 805
 806              it 'should pass the dtt option to SynthAndRecog' do
 807                expect_synthandrecog_with_options(/dtt=1000/)
 808                subject.execute
 809              end
 810            end
 811
 812            context '0' do
 813              let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => 0 } } }
 814
 815              it 'should pass the dtt option to SynthAndRecog' do
 816                expect_synthandrecog_with_options(/dtt=0/)
 817                subject.execute
 818              end
 819            end
 820
 821            context 'a negative number' do
 822              let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => -1000 } } }
 823
 824              it "should return an error and not execute any actions" do
 825                subject.execute
 826                error = ProtocolError.new.setup 'option error', 'A dtmf-terminate-timeout value must be -1, 0, or a positive integer.'
 827                expect(original_command.response(0.1)).to eq(error)
 828              end
 829            end
 830
 831            context 'unset' do
 832              let(:input_command_opts) { { headers: {"DTMF-Terminate-Timeout" => nil } } }
 833
 834              it 'should not pass any options to SynthAndRecog' do
 835                expect_synthandrecog_with_options(//)
 836                subject.execute
 837              end
 838            end
 839          end
 840
 841          describe 'Input#headers(Save-Waveform)' do
 842            context 'true' do
 843              let(:input_command_opts) { { headers: {"Save-Waveform" => true } } }
 844
 845              it 'should pass the sw option to SynthAndRecog' do
 846                expect_synthandrecog_with_options(/sw=true/)
 847                subject.execute
 848              end
 849            end
 850
 851            context 'false' do
 852              let(:input_command_opts) { { headers: {"Save-Waveform" => false } } }
 853
 854              it 'should pass the sw option to SynthAndRecog' do
 855                expect_synthandrecog_with_options(/sw=false/)
 856                subject.execute
 857              end
 858            end
 859
 860            context 'unset' do
 861              let(:input_command_opts) { { headers: {"Save-Waveform" => nil } } }
 862
 863              it 'should not pass any options to SynthAndRecog' do
 864                expect_synthandrecog_with_options(//)
 865                subject.execute
 866              end
 867            end
 868          end
 869
 870          describe 'Input#headers(New-Audio-Channel)' do
 871            context 'true' do
 872              let(:input_command_opts) { { headers: {"New-Audio-Channel" => true } } }
 873
 874              it 'should pass the nac option to SynthAndRecog' do
 875                expect_synthandrecog_with_options(/nac=true/)
 876                subject.execute
 877              end
 878            end
 879
 880            context 'false' do
 881              let(:input_command_opts) { { headers: {"New-Audio-Channel" => false } } }
 882
 883              it 'should pass the nac option to SynthAndRecog' do
 884                expect_synthandrecog_with_options(/nac=false/)
 885                subject.execute
 886              end
 887            end
 888
 889            context 'unset' do
 890              let(:input_command_opts) { { headers: {"New-Audio-Channel" => nil } } }
 891
 892              it 'should not pass any options to SynthAndRecog' do
 893                expect_synthandrecog_with_options(//)
 894                subject.execute
 895              end
 896            end
 897          end
 898
 899          describe 'Input#headers(Recognition-Mode)' do
 900            context 'a string' do
 901              let(:input_command_opts) { { headers: {"Recognition-Mode" => "hotword" } } }
 902
 903              it 'should pass the rm option to SynthAndRecog' do
 904                expect_synthandrecog_with_options(/rm=hotword/)
 905                subject.execute
 906              end
 907            end
 908
 909            context 'unset' do
 910              let(:input_command_opts) { { headers: {"Recognition-Mode" => nil } } }
 911
 912              it 'should not pass any options to SynthAndRecog' do
 913                expect_synthandrecog_with_options(//)
 914                subject.execute
 915              end
 916            end
 917          end
 918
 919          describe 'Input#headers(Hotword-Max-Duration)' do
 920            context 'a positive number' do
 921              let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => 1000 } } }
 922
 923              it 'should pass the hmaxd option to SynthAndRecog' do
 924                expect_synthandrecog_with_options(/hmaxd=1000/)
 925                subject.execute
 926              end
 927            end
 928
 929            context '0' do
 930              let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => 0 } } }
 931
 932              it 'should pass the hmaxd option to SynthAndRecog' do
 933                expect_synthandrecog_with_options(/hmaxd=0/)
 934                subject.execute
 935              end
 936            end
 937
 938            context 'a negative number' do
 939              let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => -1000 } } }
 940
 941              it "should return an error and not execute any actions" do
 942                subject.execute
 943                error = ProtocolError.new.setup 'option error', 'A hotword-max-duration value must be -1, 0, or a positive integer.'
 944                expect(original_command.response(0.1)).to eq(error)
 945              end
 946            end
 947
 948            context 'unset' do
 949              let(:input_command_opts) { { headers: {"Hotword-Max-Duration" => nil } } }
 950
 951              it 'should not pass any options to SynthAndRecog' do
 952                expect_synthandrecog_with_options(//)
 953                subject.execute
 954              end
 955            end
 956          end
 957
 958          describe 'Input#headers(Hotword-Min-Duration)' do
 959            context 'a positive number' do
 960              let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => 1000 } } }
 961
 962              it 'should pass the hmind option to SynthAndRecog' do
 963                expect_synthandrecog_with_options(/hmind=1000/)
 964                subject.execute
 965              end
 966            end
 967
 968            context '0' do
 969              let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => 0 } } }
 970
 971              it 'should pass the hmind option to SynthAndRecog' do
 972                expect_synthandrecog_with_options(/hmind=0/)
 973                subject.execute
 974              end
 975            end
 976
 977            context 'a negative number' do
 978              let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => -1000 } } }
 979
 980              it "should return an error and not execute any actions" do
 981                subject.execute
 982                error = ProtocolError.new.setup 'option error', 'A hotword-min-duration value must be -1, 0, or a positive integer.'
 983                expect(original_command.response(0.1)).to eq(error)
 984              end
 985            end
 986
 987            context 'unset' do
 988              let(:input_command_opts) { { headers: {"Hotword-Min-Duration" => nil } } }
 989
 990              it 'should not pass any options to SynthAndRecog' do
 991                expect_synthandrecog_with_options(//)
 992                subject.execute
 993              end
 994            end
 995          end
 996
 997          describe 'Input#headers(Clear-DTMF-Buffer)' do
 998            context 'true' do
 999              let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => true } } }
1000
1001              it 'should pass the cdb option to SynthAndRecog' do
1002                expect_synthandrecog_with_options(/cdb=true/)
1003                subject.execute
1004              end
1005            end
1006
1007            context 'false' do
1008              let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => false } } }
1009
1010              it 'should pass the cdb option to SynthAndRecog' do
1011                expect_synthandrecog_with_options(/cdb=false/)
1012                subject.execute
1013              end
1014            end
1015
1016            context 'unset' do
1017              let(:input_command_opts) { { headers: {"Clear-DTMF-Buffer" => nil } } }
1018
1019              it 'should not pass any options to SynthAndRecog' do
1020                expect_synthandrecog_with_options(//)
1021                subject.execute
1022              end
1023            end
1024          end
1025
1026          describe 'Input#headers(Early-No-Match)' do
1027            context 'true' do
1028              let(:input_command_opts) { { headers: {"Early-No-Match" => true } } }
1029
1030              it 'should pass the enm option to SynthAndRecog' do
1031                expect_synthandrecog_with_options(/enm=true/)
1032                subject.execute
1033              end
1034            end
1035
1036            context 'a negative number' do
1037              let(:input_command_opts) { { headers: {"Early-No-Match" => false } } }
1038
1039              it "should return an error and not execute any actions" do
1040                expect_synthandrecog_with_options(/enm=false/)
1041                subject.execute
1042              end
1043            end
1044
1045            context 'unset' do
1046              let(:input_command_opts) { { headers: {"Early-No-Match" => nil } } }
1047
1048              it 'should not pass any options to SynthAndRecog' do
1049                expect_synthandrecog_with_options(//)
1050                subject.execute
1051              end
1052            end
1053          end
1054
1055          describe 'Input#headers(Input-Waveform-URI)' do
1056            context 'a positive number' do
1057              let(:input_command_opts) { { headers: {"Input-Waveform-URI" => 'http://wave.form.com' } } }
1058
1059              it 'should pass the iwu option to SynthAndRecog' do
1060                expect_synthandrecog_with_options(/iwu=http:\/\/wave\.form\.com/)
1061                subject.execute
1062              end
1063            end
1064
1065            context 'unset' do
1066              let(:input_command_opts) { { headers: {"Input-Waveform-URI" => nil } } }
1067
1068              it 'should not pass any options to SynthAndRecog' do
1069                expect_synthandrecog_with_options(//)
1070                subject.execute
1071              end
1072            end
1073          end
1074
1075          describe 'Input#headers(Media-Type)' do
1076            context 'a string' do
1077              let(:input_command_opts) { { headers: {"Media-Type" => "foo/type" } } }
1078
1079              it 'should pass the mt option to SynthAndRecog' do
1080                expect_synthandrecog_with_options(/mt=foo\/type/)
1081                subject.execute
1082              end
1083            end
1084
1085            context 'unset' do
1086              let(:input_command_opts) { { headers: {"Media-Type" => nil } } }
1087
1088              it 'should not pass any options to SynthAndRecog' do
1089                expect_synthandrecog_with_options(//)
1090                subject.execute
1091              end
1092            end
1093          end
1094
1095          describe 'Input#mode' do
1096            skip
1097          end
1098
1099          describe 'Input#terminator' do
1100            context 'a string' do
1101              let(:input_command_opts) { { terminator: '#' } }
1102
1103              it 'should pass the dttc option to SynthAndRecog' do
1104                expect_synthandrecog_with_options(/dttc=#/)
1105                subject.execute
1106              end
1107            end
1108
1109            context 'unset' do
1110              let(:input_command_opts) { { terminator: nil } }
1111
1112              it 'should not pass any options to SynthAndRecog' do
1113                expect_synthandrecog_with_options(//)
1114                subject.execute
1115              end
1116            end
1117          end
1118
1119          describe 'Input#recognizer' do
1120            skip
1121          end
1122
1123          describe 'Input#sensitivity' do
1124            context 'a string' do
1125              let(:input_command_opts) { { sensitivity: '0.2' } }
1126
1127              it 'should pass the sl option to SynthAndRecog' do
1128                expect_synthandrecog_with_options(/sl=0.2/)
1129                subject.execute
1130              end
1131            end
1132
1133            context 'unset' do
1134              let(:input_command_opts) { { sensitivity: nil } }
1135
1136              it 'should not pass any options to SynthAndRecog' do
1137                expect_synthandrecog_with_options(//)
1138                subject.execute
1139              end
1140            end
1141          end
1142
1143          describe 'Input#min-confidence' do
1144            context 'a string' do
1145              let(:input_command_opts) { { min_confidence: '0.5' } }
1146
1147              it 'should pass the ct option to SynthAndRecog' do
1148                expect_synthandrecog_with_options(/ct=0.5/)
1149                subject.execute
1150              end
1151            end
1152
1153            context 'unset' do
1154              let(:input_command_opts) { { min_confidence: nil } }
1155
1156              it 'should not pass any options to SynthAndRecog' do
1157                expect_synthandrecog_with_options(//)
1158                subject.execute
1159              end
1160            end
1161          end
1162
1163          describe 'Input#max-silence' do
1164            skip
1165          end
1166
1167          describe 'Input#match-content-type' do
1168            skip
1169          end
1170
1171          describe 'Input#language' do
1172            context 'a string' do
1173              let(:input_command_opts) { { language: 'en-GB' } }
1174
1175              it 'should pass the spl option to SynthAndRecog' do
1176                expect_synthandrecog_with_options(/spl=en-GB/)
1177                subject.execute
1178              end
1179            end
1180
1181            context 'unset' do
1182              let(:input_command_opts) { { language: nil } }
1183
1184              it 'should not pass any options to SynthAndRecog' do
1185                expect_synthandrecog_with_options(//)
1186                subject.execute
1187              end
1188            end
1189          end
1190
1191          describe "#execute_command" do
1192            context "with a command it does not understand" do
1193              let(:command) { Punchblock::Component::Output::Pause.new }
1194
1195              before { command.request! }
1196              it "returns a ProtocolError response" do
1197                subject.execute_command command
1198                expect(command.response(0.1)).to be_a ProtocolError
1199              end
1200            end
1201
1202            context "with a Stop command" do
1203              let(:command) { Punchblock::Component::Stop.new }
1204              let(:reason) { original_command.complete_event(5).reason }
1205              let(:channel) { "SIP/1234-00000000" }
1206              let :ami_event do
1207                RubyAMI::Event.new 'AsyncAGI',
1208                  'SubEvent'  => "Start",
1209                  'Channel'   => channel,
1210                  '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"
1211              end
1212
1213              before do
1214                command.request!
1215                original_command.execute!
1216              end
1217
1218              it "sets the command response to true" do
1219                expect(mock_call).to receive(:redirect_back)
1220                subject.execute_command command
1221                expect(command.response(0.1)).to eq(true)
1222              end
1223
1224              it "sends the correct complete event" do
1225                expect(mock_call).to receive(:redirect_back)
1226                subject.execute_command command
1227                expect(original_command).not_to be_complete
1228                mock_call.process_ami_event ami_event
1229                expect(reason).to be_a Punchblock::Event::Complete::Stop
1230                expect(original_command).to be_complete
1231              end
1232
1233              it "redirects the call by unjoining it" do
1234                expect(mock_call).to receive(:redirect_back)
1235                subject.execute_command command
1236              end
1237            end
1238          end
1239
1240        end
1241      end
1242    end
1243  end
1244end