/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb
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