adhearsion 2.6.4 → 3.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +6 -9
- data/CHANGELOG.md +22 -7
- data/Gemfile +2 -0
- data/README.markdown +4 -5
- data/Rakefile +1 -2
- data/adhearsion.gemspec +19 -8
- data/features/cli_create.feature +19 -3
- data/features/step_definitions/cli_steps.rb +0 -11
- data/features/support/env.rb +3 -4
- data/lib/adhearsion.rb +48 -27
- data/lib/adhearsion/call.rb +34 -50
- data/lib/adhearsion/call_controller.rb +6 -12
- data/lib/adhearsion/call_controller/dial.rb +15 -53
- data/lib/adhearsion/call_controller/input.rb +39 -162
- data/lib/adhearsion/call_controller/input/ask_grammar_builder.rb +44 -0
- data/lib/adhearsion/call_controller/input/menu_builder.rb +136 -0
- data/lib/adhearsion/call_controller/input/prompt_builder.rb +78 -0
- data/lib/adhearsion/call_controller/input/result.rb +46 -0
- data/lib/adhearsion/call_controller/output.rb +48 -67
- data/lib/adhearsion/call_controller/output/abstract_player.rb +3 -3
- data/lib/adhearsion/call_controller/output/async_player.rb +3 -3
- data/lib/adhearsion/call_controller/output/player.rb +1 -1
- data/lib/adhearsion/call_controller/record.rb +23 -8
- data/lib/adhearsion/calls.rb +1 -1
- data/lib/adhearsion/cli_commands/ahn_command.rb +2 -65
- data/lib/adhearsion/cli_commands/thor_errors.rb +0 -6
- data/lib/adhearsion/configuration.rb +91 -39
- data/lib/adhearsion/core_ext/blather/stanza.rb +41 -0
- data/lib/adhearsion/core_ext/blather/stanza/presence.rb +13 -0
- data/lib/adhearsion/error.rb +5 -0
- data/lib/adhearsion/event.rb +21 -0
- data/lib/adhearsion/event/active_speaker.rb +11 -0
- data/lib/adhearsion/event/answered.rb +11 -0
- data/lib/adhearsion/event/asterisk.rb +10 -0
- data/lib/adhearsion/event/asterisk/ami.rb +34 -0
- data/lib/adhearsion/event/complete.rb +75 -0
- data/lib/adhearsion/event/dtmf.rb +11 -0
- data/lib/adhearsion/event/end.rb +22 -0
- data/lib/adhearsion/event/input_timers_started.rb +9 -0
- data/lib/adhearsion/event/joined.rb +17 -0
- data/lib/adhearsion/event/offer.rb +14 -0
- data/lib/adhearsion/event/ringing.rb +11 -0
- data/lib/adhearsion/event/started_speaking.rb +13 -0
- data/lib/adhearsion/event/stopped_speaking.rb +13 -0
- data/lib/adhearsion/event/unjoined.rb +17 -0
- data/lib/adhearsion/events.rb +47 -66
- data/lib/adhearsion/foundation.rb +0 -1
- data/lib/adhearsion/foundation/object.rb +0 -5
- data/lib/adhearsion/generators/app/app_generator.rb +4 -1
- data/lib/adhearsion/generators/app/templates/Gemfile.erb +2 -10
- data/lib/adhearsion/generators/app/templates/adhearsion.erb +9 -9
- data/lib/adhearsion/generators/app/templates/config.ru +7 -0
- data/lib/adhearsion/generators/app/templates/en.yml +4 -0
- data/lib/adhearsion/generators/app/templates/events.erb +2 -2
- data/lib/adhearsion/generators/app/templates/hello_world.wav +0 -0
- data/lib/adhearsion/generators/app/templates/simon_game.rb +2 -1
- data/lib/adhearsion/generators/app/templates/simon_game_spec.rb +2 -2
- data/lib/adhearsion/has_headers.rb +34 -0
- data/lib/adhearsion/http_server.rb +37 -0
- data/lib/adhearsion/initializer.rb +19 -153
- data/lib/adhearsion/logging.rb +6 -25
- data/lib/adhearsion/outbound_call.rb +5 -5
- data/lib/adhearsion/plugin.rb +1 -0
- data/lib/adhearsion/protocol_error.rb +26 -0
- data/lib/adhearsion/rayo.rb +30 -0
- data/lib/adhearsion/rayo/client.rb +62 -0
- data/lib/adhearsion/rayo/client/component_registry.rb +33 -0
- data/lib/adhearsion/rayo/command.rb +21 -0
- data/lib/adhearsion/rayo/command/accept.rb +16 -0
- data/lib/adhearsion/rayo/command/answer.rb +16 -0
- data/lib/adhearsion/rayo/command/dial.rb +57 -0
- data/lib/adhearsion/rayo/command/hangup.rb +16 -0
- data/lib/adhearsion/rayo/command/join.rb +43 -0
- data/lib/adhearsion/rayo/command/mute.rb +13 -0
- data/lib/adhearsion/rayo/command/redirect.rb +23 -0
- data/lib/adhearsion/rayo/command/reject.rb +40 -0
- data/lib/adhearsion/rayo/command/unjoin.rb +24 -0
- data/lib/adhearsion/rayo/command/unmute.rb +13 -0
- data/lib/adhearsion/rayo/command_node.rb +47 -0
- data/lib/adhearsion/rayo/component.rb +21 -0
- data/lib/adhearsion/rayo/component/asterisk.rb +13 -0
- data/lib/adhearsion/rayo/component/asterisk/agi.rb +14 -0
- data/lib/adhearsion/rayo/component/asterisk/agi/command.rb +46 -0
- data/lib/adhearsion/rayo/component/asterisk/ami.rb +14 -0
- data/lib/adhearsion/rayo/component/asterisk/ami/action.rb +61 -0
- data/lib/adhearsion/rayo/component/component_node.rb +90 -0
- data/lib/adhearsion/rayo/component/input.rb +186 -0
- data/lib/adhearsion/rayo/component/output.rb +471 -0
- data/lib/adhearsion/rayo/component/prompt.rb +53 -0
- data/lib/adhearsion/rayo/component/receive_fax.rb +26 -0
- data/lib/adhearsion/rayo/component/record.rb +165 -0
- data/lib/adhearsion/rayo/component/send_fax.rb +64 -0
- data/lib/adhearsion/rayo/component/stop.rb +11 -0
- data/lib/adhearsion/rayo/connection.rb +12 -0
- data/lib/adhearsion/rayo/connection/asterisk.rb +74 -0
- data/lib/adhearsion/rayo/connection/connected.rb +22 -0
- data/lib/adhearsion/rayo/connection/generic_connection.rb +22 -0
- data/lib/adhearsion/rayo/connection/xmpp.rb +198 -0
- data/lib/adhearsion/rayo/disconnected_error.rb +22 -0
- data/lib/adhearsion/{punchblock_plugin → rayo}/initializer.rb +19 -19
- data/lib/adhearsion/rayo/rayo_node.rb +127 -0
- data/lib/adhearsion/rayo/ref.rb +57 -0
- data/lib/adhearsion/statistics.rb +1 -1
- data/lib/adhearsion/tasks.rb +1 -2
- data/lib/adhearsion/tasks/configuration.rb +1 -1
- data/lib/adhearsion/tasks/environment.rb +0 -2
- data/lib/adhearsion/tasks/i18n.rb +49 -0
- data/lib/adhearsion/translator.rb +11 -0
- data/lib/adhearsion/translator/asterisk.rb +234 -0
- data/lib/adhearsion/translator/asterisk/agi_app.rb +17 -0
- data/lib/adhearsion/translator/asterisk/agi_command.rb +45 -0
- data/lib/adhearsion/translator/asterisk/ami_error_converter.rb +20 -0
- data/lib/adhearsion/translator/asterisk/call.rb +416 -0
- data/lib/adhearsion/translator/asterisk/channel.rb +43 -0
- data/lib/adhearsion/translator/asterisk/component.rb +88 -0
- data/lib/adhearsion/translator/asterisk/component/asterisk.rb +15 -0
- data/lib/adhearsion/translator/asterisk/component/asterisk/agi_command.rb +42 -0
- data/lib/adhearsion/translator/asterisk/component/asterisk/ami_action.rb +68 -0
- data/lib/adhearsion/translator/asterisk/component/composed_prompt.rb +76 -0
- data/lib/adhearsion/translator/asterisk/component/dtmf_recognizer.rb +137 -0
- data/lib/adhearsion/translator/asterisk/component/input.rb +34 -0
- data/lib/adhearsion/translator/asterisk/component/input_component.rb +90 -0
- data/lib/adhearsion/translator/asterisk/component/mrcp_native_prompt.rb +71 -0
- data/lib/adhearsion/translator/asterisk/component/mrcp_prompt.rb +55 -0
- data/lib/adhearsion/translator/asterisk/component/mrcp_recog_prompt.rb +165 -0
- data/lib/adhearsion/translator/asterisk/component/output.rb +233 -0
- data/lib/adhearsion/translator/asterisk/component/record.rb +101 -0
- data/lib/adhearsion/translator/asterisk/component/stop_by_redirect.rb +30 -0
- data/lib/adhearsion/translator/asterisk/unimrcp_app.rb +28 -0
- data/lib/adhearsion/uri_list.rb +21 -0
- data/lib/adhearsion/version.rb +1 -1
- data/spec/adhearsion/call_controller/dial_spec.rb +79 -1420
- data/spec/adhearsion/call_controller/input_spec.rb +1141 -237
- data/spec/adhearsion/call_controller/output/async_player_spec.rb +10 -10
- data/spec/adhearsion/call_controller/output/player_spec.rb +8 -8
- data/spec/adhearsion/call_controller/output_spec.rb +162 -215
- data/spec/adhearsion/call_controller/record_spec.rb +15 -16
- data/spec/adhearsion/call_controller_spec.rb +23 -40
- data/spec/adhearsion/call_spec.rb +123 -129
- data/spec/adhearsion/calls_spec.rb +3 -3
- data/spec/adhearsion/configuration_spec.rb +94 -108
- data/spec/adhearsion/event/answered_spec.rb +50 -0
- data/spec/adhearsion/event/asterisk/Find Results +402 -0
- data/spec/adhearsion/event/asterisk/ami_spec.rb +81 -0
- data/spec/adhearsion/event/complete_spec.rb +176 -0
- data/spec/adhearsion/event/dtmf_spec.rb +35 -0
- data/spec/adhearsion/event/end_spec.rb +85 -0
- data/spec/adhearsion/event/input_timers_started_spec.rb +19 -0
- data/spec/adhearsion/event/joined_spec.rb +53 -0
- data/spec/adhearsion/event/offer_spec.rb +106 -0
- data/spec/adhearsion/event/ringing_spec.rb +50 -0
- data/spec/adhearsion/event/started_speaking_spec.rb +37 -0
- data/spec/adhearsion/event/stopped_speaking_spec.rb +37 -0
- data/spec/adhearsion/event/unjoined_spec.rb +48 -0
- data/spec/adhearsion/event/untitled +0 -0
- data/spec/adhearsion/events_spec.rb +19 -45
- data/spec/adhearsion/initializer_spec.rb +12 -184
- data/spec/adhearsion/logging_spec.rb +5 -20
- data/spec/adhearsion/outbound_call_spec.rb +13 -13
- data/spec/adhearsion/plugin_spec.rb +3 -4
- data/spec/adhearsion/protocol_error_spec.rb +91 -0
- data/spec/adhearsion/rayo/client/component_registry_spec.rb +26 -0
- data/spec/adhearsion/rayo/client_spec.rb +134 -0
- data/spec/adhearsion/rayo/command/accept_spec.rb +63 -0
- data/spec/adhearsion/rayo/command/answer_spec.rb +73 -0
- data/spec/adhearsion/rayo/command/dial_spec.rb +156 -0
- data/spec/adhearsion/rayo/command/hangup_spec.rb +63 -0
- data/spec/adhearsion/rayo/command/join_spec.rb +158 -0
- data/spec/adhearsion/rayo/command/mute_spec.rb +32 -0
- data/spec/adhearsion/rayo/command/redirect_spec.rb +89 -0
- data/spec/adhearsion/rayo/command/reject_spec.rb +117 -0
- data/spec/adhearsion/rayo/command/unjoin_spec.rb +82 -0
- data/spec/adhearsion/rayo/command/unmute_spec.rb +32 -0
- data/spec/adhearsion/rayo/command_node_spec.rb +101 -0
- data/spec/adhearsion/rayo/component/asterisk/agi/command_spec.rb +111 -0
- data/spec/adhearsion/rayo/component/asterisk/ami/action_spec.rb +173 -0
- data/spec/adhearsion/rayo/component/component_node_spec.rb +110 -0
- data/spec/adhearsion/rayo/component/input_spec.rb +715 -0
- data/spec/adhearsion/rayo/component/output_spec.rb +1030 -0
- data/spec/adhearsion/rayo/component/prompt_spec.rb +171 -0
- data/spec/adhearsion/rayo/component/receive_fax_spec.rb +136 -0
- data/spec/adhearsion/rayo/component/record_spec.rb +497 -0
- data/spec/adhearsion/rayo/component/send_fax_spec.rb +144 -0
- data/spec/adhearsion/rayo/connection/asterisk_spec.rb +118 -0
- data/spec/adhearsion/rayo/connection/xmpp_spec.rb +449 -0
- data/spec/adhearsion/rayo/initializer_spec.rb +353 -0
- data/spec/adhearsion/rayo/ref_spec.rb +168 -0
- data/spec/adhearsion/rayo_spec.rb +7 -0
- data/spec/adhearsion/router/route_spec.rb +1 -1
- data/spec/adhearsion/statistics_spec.rb +2 -5
- data/spec/adhearsion/translator/asterisk/call_spec.rb +2047 -0
- data/spec/adhearsion/translator/asterisk/component/asterisk/agi_command_spec.rb +256 -0
- data/spec/adhearsion/translator/asterisk/component/asterisk/ami_action_spec.rb +151 -0
- data/spec/adhearsion/translator/asterisk/component/composed_prompt_spec.rb +257 -0
- data/spec/adhearsion/translator/asterisk/component/input_spec.rb +571 -0
- data/spec/adhearsion/translator/asterisk/component/mrcp_native_prompt_spec.rb +774 -0
- data/spec/adhearsion/translator/asterisk/component/mrcp_prompt_spec.rb +1244 -0
- data/spec/adhearsion/translator/asterisk/component/output_spec.rb +1850 -0
- data/spec/adhearsion/translator/asterisk/component/record_spec.rb +426 -0
- data/spec/adhearsion/translator/asterisk/component/stop_by_redirect_spec.rb +62 -0
- data/spec/adhearsion/translator/asterisk/component_spec.rb +83 -0
- data/spec/adhearsion/translator/asterisk_spec.rb +685 -0
- data/spec/adhearsion/uri_list_spec.rb +88 -0
- data/spec/adhearsion_spec.rb +89 -14
- data/spec/fixtures/locale/en.yml +13 -0
- data/spec/fixtures/locale/it.yml +13 -0
- data/spec/spec_helper.rb +18 -5
- data/spec/support/call_controller_test_helpers.rb +3 -2
- data/spec/support/initializer_stubs.rb +3 -1
- data/spec/support/punchblock_examples.rb +65 -0
- data/spec/support/punchblock_mocks.rb +12 -0
- metadata +412 -70
- data/features/cli_daemon.feature +0 -20
- data/features/cli_restart.feature +0 -52
- data/features/cli_stop.feature +0 -50
- data/lib/adhearsion/call_controller/menu_dsl.rb +0 -21
- data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +0 -26
- data/lib/adhearsion/call_controller/menu_dsl/calculated_match.rb +0 -43
- data/lib/adhearsion/call_controller/menu_dsl/calculated_match_collection.rb +0 -45
- data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +0 -11
- data/lib/adhearsion/call_controller/menu_dsl/match_calculator.rb +0 -40
- data/lib/adhearsion/call_controller/menu_dsl/menu.rb +0 -207
- data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +0 -92
- data/lib/adhearsion/call_controller/menu_dsl/range_match_calculator.rb +0 -60
- data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +0 -25
- data/lib/adhearsion/call_controller/utility.rb +0 -77
- data/lib/adhearsion/foundation/custom_daemonizer.rb +0 -52
- data/lib/adhearsion/punchblock_plugin.rb +0 -63
- data/scripts/cloc-1.64.pl +0 -10483
- data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +0 -76
- data/spec/adhearsion/call_controller/menu_dsl/calculated_match_collection_spec.rb +0 -60
- data/spec/adhearsion/call_controller/menu_dsl/calculated_match_spec.rb +0 -61
- data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +0 -39
- data/spec/adhearsion/call_controller/menu_dsl/match_calculator_spec.rb +0 -17
- data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +0 -165
- data/spec/adhearsion/call_controller/menu_dsl/menu_spec.rb +0 -420
- data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +0 -32
- data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +0 -40
- data/spec/adhearsion/call_controller/utility_spec.rb +0 -90
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +0 -356
- data/spec/adhearsion/punchblock_plugin_spec.rb +0 -59
|
@@ -0,0 +1,1850 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Adhearsion
|
|
6
|
+
module Translator
|
|
7
|
+
class Asterisk
|
|
8
|
+
module Component
|
|
9
|
+
describe Output do
|
|
10
|
+
include HasMockCallbackConnection
|
|
11
|
+
|
|
12
|
+
let(:renderer) { nil }
|
|
13
|
+
let(:ami_client) { double('AMI') }
|
|
14
|
+
let(:translator) { Translator::Asterisk.new ami_client, connection }
|
|
15
|
+
let(:mock_call) { Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
|
|
16
|
+
|
|
17
|
+
let :original_command do
|
|
18
|
+
Adhearsion::Rayo::Component::Output.new command_options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
let :ssml_doc do
|
|
22
|
+
RubySpeech::SSML.draw do
|
|
23
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
let(:command_opts) { {} }
|
|
28
|
+
|
|
29
|
+
let :command_options do
|
|
30
|
+
{ :render_document => {:value => ssml_doc}, renderer: renderer }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
let(:ast13mode) { false }
|
|
34
|
+
|
|
35
|
+
subject { Output.new original_command, mock_call }
|
|
36
|
+
|
|
37
|
+
def expect_answered(value = true)
|
|
38
|
+
allow(mock_call).to receive(:answered?).and_return(value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def expect_mrcpsynth_with_options(options)
|
|
42
|
+
expect(mock_call).to receive(:execute_agi_command).once { |*args|
|
|
43
|
+
expect(args[0]).to eq('EXEC MRCPSynth')
|
|
44
|
+
expect(args[1]).to match options
|
|
45
|
+
}.and_return code: 200, result: 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#execute' do
|
|
49
|
+
before { original_command.request! }
|
|
50
|
+
|
|
51
|
+
context 'with an invalid renderer' do
|
|
52
|
+
let(:renderer) { 'foobar' }
|
|
53
|
+
|
|
54
|
+
it "should return an error and not execute any actions" do
|
|
55
|
+
subject.execute
|
|
56
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'The renderer foobar is unsupported.'
|
|
57
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'with a renderer of :swift' do
|
|
62
|
+
let(:renderer) { 'swift' }
|
|
63
|
+
|
|
64
|
+
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
65
|
+
|
|
66
|
+
let :ssml_doc do
|
|
67
|
+
RubySpeech::SSML.draw do
|
|
68
|
+
audio :src => audio_filename
|
|
69
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
let :command_options do
|
|
74
|
+
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ssml_with_options(prefix = '', postfix = '')
|
|
78
|
+
base_doc = ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }
|
|
79
|
+
prefix + base_doc + postfix
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
before { expect_answered }
|
|
83
|
+
|
|
84
|
+
it "should execute Swift" do
|
|
85
|
+
expect(mock_call).to receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
|
|
86
|
+
subject.execute
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'should send a complete event when Swift completes' do
|
|
90
|
+
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
|
|
91
|
+
subject.execute
|
|
92
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "when we get a RubyAMI Error" do
|
|
96
|
+
it "should send an error complete event" do
|
|
97
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
98
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
99
|
+
subject.execute
|
|
100
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
101
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
102
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context "when the channel is gone" do
|
|
107
|
+
it "should send an error complete event" do
|
|
108
|
+
error = ChannelGoneError.new 'FooBar'
|
|
109
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
110
|
+
subject.execute
|
|
111
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
112
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Hangup
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context "when the call is not answered" do
|
|
117
|
+
before { expect_answered false }
|
|
118
|
+
|
|
119
|
+
it "should send progress" do
|
|
120
|
+
expect(mock_call).to receive(:send_progress)
|
|
121
|
+
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
|
|
122
|
+
subject.execute
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe 'interrupt_on' do
|
|
127
|
+
context "set to nil" do
|
|
128
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
129
|
+
it "should not add interrupt arguments" do
|
|
130
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
|
|
131
|
+
subject.execute
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
context "set to :any" do
|
|
136
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
137
|
+
it "should add the interrupt options to the argument" do
|
|
138
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
|
|
139
|
+
subject.execute
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "set to :dtmf" do
|
|
144
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
145
|
+
it "should add the interrupt options to the argument" do
|
|
146
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
|
|
147
|
+
subject.execute
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
context "set to :voice" do
|
|
152
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
|
153
|
+
it "should return an error and not execute any actions" do
|
|
154
|
+
subject.execute
|
|
155
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
156
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe 'voice' do
|
|
162
|
+
context "set to nil" do
|
|
163
|
+
let(:command_opts) { { :voice => nil } }
|
|
164
|
+
it "should not add a voice at the beginning of the argument" do
|
|
165
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
|
|
166
|
+
subject.execute
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context "set to Leonard" do
|
|
171
|
+
let(:command_opts) { { :voice => "Leonard" } }
|
|
172
|
+
it "should add a voice at the beginning of the argument" do
|
|
173
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('Leonard^', '')).and_return code: 200, result: 1
|
|
174
|
+
subject.execute
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
describe "with multiple documents" do
|
|
181
|
+
let :first_ssml_doc do
|
|
182
|
+
RubySpeech::SSML.draw do
|
|
183
|
+
audio :src => audio_filename
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
let :second_ssml_doc do
|
|
187
|
+
RubySpeech::SSML.draw do
|
|
188
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
let(:command_opts) { { render_documents: [{value: first_ssml_doc}, {value: second_ssml_doc}] } }
|
|
192
|
+
|
|
193
|
+
it "executes Swift with a concatenated version of the documents" do
|
|
194
|
+
expect(mock_call).to receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
|
|
195
|
+
subject.execute
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
context 'with a renderer of :unimrcp' do
|
|
201
|
+
let(:renderer) { :unimrcp }
|
|
202
|
+
|
|
203
|
+
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
204
|
+
|
|
205
|
+
let :ssml_doc do
|
|
206
|
+
RubySpeech::SSML.draw do
|
|
207
|
+
audio :src => audio_filename
|
|
208
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
let(:command_opts) { {} }
|
|
213
|
+
|
|
214
|
+
let :command_options do
|
|
215
|
+
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
let(:synthstatus) { 'OK' }
|
|
219
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
|
220
|
+
|
|
221
|
+
before { expect_answered }
|
|
222
|
+
|
|
223
|
+
it "should execute MRCPSynth" do
|
|
224
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
|
|
225
|
+
subject.execute
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it 'should send a complete event when MRCPSynth completes' do
|
|
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 be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
context "when we get a RubyAMI Error" do
|
|
235
|
+
it "should send an error complete event" do
|
|
236
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
237
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
238
|
+
subject.execute
|
|
239
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
240
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
241
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context "when the channel is gone" do
|
|
246
|
+
it "should send an error complete event" do
|
|
247
|
+
error = ChannelGoneError.new 'FooBar'
|
|
248
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
249
|
+
subject.execute
|
|
250
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
251
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Hangup
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
context "when the call is not answered" do
|
|
256
|
+
before { expect_answered false }
|
|
257
|
+
|
|
258
|
+
it "should send progress" do
|
|
259
|
+
expect(mock_call).to receive(:send_progress)
|
|
260
|
+
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
|
|
261
|
+
subject.execute
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
context "when the SYNTHSTATUS variable is set to 'ERROR'" do
|
|
266
|
+
let(:synthstatus) { 'ERROR' }
|
|
267
|
+
|
|
268
|
+
it "should send an error complete event" do
|
|
269
|
+
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
|
|
270
|
+
subject.execute
|
|
271
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
272
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
273
|
+
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
describe 'document' do
|
|
278
|
+
context 'unset' do
|
|
279
|
+
let(:ssml_doc) { nil }
|
|
280
|
+
it "should return an error and not execute any actions" do
|
|
281
|
+
subject.execute
|
|
282
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
283
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context 'with multiple documents' do
|
|
288
|
+
let(:command_opts) { { :render_documents => [{:value => ssml_doc}, {:value => ssml_doc}] } }
|
|
289
|
+
|
|
290
|
+
it "should execute MRCPSynth once with each document" do
|
|
291
|
+
param = ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')
|
|
292
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
|
|
293
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
|
|
294
|
+
subject.execute
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it 'should not execute further output after a stop command' do
|
|
298
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
299
|
+
sleep 0.5
|
|
300
|
+
end
|
|
301
|
+
latch = CountDownLatch.new 1
|
|
302
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
303
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
304
|
+
latch.countdown!
|
|
305
|
+
}
|
|
306
|
+
Celluloid::Future.new { subject.execute }
|
|
307
|
+
sleep 0.2
|
|
308
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
309
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
310
|
+
stop_command.request!
|
|
311
|
+
subject.execute_command stop_command
|
|
312
|
+
expect(latch.wait(2)).to be_truthy
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
describe 'start-offset' do
|
|
318
|
+
context 'unset' do
|
|
319
|
+
let(:command_opts) { { :start_offset => nil } }
|
|
320
|
+
it 'should not pass any options to MRCPSynth' do
|
|
321
|
+
expect_mrcpsynth_with_options(//)
|
|
322
|
+
subject.execute
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
context 'set' do
|
|
327
|
+
let(:command_opts) { { :start_offset => 10 } }
|
|
328
|
+
it "should return an error and not execute any actions" do
|
|
329
|
+
subject.execute
|
|
330
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
331
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
describe 'start-paused' do
|
|
337
|
+
context 'false' do
|
|
338
|
+
let(:command_opts) { { :start_paused => false } }
|
|
339
|
+
it 'should not pass any options to MRCPSynth' do
|
|
340
|
+
expect_mrcpsynth_with_options(//)
|
|
341
|
+
subject.execute
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
context 'true' do
|
|
346
|
+
let(:command_opts) { { :start_paused => true } }
|
|
347
|
+
it "should return an error and not execute any actions" do
|
|
348
|
+
subject.execute
|
|
349
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
350
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
describe 'repeat-interval' do
|
|
356
|
+
context 'unset' do
|
|
357
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
|
358
|
+
it 'should not pass any options to MRCPSynth' do
|
|
359
|
+
expect_mrcpsynth_with_options(//)
|
|
360
|
+
subject.execute
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
context 'set' do
|
|
365
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
|
366
|
+
it "should return an error and not execute any actions" do
|
|
367
|
+
subject.execute
|
|
368
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
369
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
describe 'repeat-times' do
|
|
375
|
+
context 'unset' do
|
|
376
|
+
let(:command_opts) { { :repeat_times => nil } }
|
|
377
|
+
it 'should not pass any options to MRCPSynth' do
|
|
378
|
+
expect_mrcpsynth_with_options(//)
|
|
379
|
+
subject.execute
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
context 'set' do
|
|
384
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
|
385
|
+
|
|
386
|
+
it "should render the specified number of times" do
|
|
387
|
+
2.times { expect_mrcpsynth_with_options(//) }
|
|
388
|
+
subject.execute
|
|
389
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
context 'to 0' do
|
|
393
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
|
394
|
+
|
|
395
|
+
it "should render 10,000 the specified number of times" do
|
|
396
|
+
expect_answered
|
|
397
|
+
1000.times { expect_mrcpsynth_with_options(//) }
|
|
398
|
+
subject.execute
|
|
399
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
it 'should not execute further output after a stop command' do
|
|
404
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
405
|
+
sleep 0.2
|
|
406
|
+
end
|
|
407
|
+
latch = CountDownLatch.new 1
|
|
408
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
409
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
410
|
+
latch.countdown!
|
|
411
|
+
}
|
|
412
|
+
Celluloid::Future.new { subject.execute }
|
|
413
|
+
sleep 0.1
|
|
414
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
415
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
416
|
+
stop_command.request!
|
|
417
|
+
subject.execute_command stop_command
|
|
418
|
+
expect(latch.wait(2)).to be_truthy
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
describe 'max-time' do
|
|
424
|
+
context 'unset' do
|
|
425
|
+
let(:command_opts) { { :max_time => nil } }
|
|
426
|
+
it 'should not pass any options to MRCPSynth' do
|
|
427
|
+
expect_mrcpsynth_with_options(//)
|
|
428
|
+
subject.execute
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
context 'set' do
|
|
433
|
+
let(:command_opts) { { :max_time => 30 } }
|
|
434
|
+
it "should return an error and not execute any actions" do
|
|
435
|
+
subject.execute
|
|
436
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
437
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
describe 'voice' do
|
|
443
|
+
context 'unset' do
|
|
444
|
+
let(:command_opts) { { :voice => nil } }
|
|
445
|
+
it 'should not pass the v option to MRCPSynth' do
|
|
446
|
+
expect_mrcpsynth_with_options(//)
|
|
447
|
+
subject.execute
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
context 'set' do
|
|
452
|
+
let(:command_opts) { { :voice => 'alison' } }
|
|
453
|
+
it 'should pass the v option to MRCPSynth' do
|
|
454
|
+
expect_mrcpsynth_with_options(/v=alison/)
|
|
455
|
+
subject.execute
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
describe 'interrupt_on' do
|
|
461
|
+
context "set to nil" do
|
|
462
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
463
|
+
it "should not pass the i option to MRCPSynth" do
|
|
464
|
+
expect_mrcpsynth_with_options(//)
|
|
465
|
+
subject.execute
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
context "set to :any" do
|
|
470
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
471
|
+
it "should pass the i option to MRCPSynth" do
|
|
472
|
+
expect_mrcpsynth_with_options(/i=any/)
|
|
473
|
+
subject.execute
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
context "set to :dtmf" do
|
|
478
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
479
|
+
it "should pass the i option to MRCPSynth" do
|
|
480
|
+
expect_mrcpsynth_with_options(/i=any/)
|
|
481
|
+
subject.execute
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
context "set to :voice" do
|
|
486
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
|
487
|
+
it "should return an error and not execute any actions" do
|
|
488
|
+
subject.execute
|
|
489
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
490
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
[:asterisk, nil].each do |renderer|
|
|
497
|
+
context "with a renderer of #{renderer.inspect}" do
|
|
498
|
+
def expect_playback(filename = audio_filename)
|
|
499
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', filename).and_return code: 200
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def expect_playback_noanswer
|
|
503
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
507
|
+
|
|
508
|
+
let :ssml_doc do
|
|
509
|
+
RubySpeech::SSML.draw do
|
|
510
|
+
audio :src => audio_filename
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
let(:command_opts) { {} }
|
|
515
|
+
|
|
516
|
+
let :command_options do
|
|
517
|
+
{ :render_document => {:value => ssml_doc}, renderer: renderer }.merge(command_opts)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
let :original_command do
|
|
521
|
+
Adhearsion::Rayo::Component::Output.new command_options
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
let(:playbackstatus) { 'SUCCESS' }
|
|
525
|
+
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
|
|
526
|
+
|
|
527
|
+
describe 'ssml' do
|
|
528
|
+
context 'unset' do
|
|
529
|
+
let(:ssml_doc) { nil }
|
|
530
|
+
it "should return an error and not execute any actions" do
|
|
531
|
+
subject.execute
|
|
532
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
533
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
context 'with a single audio SSML node' do
|
|
538
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
539
|
+
let :ssml_doc do
|
|
540
|
+
RubySpeech::SSML.draw { audio :src => audio_filename }
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
it 'should playback the audio file using Playback' do
|
|
544
|
+
expect_answered
|
|
545
|
+
expect_playback
|
|
546
|
+
subject.execute
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
it 'should send a complete event when the file finishes playback' do
|
|
550
|
+
def mock_call.answered?
|
|
551
|
+
true
|
|
552
|
+
end
|
|
553
|
+
expect_playback
|
|
554
|
+
subject.execute
|
|
555
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
context "when the audio filename is prefixed by file://" do
|
|
559
|
+
let(:audio_filename) { 'file://tt-monkeys' }
|
|
560
|
+
|
|
561
|
+
it 'should playback the audio file using Playback' do
|
|
562
|
+
expect_answered
|
|
563
|
+
expect_playback 'tt-monkeys'
|
|
564
|
+
subject.execute
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
context "when the audio filename has an extension" do
|
|
569
|
+
let(:audio_filename) { 'tt-monkeys.wav' }
|
|
570
|
+
|
|
571
|
+
it 'should playback the audio file using Playback' do
|
|
572
|
+
expect_answered
|
|
573
|
+
expect_playback 'tt-monkeys'
|
|
574
|
+
subject.execute
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
context "when there are other dots in the filename" do
|
|
578
|
+
let(:audio_filename) { 'blue.tt-monkeys.wav' }
|
|
579
|
+
|
|
580
|
+
it 'should playback the audio file using Playback' do
|
|
581
|
+
expect_answered
|
|
582
|
+
expect_playback 'blue.tt-monkeys'
|
|
583
|
+
subject.execute
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
context "and no file extension" do
|
|
587
|
+
let(:audio_filename) { '/var/lib/gems/1.9.1/gems/myapp-1.0.0/prompts/greeting' }
|
|
588
|
+
|
|
589
|
+
it 'should playback the audio file using Playback' do
|
|
590
|
+
expect_answered
|
|
591
|
+
expect_playback '/var/lib/gems/1.9.1/gems/myapp-1.0.0/prompts/greeting'
|
|
592
|
+
subject.execute
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
context "when we get a RubyAMI Error" do
|
|
599
|
+
it "should send an error complete event" do
|
|
600
|
+
expect_answered
|
|
601
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
602
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
603
|
+
subject.execute
|
|
604
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
605
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
606
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
context "when the channel is gone" do
|
|
611
|
+
it "should send an error complete event" do
|
|
612
|
+
expect_answered
|
|
613
|
+
error = ChannelGoneError.new 'FooBar'
|
|
614
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
615
|
+
subject.execute
|
|
616
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
617
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Hangup
|
|
618
|
+
end
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
|
622
|
+
let(:playbackstatus) { 'FAILED' }
|
|
623
|
+
|
|
624
|
+
it "should send an error complete event" do
|
|
625
|
+
expect_answered
|
|
626
|
+
expect(mock_call).to receive(:execute_agi_command).and_return code: 200, result: 1
|
|
627
|
+
subject.execute
|
|
628
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
629
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
630
|
+
expect(complete_reason.details).to eq("Terminated due to playback error")
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
context 'with a single text node without spaces' do
|
|
636
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
637
|
+
let :ssml_doc do
|
|
638
|
+
RubySpeech::SSML.draw { string audio_filename }
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
it 'should playback the audio file using Playback' do
|
|
642
|
+
expect_answered
|
|
643
|
+
expect_playback
|
|
644
|
+
subject.execute
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
it 'should send a complete event when the file finishes playback' do
|
|
648
|
+
expect_answered
|
|
649
|
+
expect_playback
|
|
650
|
+
subject.execute
|
|
651
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
context "when we get a RubyAMI Error" do
|
|
655
|
+
it "should send an error complete event" do
|
|
656
|
+
expect_answered
|
|
657
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
658
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
659
|
+
subject.execute
|
|
660
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
661
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
662
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
context "with early media playback" do
|
|
667
|
+
it "should play the file with Playback" do
|
|
668
|
+
expect_answered false
|
|
669
|
+
expect_playback_noanswer
|
|
670
|
+
expect(mock_call).to receive(:send_progress)
|
|
671
|
+
subject.execute
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
context "with interrupt_on set to something that is not nil" do
|
|
675
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
676
|
+
let :command_options do
|
|
677
|
+
{
|
|
678
|
+
:render_document => {
|
|
679
|
+
:value => RubySpeech::SSML.draw { string audio_filename },
|
|
680
|
+
},
|
|
681
|
+
:interrupt_on => :any
|
|
682
|
+
}
|
|
683
|
+
end
|
|
684
|
+
it "should return an error when the output is interruptible and it is early media" do
|
|
685
|
+
expect_answered false
|
|
686
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
|
|
687
|
+
subject.execute
|
|
688
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
context 'with multiple audio SSML nodes' do
|
|
695
|
+
let(:audio_filename1) { 'foo' }
|
|
696
|
+
let(:audio_filename2) { 'bar' }
|
|
697
|
+
let :ssml_doc do
|
|
698
|
+
RubySpeech::SSML.draw do
|
|
699
|
+
audio :src => audio_filename1
|
|
700
|
+
audio :src => audio_filename2
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
it 'should playback all audio files using Playback' do
|
|
705
|
+
latch = CountDownLatch.new 2
|
|
706
|
+
expect_playback [audio_filename1, audio_filename2].join('&')
|
|
707
|
+
expect_answered
|
|
708
|
+
subject.execute
|
|
709
|
+
latch.wait 2
|
|
710
|
+
sleep 2
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
it 'should send a complete event after the final file has finished playback' do
|
|
714
|
+
expect_answered
|
|
715
|
+
expect_playback [audio_filename1, audio_filename2].join('&')
|
|
716
|
+
latch = CountDownLatch.new 1
|
|
717
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
718
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
719
|
+
latch.countdown!
|
|
720
|
+
}
|
|
721
|
+
subject.execute
|
|
722
|
+
expect(latch.wait(2)).to be_truthy
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
context "with an SSML document containing elements other than <audio/>" do
|
|
727
|
+
let :ssml_doc do
|
|
728
|
+
RubySpeech::SSML.draw do
|
|
729
|
+
string "Foo Bar"
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
it "should return an unrenderable document error" do
|
|
734
|
+
subject.execute
|
|
735
|
+
error = Adhearsion::ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.'
|
|
736
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
context 'with multiple documents' do
|
|
741
|
+
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
|
|
742
|
+
|
|
743
|
+
it "should render each document in turn using a Playback per document" do
|
|
744
|
+
expect_answered
|
|
745
|
+
2.times { expect_playback }
|
|
746
|
+
subject.execute
|
|
747
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
it 'should not execute further output after a stop command' do
|
|
751
|
+
expect_answered
|
|
752
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
753
|
+
sleep 0.2
|
|
754
|
+
end
|
|
755
|
+
latch = CountDownLatch.new 1
|
|
756
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
757
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
758
|
+
latch.countdown!
|
|
759
|
+
}
|
|
760
|
+
Celluloid::Future.new { subject.execute }
|
|
761
|
+
sleep 0.1
|
|
762
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
763
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
764
|
+
stop_command.request!
|
|
765
|
+
subject.execute_command stop_command
|
|
766
|
+
expect(latch.wait(2)).to be_truthy
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
|
770
|
+
let(:playbackstatus) { 'FAILED' }
|
|
771
|
+
|
|
772
|
+
it "should terminate playback and send an error complete event" do
|
|
773
|
+
expect_answered
|
|
774
|
+
expect(mock_call).to receive(:execute_agi_command).once.and_return code: 200, result: 1
|
|
775
|
+
subject.execute
|
|
776
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
777
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
778
|
+
expect(complete_reason.details).to eq("Terminated due to playback error")
|
|
779
|
+
end
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
describe 'start-offset' do
|
|
785
|
+
context 'unset' do
|
|
786
|
+
let(:command_opts) { { :start_offset => nil } }
|
|
787
|
+
it 'should not pass any options to Playback' do
|
|
788
|
+
expect_answered
|
|
789
|
+
expect_playback
|
|
790
|
+
subject.execute
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
context 'set' do
|
|
795
|
+
let(:command_opts) { { :start_offset => 10 } }
|
|
796
|
+
it "should return an error and not execute any actions" do
|
|
797
|
+
subject.execute
|
|
798
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
799
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
describe 'start-paused' do
|
|
805
|
+
context 'false' do
|
|
806
|
+
let(:command_opts) { { :start_paused => false } }
|
|
807
|
+
it 'should not pass any options to Playback' do
|
|
808
|
+
expect_answered
|
|
809
|
+
expect_playback
|
|
810
|
+
subject.execute
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
context 'true' do
|
|
815
|
+
let(:command_opts) { { :start_paused => true } }
|
|
816
|
+
it "should return an error and not execute any actions" do
|
|
817
|
+
subject.execute
|
|
818
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
819
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
describe 'repeat-interval' do
|
|
825
|
+
context 'unset' do
|
|
826
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
|
827
|
+
it 'should not pass any options to Playback' do
|
|
828
|
+
expect_answered
|
|
829
|
+
expect_playback
|
|
830
|
+
subject.execute
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
|
|
834
|
+
context 'set' do
|
|
835
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
|
836
|
+
it "should return an error and not execute any actions" do
|
|
837
|
+
subject.execute
|
|
838
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
839
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
describe 'repeat-times' do
|
|
845
|
+
context 'unset' do
|
|
846
|
+
let(:command_opts) { { :repeat_times => nil } }
|
|
847
|
+
it 'should not pass any options to Playback' do
|
|
848
|
+
expect_answered
|
|
849
|
+
expect_playback
|
|
850
|
+
subject.execute
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
context 'set' do
|
|
855
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
|
856
|
+
|
|
857
|
+
it "should render the specified number of times" do
|
|
858
|
+
expect_answered
|
|
859
|
+
2.times { expect_playback }
|
|
860
|
+
subject.execute
|
|
861
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
context 'to 0' do
|
|
865
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
|
866
|
+
|
|
867
|
+
it "should render 10,000 the specified number of times" do
|
|
868
|
+
expect_answered
|
|
869
|
+
1000.times { expect_playback }
|
|
870
|
+
subject.execute
|
|
871
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
it 'should not execute further output after a stop command' do
|
|
876
|
+
expect_answered
|
|
877
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
878
|
+
sleep 0.2
|
|
879
|
+
end
|
|
880
|
+
latch = CountDownLatch.new 1
|
|
881
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
882
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
883
|
+
latch.countdown!
|
|
884
|
+
}
|
|
885
|
+
Celluloid::Future.new { subject.execute }
|
|
886
|
+
sleep 0.1
|
|
887
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
888
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
889
|
+
stop_command.request!
|
|
890
|
+
subject.execute_command stop_command
|
|
891
|
+
expect(latch.wait(2)).to be_truthy
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
describe 'max-time' do
|
|
897
|
+
context 'unset' do
|
|
898
|
+
let(:command_opts) { { :max_time => nil } }
|
|
899
|
+
it 'should not pass any options to Playback' do
|
|
900
|
+
expect_answered
|
|
901
|
+
expect_playback
|
|
902
|
+
subject.execute
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
context 'set' do
|
|
907
|
+
let(:command_opts) { { :max_time => 30 } }
|
|
908
|
+
it "should return an error and not execute any actions" do
|
|
909
|
+
subject.execute
|
|
910
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
911
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
describe 'voice' do
|
|
917
|
+
context 'unset' do
|
|
918
|
+
let(:command_opts) { { :voice => nil } }
|
|
919
|
+
it 'should not pass the v option to Playback' do
|
|
920
|
+
expect_answered
|
|
921
|
+
expect_playback
|
|
922
|
+
subject.execute
|
|
923
|
+
end
|
|
924
|
+
end
|
|
925
|
+
|
|
926
|
+
context 'set' do
|
|
927
|
+
let(:command_opts) { { :voice => 'alison' } }
|
|
928
|
+
it "should return an error and not execute any actions" do
|
|
929
|
+
subject.execute
|
|
930
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
|
|
931
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
describe 'interrupt_on' do
|
|
937
|
+
def ami_event_for_dtmf(digit, position)
|
|
938
|
+
if ast13mode
|
|
939
|
+
RubyAMI::Event.new 'DTMF' + (position == :start ? 'Begin' : '') + (position == :end ? 'End' : ''),
|
|
940
|
+
'Digit' => digit.to_s
|
|
941
|
+
else
|
|
942
|
+
RubyAMI::Event.new 'DTMF',
|
|
943
|
+
'Digit' => digit.to_s,
|
|
944
|
+
'Start' => position == :start ? 'Yes' : 'No',
|
|
945
|
+
'End' => position == :end ? 'Yes' : 'No'
|
|
946
|
+
end
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
def send_ami_events_for_dtmf(digit)
|
|
950
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
|
|
951
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
955
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
956
|
+
let :ami_event do
|
|
957
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
958
|
+
'SubEvent' => "Start",
|
|
959
|
+
'Channel' => channel,
|
|
960
|
+
'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"
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
context "set to nil" do
|
|
964
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
965
|
+
it "does not redirect the call" do
|
|
966
|
+
expect_answered
|
|
967
|
+
expect_playback
|
|
968
|
+
expect(mock_call).to receive(:redirect_back).never
|
|
969
|
+
subject.execute
|
|
970
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
971
|
+
send_ami_events_for_dtmf 1
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
context "set to :any" do
|
|
976
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
977
|
+
|
|
978
|
+
before do
|
|
979
|
+
expect_answered
|
|
980
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
|
981
|
+
expect(subject).to receive(:send_finish).and_return nil
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
context "when a DTMF digit is received" do
|
|
985
|
+
it "sends the correct complete event" do
|
|
986
|
+
expect(mock_call).to receive :redirect_back
|
|
987
|
+
subject.execute
|
|
988
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
989
|
+
expect(original_command).not_to be_complete
|
|
990
|
+
send_ami_events_for_dtmf 1
|
|
991
|
+
mock_call.process_ami_event ami_event
|
|
992
|
+
sleep 0.2
|
|
993
|
+
expect(original_command).to be_complete
|
|
994
|
+
expect(reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
it "redirects the call back to async AGI" do
|
|
998
|
+
expect(mock_call).to receive(:redirect_back).once
|
|
999
|
+
subject.execute
|
|
1000
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1001
|
+
send_ami_events_for_dtmf 1
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
context "set to :dtmf" do
|
|
1007
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
1008
|
+
|
|
1009
|
+
before do
|
|
1010
|
+
expect_answered
|
|
1011
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
|
1012
|
+
expect(subject).to receive(:send_finish).and_return nil
|
|
1013
|
+
end
|
|
1014
|
+
|
|
1015
|
+
context "when a DTMF digit is received" do
|
|
1016
|
+
it "sends the correct complete event" do
|
|
1017
|
+
expect(mock_call).to receive :redirect_back
|
|
1018
|
+
subject.execute
|
|
1019
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1020
|
+
expect(original_command).not_to be_complete
|
|
1021
|
+
send_ami_events_for_dtmf 1
|
|
1022
|
+
mock_call.process_ami_event ami_event
|
|
1023
|
+
sleep 0.2
|
|
1024
|
+
expect(original_command).to be_complete
|
|
1025
|
+
expect(reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
it "redirects the call back to async AGI" do
|
|
1029
|
+
expect(mock_call).to receive(:redirect_back).once
|
|
1030
|
+
subject.execute
|
|
1031
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1032
|
+
send_ami_events_for_dtmf 1
|
|
1033
|
+
end
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
context "set to :voice" do
|
|
1038
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
|
1039
|
+
it "should return an error and not execute any actions" do
|
|
1040
|
+
subject.execute
|
|
1041
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
1042
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1043
|
+
end
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
context "with a renderer of :native_or_unimrcp" do
|
|
1050
|
+
def expect_playback(filename = audio_filename)
|
|
1051
|
+
expect(mock_call).to receive(:execute_agi_command).ordered.once.with('EXEC Playback', filename).and_return code: 200
|
|
1052
|
+
end
|
|
1053
|
+
|
|
1054
|
+
def expect_playback_noanswer
|
|
1055
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
def expect_mrcpsynth(doc = ssml_doc)
|
|
1059
|
+
expect(mock_call).to receive(:execute_agi_command).ordered.once.with('EXEC MRCPSynth', ["\"#{doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
|
|
1060
|
+
end
|
|
1061
|
+
|
|
1062
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
1063
|
+
|
|
1064
|
+
let :ssml_doc do
|
|
1065
|
+
RubySpeech::SSML.draw do
|
|
1066
|
+
audio :src => audio_filename do
|
|
1067
|
+
string "Foobar"
|
|
1068
|
+
end
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
let(:command_opts) { {} }
|
|
1073
|
+
|
|
1074
|
+
let :command_options do
|
|
1075
|
+
{ :render_document => {:value => ssml_doc}, renderer: :native_or_unimrcp }.merge(command_opts)
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
let :original_command do
|
|
1079
|
+
Adhearsion::Rayo::Component::Output.new command_options
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
let(:playbackstatus) { 'SUCCESS' }
|
|
1083
|
+
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
|
|
1084
|
+
|
|
1085
|
+
describe 'ssml' do
|
|
1086
|
+
context 'unset' do
|
|
1087
|
+
let(:ssml_doc) { nil }
|
|
1088
|
+
it "should return an error and not execute any actions" do
|
|
1089
|
+
subject.execute
|
|
1090
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
1091
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1092
|
+
end
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
context 'with a single audio SSML node' do
|
|
1096
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
1097
|
+
let :ssml_doc do
|
|
1098
|
+
RubySpeech::SSML.draw language: 'pt-BR' do
|
|
1099
|
+
audio :src => audio_filename do
|
|
1100
|
+
voice name: 'frank' do
|
|
1101
|
+
string "Hello world"
|
|
1102
|
+
end
|
|
1103
|
+
end
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
it 'should playback the audio file using Playback' do
|
|
1108
|
+
expect_answered
|
|
1109
|
+
expect_playback
|
|
1110
|
+
subject.execute
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
it 'should send a complete event when the file finishes playback' do
|
|
1114
|
+
def mock_call.answered?
|
|
1115
|
+
true
|
|
1116
|
+
end
|
|
1117
|
+
expect_playback
|
|
1118
|
+
subject.execute
|
|
1119
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
context "when the audio filename is prefixed by file://" do
|
|
1123
|
+
let(:audio_filename) { 'file://tt-monkeys' }
|
|
1124
|
+
|
|
1125
|
+
it 'should playback the audio file using Playback' do
|
|
1126
|
+
expect_answered
|
|
1127
|
+
expect_playback 'tt-monkeys'
|
|
1128
|
+
subject.execute
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
|
|
1132
|
+
context "when the audio filename has an extension" do
|
|
1133
|
+
let(:audio_filename) { 'tt-monkeys.wav' }
|
|
1134
|
+
|
|
1135
|
+
it 'should playback the audio file using Playback' do
|
|
1136
|
+
expect_answered
|
|
1137
|
+
expect_playback 'tt-monkeys'
|
|
1138
|
+
subject.execute
|
|
1139
|
+
end
|
|
1140
|
+
|
|
1141
|
+
context "when there are other dots in the filename" do
|
|
1142
|
+
let(:audio_filename) { 'blue.tt-monkeys.wav' }
|
|
1143
|
+
|
|
1144
|
+
it 'should playback the audio file using Playback' do
|
|
1145
|
+
expect_answered
|
|
1146
|
+
expect_playback 'blue.tt-monkeys'
|
|
1147
|
+
subject.execute
|
|
1148
|
+
end
|
|
1149
|
+
end
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
context "when we get a RubyAMI Error" do
|
|
1153
|
+
it "should send an error complete event" do
|
|
1154
|
+
expect_answered
|
|
1155
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
1156
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
1157
|
+
subject.execute
|
|
1158
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1159
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
1160
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
1161
|
+
end
|
|
1162
|
+
end
|
|
1163
|
+
|
|
1164
|
+
context "when the channel is gone" do
|
|
1165
|
+
it "should send an error complete event" do
|
|
1166
|
+
expect_answered
|
|
1167
|
+
error = ChannelGoneError.new 'FooBar'
|
|
1168
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
1169
|
+
subject.execute
|
|
1170
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1171
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Hangup
|
|
1172
|
+
end
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
|
1176
|
+
let(:playbackstatus) { 'FAILED' }
|
|
1177
|
+
|
|
1178
|
+
let(:synthstatus) { 'SUCCESS' }
|
|
1179
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
|
1180
|
+
|
|
1181
|
+
let :fallback_doc do
|
|
1182
|
+
RubySpeech::SSML.draw language: 'pt-BR' do
|
|
1183
|
+
voice name: 'frank' do
|
|
1184
|
+
string "Hello world"
|
|
1185
|
+
end
|
|
1186
|
+
end
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
it "should attempt to render the children of the audio tag via MRCP and then send a complete event" do
|
|
1190
|
+
expect_answered
|
|
1191
|
+
expect_playback
|
|
1192
|
+
expect_mrcpsynth fallback_doc
|
|
1193
|
+
subject.execute
|
|
1194
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
|
1198
|
+
let(:synthstatus) { 'ERROR' }
|
|
1199
|
+
|
|
1200
|
+
it "should send an error complete event" do
|
|
1201
|
+
expect_answered
|
|
1202
|
+
expect_playback
|
|
1203
|
+
expect_mrcpsynth fallback_doc
|
|
1204
|
+
subject.execute
|
|
1205
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1206
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
1207
|
+
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
|
|
1208
|
+
end
|
|
1209
|
+
end
|
|
1210
|
+
end
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
context 'with a single text node without spaces' do
|
|
1214
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
1215
|
+
let :ssml_doc do
|
|
1216
|
+
RubySpeech::SSML.draw { string audio_filename }
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
it 'should playback the audio file using Playback' do
|
|
1220
|
+
expect_answered
|
|
1221
|
+
expect_playback
|
|
1222
|
+
subject.execute
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
it 'should send a complete event when the file finishes playback' do
|
|
1226
|
+
expect_answered
|
|
1227
|
+
expect_playback
|
|
1228
|
+
subject.execute
|
|
1229
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
context "when we get a RubyAMI Error" do
|
|
1233
|
+
it "should send an error complete event" do
|
|
1234
|
+
expect_answered
|
|
1235
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
1236
|
+
expect(mock_call).to receive(:execute_agi_command).and_raise error
|
|
1237
|
+
subject.execute
|
|
1238
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1239
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
1240
|
+
expect(complete_reason.details).to eq("Terminated due to AMI error 'FooBar'")
|
|
1241
|
+
end
|
|
1242
|
+
end
|
|
1243
|
+
|
|
1244
|
+
context "with early media playback" do
|
|
1245
|
+
it "should play the file with Playback" do
|
|
1246
|
+
expect_answered false
|
|
1247
|
+
expect_playback_noanswer
|
|
1248
|
+
expect(mock_call).to receive(:send_progress)
|
|
1249
|
+
subject.execute
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1252
|
+
context "with interrupt_on set to something that is not nil" do
|
|
1253
|
+
let(:audio_filename) { 'tt-monkeys' }
|
|
1254
|
+
let :command_options do
|
|
1255
|
+
{
|
|
1256
|
+
:render_document => {
|
|
1257
|
+
:value => RubySpeech::SSML.draw { string audio_filename },
|
|
1258
|
+
},
|
|
1259
|
+
:interrupt_on => :any
|
|
1260
|
+
}
|
|
1261
|
+
end
|
|
1262
|
+
it "should return an error when the output is interruptible and it is early media" do
|
|
1263
|
+
expect_answered false
|
|
1264
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
|
|
1265
|
+
subject.execute
|
|
1266
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1267
|
+
end
|
|
1268
|
+
end
|
|
1269
|
+
end
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
context 'with multiple audio SSML nodes' do
|
|
1273
|
+
let(:audio_filename1) { 'foo' }
|
|
1274
|
+
let(:audio_filename2) { 'bar' }
|
|
1275
|
+
let(:audio_filename3) { 'baz' }
|
|
1276
|
+
let :ssml_doc do
|
|
1277
|
+
RubySpeech::SSML.draw do
|
|
1278
|
+
audio :src => audio_filename1 do
|
|
1279
|
+
string "Fallback 1"
|
|
1280
|
+
end
|
|
1281
|
+
audio :src => audio_filename2 do
|
|
1282
|
+
string "Fallback 2"
|
|
1283
|
+
end
|
|
1284
|
+
audio :src => audio_filename3 do
|
|
1285
|
+
string "Fallback 3"
|
|
1286
|
+
end
|
|
1287
|
+
end
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
it 'should playback all audio files using Playback' do
|
|
1291
|
+
latch = CountDownLatch.new 2
|
|
1292
|
+
expect_playback audio_filename1
|
|
1293
|
+
expect_playback audio_filename2
|
|
1294
|
+
expect_playback audio_filename3
|
|
1295
|
+
expect_answered
|
|
1296
|
+
subject.execute
|
|
1297
|
+
latch.wait 2
|
|
1298
|
+
sleep 2
|
|
1299
|
+
end
|
|
1300
|
+
|
|
1301
|
+
it 'should send a complete event after the final file has finished playback' do
|
|
1302
|
+
expect_answered
|
|
1303
|
+
expect_playback audio_filename1
|
|
1304
|
+
expect_playback audio_filename2
|
|
1305
|
+
expect_playback audio_filename3
|
|
1306
|
+
latch = CountDownLatch.new 1
|
|
1307
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
1308
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1309
|
+
latch.countdown!
|
|
1310
|
+
}
|
|
1311
|
+
subject.execute
|
|
1312
|
+
expect(latch.wait(2)).to be_truthy
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
it 'should not execute further output after a stop command' do
|
|
1316
|
+
expect_answered
|
|
1317
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
1318
|
+
sleep 0.2
|
|
1319
|
+
end
|
|
1320
|
+
latch = CountDownLatch.new 1
|
|
1321
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
1322
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1323
|
+
latch.countdown!
|
|
1324
|
+
}
|
|
1325
|
+
Celluloid::Future.new { subject.execute }
|
|
1326
|
+
sleep 0.1
|
|
1327
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
1328
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
1329
|
+
stop_command.request!
|
|
1330
|
+
subject.execute_command stop_command
|
|
1331
|
+
expect(latch.wait(2)).to be_truthy
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
|
1335
|
+
let(:synthstatus) { 'SUCCESS' }
|
|
1336
|
+
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
|
|
1337
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
|
1338
|
+
|
|
1339
|
+
let :fallback_doc do
|
|
1340
|
+
RubySpeech::SSML.draw do
|
|
1341
|
+
string "Fallback 2"
|
|
1342
|
+
end
|
|
1343
|
+
end
|
|
1344
|
+
|
|
1345
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
|
1346
|
+
expect_answered
|
|
1347
|
+
expect_playback audio_filename1
|
|
1348
|
+
expect_playback audio_filename2
|
|
1349
|
+
expect_mrcpsynth fallback_doc
|
|
1350
|
+
expect_playback audio_filename3
|
|
1351
|
+
subject.execute
|
|
1352
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1353
|
+
end
|
|
1354
|
+
|
|
1355
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
|
1356
|
+
let(:synthstatus) { 'ERROR' }
|
|
1357
|
+
|
|
1358
|
+
it "should terminate playback and send an error complete event" do
|
|
1359
|
+
expect_answered
|
|
1360
|
+
expect_playback audio_filename1
|
|
1361
|
+
expect_playback audio_filename2
|
|
1362
|
+
expect_mrcpsynth fallback_doc
|
|
1363
|
+
subject.execute
|
|
1364
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1365
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
1366
|
+
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
|
|
1367
|
+
end
|
|
1368
|
+
end
|
|
1369
|
+
end
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
context "with an SSML document containing top-level elements other than <audio/>" do
|
|
1373
|
+
let :ssml_doc do
|
|
1374
|
+
RubySpeech::SSML.draw do
|
|
1375
|
+
voice name: 'Paul' do
|
|
1376
|
+
string "Foo Bar"
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
|
|
1382
|
+
|
|
1383
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
|
1384
|
+
expect_answered
|
|
1385
|
+
expect_mrcpsynth ssml_doc
|
|
1386
|
+
subject.execute
|
|
1387
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1388
|
+
end
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
context "with mixed TTS and audio tags" do
|
|
1392
|
+
let :ssml_doc do
|
|
1393
|
+
RubySpeech::SSML.draw do
|
|
1394
|
+
voice name: 'Paul' do
|
|
1395
|
+
string "Foo Bar"
|
|
1396
|
+
end
|
|
1397
|
+
audio src: 'tt-monkeys'
|
|
1398
|
+
voice name: 'Frank' do
|
|
1399
|
+
string "Doo Dah"
|
|
1400
|
+
end
|
|
1401
|
+
string 'tt-weasels'
|
|
1402
|
+
end
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
let :first_doc do
|
|
1406
|
+
RubySpeech::SSML.draw do
|
|
1407
|
+
voice name: 'Paul' do
|
|
1408
|
+
string "Foo Bar"
|
|
1409
|
+
end
|
|
1410
|
+
end
|
|
1411
|
+
end
|
|
1412
|
+
|
|
1413
|
+
let :second_doc do
|
|
1414
|
+
RubySpeech::SSML.draw do
|
|
1415
|
+
voice name: 'Frank' do
|
|
1416
|
+
string "Doo Dah"
|
|
1417
|
+
end
|
|
1418
|
+
end
|
|
1419
|
+
end
|
|
1420
|
+
|
|
1421
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
|
|
1422
|
+
|
|
1423
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
|
1424
|
+
expect_answered
|
|
1425
|
+
expect_mrcpsynth first_doc
|
|
1426
|
+
expect_playback 'tt-monkeys'
|
|
1427
|
+
expect_mrcpsynth second_doc
|
|
1428
|
+
expect_playback 'tt-weasels'
|
|
1429
|
+
subject.execute
|
|
1430
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1431
|
+
end
|
|
1432
|
+
end
|
|
1433
|
+
|
|
1434
|
+
context 'with multiple documents' do
|
|
1435
|
+
let :second_ssml_doc do
|
|
1436
|
+
RubySpeech::SSML.draw do
|
|
1437
|
+
audio :src => 'two.wav' do
|
|
1438
|
+
string "Bazzz"
|
|
1439
|
+
end
|
|
1440
|
+
end
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
let :third_ssml_doc do
|
|
1444
|
+
RubySpeech::SSML.draw do
|
|
1445
|
+
audio :src => 'three.wav' do
|
|
1446
|
+
string "Barrrr"
|
|
1447
|
+
end
|
|
1448
|
+
end
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: second_ssml_doc}, {value: third_ssml_doc}] } }
|
|
1452
|
+
|
|
1453
|
+
it "should render each document in turn using a Playback per document" do
|
|
1454
|
+
expect_answered
|
|
1455
|
+
expect_playback
|
|
1456
|
+
expect_playback 'two'
|
|
1457
|
+
expect_playback 'three'
|
|
1458
|
+
subject.execute
|
|
1459
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1460
|
+
end
|
|
1461
|
+
|
|
1462
|
+
it 'should not execute further output after a stop command' do
|
|
1463
|
+
expect_answered
|
|
1464
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
1465
|
+
sleep 0.2
|
|
1466
|
+
end
|
|
1467
|
+
latch = CountDownLatch.new 1
|
|
1468
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
1469
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1470
|
+
latch.countdown!
|
|
1471
|
+
}
|
|
1472
|
+
Celluloid::Future.new { subject.execute }
|
|
1473
|
+
sleep 0.1
|
|
1474
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
1475
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
1476
|
+
stop_command.request!
|
|
1477
|
+
subject.execute_command stop_command
|
|
1478
|
+
expect(latch.wait(2)).to be_truthy
|
|
1479
|
+
end
|
|
1480
|
+
|
|
1481
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
|
1482
|
+
let(:synthstatus) { 'SUCCESS' }
|
|
1483
|
+
before { allow(mock_call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
|
|
1484
|
+
before { allow(mock_call).to receive(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
|
1485
|
+
|
|
1486
|
+
let :fallback_doc do
|
|
1487
|
+
RubySpeech::SSML.draw do
|
|
1488
|
+
string "Bazzz"
|
|
1489
|
+
end
|
|
1490
|
+
end
|
|
1491
|
+
|
|
1492
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
|
1493
|
+
expect_answered
|
|
1494
|
+
expect_playback
|
|
1495
|
+
expect_playback 'two'
|
|
1496
|
+
expect_mrcpsynth fallback_doc
|
|
1497
|
+
expect_playback 'three'
|
|
1498
|
+
subject.execute
|
|
1499
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1500
|
+
end
|
|
1501
|
+
|
|
1502
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
|
1503
|
+
let(:synthstatus) { 'ERROR' }
|
|
1504
|
+
|
|
1505
|
+
it "should terminate playback and send an error complete event" do
|
|
1506
|
+
expect_answered
|
|
1507
|
+
expect_playback
|
|
1508
|
+
expect_playback 'two'
|
|
1509
|
+
expect_mrcpsynth fallback_doc
|
|
1510
|
+
subject.execute
|
|
1511
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
1512
|
+
expect(complete_reason).to be_a Adhearsion::Event::Complete::Error
|
|
1513
|
+
expect(complete_reason.details).to eq("Terminated due to UniMRCP error")
|
|
1514
|
+
end
|
|
1515
|
+
end
|
|
1516
|
+
end
|
|
1517
|
+
end
|
|
1518
|
+
end
|
|
1519
|
+
|
|
1520
|
+
describe 'start-offset' do
|
|
1521
|
+
context 'unset' do
|
|
1522
|
+
let(:command_opts) { { :start_offset => nil } }
|
|
1523
|
+
it 'should not pass any options to Playback' do
|
|
1524
|
+
expect_answered
|
|
1525
|
+
expect_playback
|
|
1526
|
+
subject.execute
|
|
1527
|
+
end
|
|
1528
|
+
end
|
|
1529
|
+
|
|
1530
|
+
context 'set' do
|
|
1531
|
+
let(:command_opts) { { :start_offset => 10 } }
|
|
1532
|
+
it "should return an error and not execute any actions" do
|
|
1533
|
+
subject.execute
|
|
1534
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
1535
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
end
|
|
1539
|
+
|
|
1540
|
+
describe 'start-paused' do
|
|
1541
|
+
context 'false' do
|
|
1542
|
+
let(:command_opts) { { :start_paused => false } }
|
|
1543
|
+
it 'should not pass any options to Playback' do
|
|
1544
|
+
expect_answered
|
|
1545
|
+
expect_playback
|
|
1546
|
+
subject.execute
|
|
1547
|
+
end
|
|
1548
|
+
end
|
|
1549
|
+
|
|
1550
|
+
context 'true' do
|
|
1551
|
+
let(:command_opts) { { :start_paused => true } }
|
|
1552
|
+
it "should return an error and not execute any actions" do
|
|
1553
|
+
subject.execute
|
|
1554
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
1555
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1556
|
+
end
|
|
1557
|
+
end
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
describe 'repeat-interval' do
|
|
1561
|
+
context 'unset' do
|
|
1562
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
|
1563
|
+
it 'should not pass any options to Playback' do
|
|
1564
|
+
expect_answered
|
|
1565
|
+
expect_playback
|
|
1566
|
+
subject.execute
|
|
1567
|
+
end
|
|
1568
|
+
end
|
|
1569
|
+
|
|
1570
|
+
context 'set' do
|
|
1571
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
|
1572
|
+
it "should return an error and not execute any actions" do
|
|
1573
|
+
subject.execute
|
|
1574
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
1575
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1576
|
+
end
|
|
1577
|
+
end
|
|
1578
|
+
end
|
|
1579
|
+
|
|
1580
|
+
describe 'repeat-times' do
|
|
1581
|
+
context 'unset' do
|
|
1582
|
+
let(:command_opts) { { :repeat_times => nil } }
|
|
1583
|
+
it 'should not pass any options to Playback' do
|
|
1584
|
+
expect_answered
|
|
1585
|
+
expect_playback
|
|
1586
|
+
subject.execute
|
|
1587
|
+
end
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
context 'set' do
|
|
1591
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
|
1592
|
+
|
|
1593
|
+
it "should render the specified number of times" do
|
|
1594
|
+
expect_answered
|
|
1595
|
+
2.times { expect_playback }
|
|
1596
|
+
subject.execute
|
|
1597
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1600
|
+
context 'to 0' do
|
|
1601
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
|
1602
|
+
|
|
1603
|
+
it "should render 10,000 the specified number of times" do
|
|
1604
|
+
expect_answered
|
|
1605
|
+
1000.times { expect_playback }
|
|
1606
|
+
subject.execute
|
|
1607
|
+
expect(original_command.complete_event(0.1).reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1608
|
+
end
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
it 'should not execute further output after a stop command' do
|
|
1612
|
+
expect_answered
|
|
1613
|
+
expect(mock_call).to receive(:execute_agi_command).once.ordered do
|
|
1614
|
+
sleep 0.2
|
|
1615
|
+
end
|
|
1616
|
+
latch = CountDownLatch.new 1
|
|
1617
|
+
expect(original_command).to receive(:add_event).once { |e|
|
|
1618
|
+
expect(e.reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1619
|
+
latch.countdown!
|
|
1620
|
+
}
|
|
1621
|
+
Celluloid::Future.new { subject.execute }
|
|
1622
|
+
sleep 0.1
|
|
1623
|
+
expect(mock_call).to receive(:redirect_back).ordered
|
|
1624
|
+
stop_command = Adhearsion::Rayo::Component::Stop.new
|
|
1625
|
+
stop_command.request!
|
|
1626
|
+
subject.execute_command stop_command
|
|
1627
|
+
expect(latch.wait(2)).to be_truthy
|
|
1628
|
+
end
|
|
1629
|
+
end
|
|
1630
|
+
end
|
|
1631
|
+
|
|
1632
|
+
describe 'max-time' do
|
|
1633
|
+
context 'unset' do
|
|
1634
|
+
let(:command_opts) { { :max_time => nil } }
|
|
1635
|
+
it 'should not pass any options to Playback' do
|
|
1636
|
+
expect_answered
|
|
1637
|
+
expect_playback
|
|
1638
|
+
subject.execute
|
|
1639
|
+
end
|
|
1640
|
+
end
|
|
1641
|
+
|
|
1642
|
+
context 'set' do
|
|
1643
|
+
let(:command_opts) { { :max_time => 30 } }
|
|
1644
|
+
it "should return an error and not execute any actions" do
|
|
1645
|
+
subject.execute
|
|
1646
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
1647
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1648
|
+
end
|
|
1649
|
+
end
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
describe 'voice' do
|
|
1653
|
+
context 'unset' do
|
|
1654
|
+
let(:command_opts) { { :voice => nil } }
|
|
1655
|
+
it 'should not pass the v option to Playback' do
|
|
1656
|
+
expect_answered
|
|
1657
|
+
expect_playback
|
|
1658
|
+
subject.execute
|
|
1659
|
+
end
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
context 'set' do
|
|
1663
|
+
let(:command_opts) { { :voice => 'alison' } }
|
|
1664
|
+
it "should return an error and not execute any actions" do
|
|
1665
|
+
subject.execute
|
|
1666
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
|
|
1667
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1668
|
+
end
|
|
1669
|
+
end
|
|
1670
|
+
end
|
|
1671
|
+
|
|
1672
|
+
describe 'interrupt_on' do
|
|
1673
|
+
def ami_event_for_dtmf(digit, position)
|
|
1674
|
+
if ast13mode
|
|
1675
|
+
RubyAMI::Event.new 'DTMF' + (position == :start ? 'Begin' : '') + (position == :end ? 'End' : ''),
|
|
1676
|
+
'Digit' => digit.to_s
|
|
1677
|
+
else
|
|
1678
|
+
RubyAMI::Event.new 'DTMF',
|
|
1679
|
+
'Digit' => digit.to_s,
|
|
1680
|
+
'Start' => position == :start ? 'Yes' : 'No',
|
|
1681
|
+
'End' => position == :end ? 'Yes' : 'No'
|
|
1682
|
+
end
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
def send_ami_events_for_dtmf(digit)
|
|
1686
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
|
|
1687
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
|
|
1688
|
+
end
|
|
1689
|
+
|
|
1690
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
1691
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
1692
|
+
let :ami_event do
|
|
1693
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
1694
|
+
'SubEvent' => "Start",
|
|
1695
|
+
'Channel' => channel,
|
|
1696
|
+
'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"
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
context "set to nil" do
|
|
1700
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
1701
|
+
it "does not redirect the call" do
|
|
1702
|
+
expect_answered
|
|
1703
|
+
expect_playback
|
|
1704
|
+
expect(mock_call).to receive(:redirect_back).never
|
|
1705
|
+
subject.execute
|
|
1706
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1707
|
+
send_ami_events_for_dtmf 1
|
|
1708
|
+
end
|
|
1709
|
+
end
|
|
1710
|
+
|
|
1711
|
+
context "set to :any" do
|
|
1712
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
1713
|
+
|
|
1714
|
+
before do
|
|
1715
|
+
expect_answered
|
|
1716
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
|
1717
|
+
expect(subject).to receive(:send_finish).and_return nil
|
|
1718
|
+
end
|
|
1719
|
+
|
|
1720
|
+
context "when a DTMF digit is received" do
|
|
1721
|
+
it "sends the correct complete event" do
|
|
1722
|
+
expect(mock_call).to receive :redirect_back
|
|
1723
|
+
subject.execute
|
|
1724
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1725
|
+
expect(original_command).not_to be_complete
|
|
1726
|
+
send_ami_events_for_dtmf 1
|
|
1727
|
+
mock_call.process_ami_event ami_event
|
|
1728
|
+
sleep 0.2
|
|
1729
|
+
expect(original_command).to be_complete
|
|
1730
|
+
expect(reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1731
|
+
end
|
|
1732
|
+
|
|
1733
|
+
it "redirects the call back to async AGI" do
|
|
1734
|
+
expect(mock_call).to receive(:redirect_back).once
|
|
1735
|
+
subject.execute
|
|
1736
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1737
|
+
send_ami_events_for_dtmf 1
|
|
1738
|
+
end
|
|
1739
|
+
end
|
|
1740
|
+
end
|
|
1741
|
+
|
|
1742
|
+
context "set to :dtmf" do
|
|
1743
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
1744
|
+
|
|
1745
|
+
before do
|
|
1746
|
+
expect_answered
|
|
1747
|
+
expect(mock_call).to receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
|
1748
|
+
expect(subject).to receive(:send_finish).and_return nil
|
|
1749
|
+
end
|
|
1750
|
+
|
|
1751
|
+
def send_correct_complete_event
|
|
1752
|
+
expect(mock_call).to receive :redirect_back
|
|
1753
|
+
subject.execute
|
|
1754
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1755
|
+
expect(original_command).not_to be_complete
|
|
1756
|
+
send_ami_events_for_dtmf 1
|
|
1757
|
+
mock_call.process_ami_event ami_event
|
|
1758
|
+
sleep 0.2
|
|
1759
|
+
expect(original_command).to be_complete
|
|
1760
|
+
expect(reason).to be_a Adhearsion::Rayo::Component::Output::Complete::Finish
|
|
1761
|
+
end
|
|
1762
|
+
|
|
1763
|
+
context "when a DTMF digit is received" do
|
|
1764
|
+
it "sends the correct complete event" do
|
|
1765
|
+
send_correct_complete_event
|
|
1766
|
+
end
|
|
1767
|
+
|
|
1768
|
+
it "redirects the call back to async AGI" do
|
|
1769
|
+
expect(mock_call).to receive(:redirect_back).once
|
|
1770
|
+
subject.execute
|
|
1771
|
+
expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
|
|
1772
|
+
send_ami_events_for_dtmf 1
|
|
1773
|
+
end
|
|
1774
|
+
|
|
1775
|
+
context 'with an Asterisk 13 DTMFEnd event' do
|
|
1776
|
+
let(:ast13mode) { true }
|
|
1777
|
+
it "sends the correct complete event" do
|
|
1778
|
+
send_correct_complete_event
|
|
1779
|
+
end
|
|
1780
|
+
end
|
|
1781
|
+
end
|
|
1782
|
+
end
|
|
1783
|
+
|
|
1784
|
+
context "set to :voice" do
|
|
1785
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
|
1786
|
+
it "should return an error and not execute any actions" do
|
|
1787
|
+
subject.execute
|
|
1788
|
+
error = Adhearsion::ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
1789
|
+
expect(original_command.response(0.1)).to eq(error)
|
|
1790
|
+
end
|
|
1791
|
+
end
|
|
1792
|
+
end
|
|
1793
|
+
end
|
|
1794
|
+
end
|
|
1795
|
+
|
|
1796
|
+
describe "#execute_command" do
|
|
1797
|
+
context "with a command it does not understand" do
|
|
1798
|
+
let(:command) { Adhearsion::Rayo::Component::Output::Pause.new }
|
|
1799
|
+
|
|
1800
|
+
before { command.request! }
|
|
1801
|
+
it "returns a Adhearsion::ProtocolError response" do
|
|
1802
|
+
subject.execute_command command
|
|
1803
|
+
expect(command.response(0.1)).to be_a Adhearsion::ProtocolError
|
|
1804
|
+
end
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
context "with a Stop command" do
|
|
1808
|
+
let(:command) { Adhearsion::Rayo::Component::Stop.new }
|
|
1809
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
1810
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
1811
|
+
let :ami_event do
|
|
1812
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
1813
|
+
'SubEvent' => "Start",
|
|
1814
|
+
'Channel' => channel,
|
|
1815
|
+
'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"
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
before do
|
|
1819
|
+
command.request!
|
|
1820
|
+
original_command.request!
|
|
1821
|
+
original_command.execute!
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1824
|
+
it "sets the command response to true" do
|
|
1825
|
+
expect(mock_call).to receive(:redirect_back)
|
|
1826
|
+
subject.execute_command command
|
|
1827
|
+
expect(command.response(0.1)).to eq(true)
|
|
1828
|
+
end
|
|
1829
|
+
|
|
1830
|
+
it "sends the correct complete event" do
|
|
1831
|
+
expect(mock_call).to receive(:redirect_back)
|
|
1832
|
+
subject.execute_command command
|
|
1833
|
+
expect(original_command).not_to be_complete
|
|
1834
|
+
mock_call.process_ami_event ami_event
|
|
1835
|
+
expect(reason).to be_a Adhearsion::Event::Complete::Stop
|
|
1836
|
+
expect(original_command).to be_complete
|
|
1837
|
+
end
|
|
1838
|
+
|
|
1839
|
+
it "redirects the call by unjoining it" do
|
|
1840
|
+
expect(mock_call).to receive(:redirect_back)
|
|
1841
|
+
subject.execute_command command
|
|
1842
|
+
end
|
|
1843
|
+
end
|
|
1844
|
+
end
|
|
1845
|
+
|
|
1846
|
+
end
|
|
1847
|
+
end
|
|
1848
|
+
end
|
|
1849
|
+
end
|
|
1850
|
+
end
|