punchblock 1.9.4 → 2.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/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.markdown +6 -0
- data/Rakefile +16 -0
- data/benchmarks/ami_event_name_comparison.rb +14 -0
- data/benchmarks/channel.rb +27 -0
- data/lib/punchblock/client.rb +2 -6
- data/lib/punchblock/command/accept.rb +3 -24
- data/lib/punchblock/command/answer.rb +3 -24
- data/lib/punchblock/command/dial.rb +24 -76
- data/lib/punchblock/command/hangup.rb +3 -19
- data/lib/punchblock/command/join.rb +21 -70
- data/lib/punchblock/command/mute.rb +3 -3
- data/lib/punchblock/command/redirect.rb +6 -39
- data/lib/punchblock/command/reject.rb +14 -54
- data/lib/punchblock/command/unjoin.rb +8 -40
- data/lib/punchblock/command/unmute.rb +3 -3
- data/lib/punchblock/command_node.rb +0 -17
- data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
- data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
- data/lib/punchblock/component/component_node.rb +1 -1
- data/lib/punchblock/component/input.rb +89 -268
- data/lib/punchblock/component/output.rb +106 -154
- data/lib/punchblock/component/prompt.rb +51 -0
- data/lib/punchblock/component/record.rb +41 -130
- data/lib/punchblock/component.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +31 -4
- data/lib/punchblock/connection/xmpp.rb +6 -14
- data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
- data/lib/punchblock/event/active_speaker.rb +2 -10
- data/lib/punchblock/event/answered.rb +3 -3
- data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
- data/lib/punchblock/event/complete.rb +26 -48
- data/lib/punchblock/event/dtmf.rb +3 -13
- data/lib/punchblock/event/end.rb +10 -11
- data/lib/punchblock/event/joined.rb +5 -25
- data/lib/punchblock/event/offer.rb +4 -25
- data/lib/punchblock/event/ringing.rb +3 -3
- data/lib/punchblock/event/unjoined.rb +5 -25
- data/lib/punchblock/event.rb +0 -10
- data/lib/punchblock/has_headers.rb +20 -26
- data/lib/punchblock/rayo_node.rb +46 -23
- data/lib/punchblock/ref.rb +39 -18
- data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
- data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
- data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
- data/lib/punchblock/translator/asterisk/call.rb +60 -39
- data/lib/punchblock/translator/asterisk/channel.rb +41 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
- data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
- data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
- data/lib/punchblock/translator/asterisk/component.rb +6 -5
- data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
- data/lib/punchblock/translator/asterisk.rb +24 -28
- data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
- data/lib/punchblock/translator/freeswitch/call.rb +15 -14
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component.rb +2 -5
- data/lib/punchblock/translator/freeswitch.rb +2 -2
- data/lib/punchblock/translator/input_component.rb +33 -13
- data/lib/punchblock/uri_list.rb +21 -0
- data/lib/punchblock/version.rb +1 -1
- data/lib/punchblock.rb +4 -3
- data/punchblock.gemspec +7 -3
- data/spec/punchblock/client/component_registry_spec.rb +1 -1
- data/spec/punchblock/client_spec.rb +10 -26
- data/spec/punchblock/command/accept_spec.rb +41 -7
- data/spec/punchblock/command/answer_spec.rb +51 -7
- data/spec/punchblock/command/dial_spec.rb +56 -14
- data/spec/punchblock/command/hangup_spec.rb +41 -7
- data/spec/punchblock/command/join_spec.rb +53 -11
- data/spec/punchblock/command/mute_spec.rb +19 -4
- data/spec/punchblock/command/redirect_spec.rb +40 -10
- data/spec/punchblock/command/reject_spec.rb +43 -11
- data/spec/punchblock/command/unjoin_spec.rb +40 -9
- data/spec/punchblock/command/unmute_spec.rb +19 -4
- data/spec/punchblock/command_node_spec.rb +0 -4
- data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
- data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
- data/spec/punchblock/component/component_node_spec.rb +3 -5
- data/spec/punchblock/component/input_spec.rb +194 -61
- data/spec/punchblock/component/output_spec.rb +194 -62
- data/spec/punchblock/component/prompt_spec.rb +132 -0
- data/spec/punchblock/component/record_spec.rb +70 -32
- data/spec/punchblock/connection/asterisk_spec.rb +17 -3
- data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
- data/spec/punchblock/connection/xmpp_spec.rb +20 -38
- data/spec/punchblock/event/answered_spec.rb +12 -10
- data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
- data/spec/punchblock/event/complete_spec.rb +15 -19
- data/spec/punchblock/event/dtmf_spec.rb +5 -6
- data/spec/punchblock/event/end_spec.rb +20 -10
- data/spec/punchblock/event/joined_spec.rb +8 -7
- data/spec/punchblock/event/offer_spec.rb +41 -12
- data/spec/punchblock/event/ringing_spec.rb +12 -10
- data/spec/punchblock/event/started_speaking_spec.rb +5 -6
- data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
- data/spec/punchblock/event/unjoined_spec.rb +7 -7
- data/spec/punchblock/ref_spec.rb +86 -9
- data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
- data/spec/punchblock/translator/asterisk_spec.rb +20 -24
- data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
- data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
- data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
- data/spec/punchblock/uri_list_spec.rb +49 -0
- data/spec/punchblock_spec.rb +11 -1
- data/spec/spec_helper.rb +7 -11
- data/spec/support/mock_connection_with_event_handler.rb +1 -1
- metadata +104 -24
- data/lib/punchblock/header.rb +0 -9
- data/lib/punchblock/key_value_pair_node.rb +0 -51
- data/spec/punchblock/header_spec.rb +0 -11
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Asterisk
|
|
8
|
+
module Component
|
|
9
|
+
describe MRCPPrompt do
|
|
10
|
+
include HasMockCallbackConnection
|
|
11
|
+
|
|
12
|
+
let(:media_engine) { :unimrcp }
|
|
13
|
+
let(:ami_client) { double('AMI') }
|
|
14
|
+
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
|
|
15
|
+
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
|
|
16
|
+
|
|
17
|
+
let :ssml_doc do
|
|
18
|
+
RubySpeech::SSML.draw do
|
|
19
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
let :voice_grammar do
|
|
24
|
+
RubySpeech::GRXML.draw :mode => 'voice', :root => 'color' do
|
|
25
|
+
rule id: 'color' do
|
|
26
|
+
one_of do
|
|
27
|
+
item { 'red' }
|
|
28
|
+
item { 'blue' }
|
|
29
|
+
item { 'green' }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
let :dtmf_grammar do
|
|
36
|
+
RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
|
|
37
|
+
rule id: 'digit' do
|
|
38
|
+
one_of do
|
|
39
|
+
0.upto(9) { |d| item { d.to_s } }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
rule id: 'pin', scope: 'public' do
|
|
44
|
+
item repeat: '2' do
|
|
45
|
+
ruleref uri: '#digit'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
let(:grammar) { voice_grammar }
|
|
52
|
+
|
|
53
|
+
let(:output_command_opts) { {} }
|
|
54
|
+
|
|
55
|
+
let :output_command_options do
|
|
56
|
+
{ render_document: {value: ssml_doc} }.merge(output_command_opts)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
let(:input_command_opts) { {} }
|
|
60
|
+
|
|
61
|
+
let :input_command_options do
|
|
62
|
+
{ grammar: {value: grammar} }.merge(input_command_opts)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
let(:command_options) { {} }
|
|
66
|
+
|
|
67
|
+
let :output_command do
|
|
68
|
+
Punchblock::Component::Output.new output_command_options
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
let :input_command do
|
|
72
|
+
Punchblock::Component::Input.new input_command_options
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
let :original_command do
|
|
76
|
+
Punchblock::Component::Prompt.new output_command, input_command, command_options
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
let(:recog_status) { 'OK' }
|
|
80
|
+
let(:recog_completion_cause) { '000' }
|
|
81
|
+
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" }
|
|
82
|
+
|
|
83
|
+
subject { described_class.new original_command, mock_call }
|
|
84
|
+
|
|
85
|
+
before do
|
|
86
|
+
original_command.request!
|
|
87
|
+
{
|
|
88
|
+
'RECOG_STATUS' => recog_status,
|
|
89
|
+
'RECOG_COMPLETION_CAUSE' => recog_completion_cause,
|
|
90
|
+
'RECOG_RESULT' => recog_result
|
|
91
|
+
}.each do |var, val|
|
|
92
|
+
mock_call.stub(:channel_var).with(var).and_return val
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context 'with an invalid recognizer' do
|
|
97
|
+
let(:input_command_opts) { { recognizer: 'foobar' } }
|
|
98
|
+
|
|
99
|
+
it "should return an error and not execute any actions" do
|
|
100
|
+
subject.execute
|
|
101
|
+
error = ProtocolError.new.setup 'option error', 'The recognizer foobar is unsupported.'
|
|
102
|
+
original_command.response(0.1).should be == error
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
[:asterisk].each do |recognizer|
|
|
107
|
+
context "with a recognizer #{recognizer.inspect}" do
|
|
108
|
+
let(:input_command_opts) { { recognizer: recognizer } }
|
|
109
|
+
|
|
110
|
+
it "should return an error and not execute any actions" do
|
|
111
|
+
subject.execute
|
|
112
|
+
error = ProtocolError.new.setup 'option error', "The recognizer #{recognizer} is unsupported."
|
|
113
|
+
original_command.response(0.1).should be == error
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def expect_mrcpsynth_with_options(options)
|
|
119
|
+
expect_app_with_options 'MRCPSynth', options
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def expect_synthandrecog_with_options(options)
|
|
123
|
+
expect_app_with_options 'SynthAndRecog', options
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def expect_app_with_options(app, options)
|
|
127
|
+
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
|
128
|
+
args[0].should be == "EXEC #{app}"
|
|
129
|
+
args[1].should match options
|
|
130
|
+
end.and_return code: 200, result: 1
|
|
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 a ref and execute SynthAndRecog" do
|
|
138
|
+
param = [[ssml_doc.to_doc.to_s, ssml_doc.to_doc.to_s].join(','), grammar.to_doc].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',')
|
|
139
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
|
140
|
+
subject.execute
|
|
141
|
+
original_command.response(0.1).should be_a Ref
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
context 'with multiple documents by URI' do
|
|
146
|
+
let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}, {url: 'http://example.com/doc2.ssml'}] } }
|
|
147
|
+
|
|
148
|
+
it "should return a ref and execute SynthAndRecog" do
|
|
149
|
+
param = [['http://example.com/doc1.ssml', 'http://example.com/doc2.ssml'].join(','), grammar.to_doc].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',')
|
|
150
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
|
151
|
+
subject.execute
|
|
152
|
+
original_command.response(0.1).should be_a Ref
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'unset' do
|
|
157
|
+
let(:output_command_options) { {} }
|
|
158
|
+
|
|
159
|
+
it "should return an error and not execute any actions" do
|
|
160
|
+
subject.execute
|
|
161
|
+
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
162
|
+
original_command.response(0.1).should be == error
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe 'Output#renderer' do
|
|
168
|
+
[nil, :unimrcp].each do |renderer|
|
|
169
|
+
context renderer.to_s do
|
|
170
|
+
let(:output_command_opts) { { renderer: renderer } }
|
|
171
|
+
|
|
172
|
+
it "should return a ref and execute SynthAndRecog" do
|
|
173
|
+
param = [ssml_doc.to_doc, grammar.to_doc].map { |o| "\"#{o.to_s.squish.gsub('"', '\"')}\"" }.push('uer=1&b=1').join(',')
|
|
174
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
|
175
|
+
subject.execute
|
|
176
|
+
original_command.response(0.1).should be_a Ref
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
context "when SynthAndRecog completes" do
|
|
180
|
+
context "with a match" do
|
|
181
|
+
let :expected_nlsml do
|
|
182
|
+
RubySpeech::NLSML.draw do
|
|
183
|
+
interpretation grammar: 'session:grammar-0', confidence: 0.43 do
|
|
184
|
+
input 'Hello', mode: :speech
|
|
185
|
+
instance 'Hello'
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'should send a match complete event' do
|
|
191
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
|
|
192
|
+
|
|
193
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
194
|
+
subject.execute
|
|
195
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context "with a nomatch cause" do
|
|
200
|
+
let(:recog_completion_cause) { '001' }
|
|
201
|
+
|
|
202
|
+
it 'should send a nomatch complete event' do
|
|
203
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::NoMatch.new
|
|
204
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
205
|
+
subject.execute
|
|
206
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context "with a noinput cause" do
|
|
211
|
+
let(:recog_completion_cause) { '002' }
|
|
212
|
+
|
|
213
|
+
it 'should send a nomatch complete event' do
|
|
214
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::NoInput.new
|
|
215
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
216
|
+
subject.execute
|
|
217
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context "when the RECOG_STATUS variable is set to 'ERROR'" do
|
|
222
|
+
let(:recog_status) { 'ERROR' }
|
|
223
|
+
|
|
224
|
+
it "should send an error complete event" do
|
|
225
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
226
|
+
subject.execute
|
|
227
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
228
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
|
229
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
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
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
|
238
|
+
subject.execute
|
|
239
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
240
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
|
241
|
+
complete_reason.details.should == "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
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
|
249
|
+
subject.execute
|
|
250
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
251
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
[:foobar, :swift, :asterisk].each do |renderer|
|
|
258
|
+
context renderer do
|
|
259
|
+
let(:output_command_opts) { { renderer: renderer } }
|
|
260
|
+
|
|
261
|
+
it "should return an error and not execute any actions" do
|
|
262
|
+
subject.execute
|
|
263
|
+
error = ProtocolError.new.setup 'option error', "The renderer #{renderer} is unsupported."
|
|
264
|
+
original_command.response(0.1).should be == error
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
describe 'barge_in' do
|
|
271
|
+
context 'unset' do
|
|
272
|
+
let(:command_options) { { barge_in: nil } }
|
|
273
|
+
|
|
274
|
+
it 'should pass the b=1 option to SynthAndRecog' do
|
|
275
|
+
expect_synthandrecog_with_options(/b=1/)
|
|
276
|
+
subject.execute
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
context 'true' do
|
|
281
|
+
let(:command_options) { { barge_in: true } }
|
|
282
|
+
|
|
283
|
+
it 'should pass the b=1 option to SynthAndRecog' do
|
|
284
|
+
expect_synthandrecog_with_options(/b=1/)
|
|
285
|
+
subject.execute
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
context 'false' do
|
|
290
|
+
let(:command_options) { { barge_in: false } }
|
|
291
|
+
|
|
292
|
+
it 'should pass the b=0 option to SynthAndRecog' do
|
|
293
|
+
expect_synthandrecog_with_options(/b=0/)
|
|
294
|
+
subject.execute
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
describe 'Output#voice' do
|
|
300
|
+
context 'unset' do
|
|
301
|
+
let(:output_command_opts) { { voice: nil } }
|
|
302
|
+
|
|
303
|
+
it 'should not pass the vn option to SynthAndRecog' do
|
|
304
|
+
expect_synthandrecog_with_options(//)
|
|
305
|
+
subject.execute
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
context 'set' do
|
|
310
|
+
let(:output_command_opts) { { voice: 'alison' } }
|
|
311
|
+
|
|
312
|
+
it 'should pass the vn option to SynthAndRecog' do
|
|
313
|
+
expect_synthandrecog_with_options(/vn=alison/)
|
|
314
|
+
subject.execute
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe 'Output#start-offset' do
|
|
320
|
+
context 'unset' do
|
|
321
|
+
let(:output_command_opts) { { start_offset: nil } }
|
|
322
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
323
|
+
expect_synthandrecog_with_options(//)
|
|
324
|
+
subject.execute
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
context 'set' do
|
|
329
|
+
let(:output_command_opts) { { start_offset: 10 } }
|
|
330
|
+
it "should return an error and not execute any actions" do
|
|
331
|
+
subject.execute
|
|
332
|
+
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
333
|
+
original_command.response(0.1).should be == error
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
describe 'Output#start-paused' do
|
|
339
|
+
context 'false' do
|
|
340
|
+
let(:output_command_opts) { { start_paused: false } }
|
|
341
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
342
|
+
expect_synthandrecog_with_options(//)
|
|
343
|
+
subject.execute
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
context 'true' do
|
|
348
|
+
let(:output_command_opts) { { start_paused: true } }
|
|
349
|
+
it "should return an error and not execute any actions" do
|
|
350
|
+
subject.execute
|
|
351
|
+
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
352
|
+
original_command.response(0.1).should be == error
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
describe 'Output#repeat-interval' do
|
|
358
|
+
context 'unset' do
|
|
359
|
+
let(:output_command_opts) { { repeat_interval: nil } }
|
|
360
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
361
|
+
expect_synthandrecog_with_options(//)
|
|
362
|
+
subject.execute
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
context 'set' do
|
|
367
|
+
let(:output_command_opts) { { repeat_interval: 10 } }
|
|
368
|
+
it "should return an error and not execute any actions" do
|
|
369
|
+
subject.execute
|
|
370
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
371
|
+
original_command.response(0.1).should be == error
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
describe 'Output#repeat-times' do
|
|
377
|
+
context 'unset' do
|
|
378
|
+
let(:output_command_opts) { { repeat_times: nil } }
|
|
379
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
380
|
+
expect_synthandrecog_with_options(//)
|
|
381
|
+
subject.execute
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
context 'set' do
|
|
386
|
+
let(:output_command_opts) { { repeat_times: 2 } }
|
|
387
|
+
it "should return an error and not execute any actions" do
|
|
388
|
+
subject.execute
|
|
389
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.'
|
|
390
|
+
original_command.response(0.1).should be == error
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
describe 'Output#max-time' do
|
|
396
|
+
context 'unset' do
|
|
397
|
+
let(:output_command_opts) { { max_time: nil } }
|
|
398
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
399
|
+
expect_synthandrecog_with_options(//)
|
|
400
|
+
subject.execute
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
context 'set' do
|
|
405
|
+
let(:output_command_opts) { { max_time: 30 } }
|
|
406
|
+
it "should return an error and not execute any actions" do
|
|
407
|
+
subject.execute
|
|
408
|
+
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
409
|
+
original_command.response(0.1).should be == error
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
describe 'Output#interrupt_on' do
|
|
415
|
+
context 'unset' do
|
|
416
|
+
let(:output_command_opts) { { interrupt_on: nil } }
|
|
417
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
418
|
+
expect_synthandrecog_with_options(//)
|
|
419
|
+
subject.execute
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
context 'set' do
|
|
424
|
+
let(:output_command_opts) { { interrupt_on: :dtmf } }
|
|
425
|
+
it "should return an error and not execute any actions" do
|
|
426
|
+
subject.execute
|
|
427
|
+
error = ProtocolError.new.setup 'option error', 'A interrupt_on value is unsupported on Asterisk.'
|
|
428
|
+
original_command.response(0.1).should be == error
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
describe 'Output#grammar' do
|
|
434
|
+
context 'with multiple inline grammars' do
|
|
435
|
+
let(:input_command_options) { { grammars: [{value: voice_grammar}, {value: dtmf_grammar}] } }
|
|
436
|
+
|
|
437
|
+
it "should return a ref and execute SynthAndRecog" do
|
|
438
|
+
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(',')
|
|
439
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
|
440
|
+
subject.execute
|
|
441
|
+
original_command.response(0.1).should be_a Ref
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
context 'with multiple grammars by URI' do
|
|
446
|
+
let(:input_command_options) { { grammars: [{url: 'http://example.com/grammar1.grxml'}, {url: 'http://example.com/grammar2.grxml'}] } }
|
|
447
|
+
|
|
448
|
+
it "should return a ref and execute SynthAndRecog" do
|
|
449
|
+
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(',')
|
|
450
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
|
451
|
+
subject.execute
|
|
452
|
+
original_command.response(0.1).should be_a Ref
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
context 'unset' do
|
|
457
|
+
let(:input_command_options) { {} }
|
|
458
|
+
|
|
459
|
+
it "should return an error and not execute any actions" do
|
|
460
|
+
subject.execute
|
|
461
|
+
error = ProtocolError.new.setup 'option error', 'A grammar is required.'
|
|
462
|
+
original_command.response(0.1).should be == error
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
describe 'Input#initial-timeout' do
|
|
468
|
+
context 'a positive number' do
|
|
469
|
+
let(:input_command_opts) { { initial_timeout: 1000 } }
|
|
470
|
+
|
|
471
|
+
it 'should pass the nit option to SynthAndRecog' do
|
|
472
|
+
expect_synthandrecog_with_options(/nit=1000/)
|
|
473
|
+
subject.execute
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
context '-1' do
|
|
478
|
+
let(:input_command_opts) { { initial_timeout: -1 } }
|
|
479
|
+
|
|
480
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
481
|
+
expect_synthandrecog_with_options(//)
|
|
482
|
+
subject.execute
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
context 'unset' do
|
|
487
|
+
let(:input_command_opts) { { initial_timeout: nil } }
|
|
488
|
+
|
|
489
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
490
|
+
expect_synthandrecog_with_options(//)
|
|
491
|
+
subject.execute
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
context 'a negative number other than -1' do
|
|
496
|
+
let(:input_command_opts) { { initial_timeout: -1000 } }
|
|
497
|
+
|
|
498
|
+
it "should return an error and not execute any actions" do
|
|
499
|
+
subject.execute
|
|
500
|
+
error = ProtocolError.new.setup 'option error', 'An initial-timeout value must be -1 or a positive integer.'
|
|
501
|
+
original_command.response(0.1).should be == error
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
describe 'Input#inter-digit-timeout' do
|
|
507
|
+
context 'a positive number' do
|
|
508
|
+
let(:input_command_opts) { { inter_digit_timeout: 1000 } }
|
|
509
|
+
|
|
510
|
+
it 'should pass the dit option to SynthAndRecog' do
|
|
511
|
+
expect_synthandrecog_with_options(/dit=1000/)
|
|
512
|
+
subject.execute
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
context '-1' do
|
|
517
|
+
let(:input_command_opts) { { inter_digit_timeout: -1 } }
|
|
518
|
+
|
|
519
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
520
|
+
expect_synthandrecog_with_options(//)
|
|
521
|
+
subject.execute
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
context 'unset' do
|
|
526
|
+
let(:input_command_opts) { { inter_digit_timeout: nil } }
|
|
527
|
+
|
|
528
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
529
|
+
expect_synthandrecog_with_options(//)
|
|
530
|
+
subject.execute
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
context 'a negative number other than -1' do
|
|
535
|
+
let(:input_command_opts) { { inter_digit_timeout: -1000 } }
|
|
536
|
+
|
|
537
|
+
it "should return an error and not execute any actions" do
|
|
538
|
+
subject.execute
|
|
539
|
+
error = ProtocolError.new.setup 'option error', 'An inter-digit-timeout value must be -1 or a positive integer.'
|
|
540
|
+
original_command.response(0.1).should be == error
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
describe 'Input#mode' do
|
|
546
|
+
pending
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
describe 'Input#terminator' do
|
|
550
|
+
context 'a string' do
|
|
551
|
+
let(:input_command_opts) { { terminator: '#' } }
|
|
552
|
+
|
|
553
|
+
it 'should pass the dttc option to SynthAndRecog' do
|
|
554
|
+
expect_synthandrecog_with_options(/dttc=#/)
|
|
555
|
+
subject.execute
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
context 'unset' do
|
|
560
|
+
let(:input_command_opts) { { terminator: nil } }
|
|
561
|
+
|
|
562
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
563
|
+
expect_synthandrecog_with_options(//)
|
|
564
|
+
subject.execute
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
describe 'Input#recognizer' do
|
|
570
|
+
pending
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
describe 'Input#sensitivity' do
|
|
574
|
+
pending
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
describe 'Input#min-confidence' do
|
|
578
|
+
pending
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
describe 'Input#max-silence' do
|
|
582
|
+
pending
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
describe 'Input#match-content-type' do
|
|
586
|
+
pending
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
describe 'Input#language' do
|
|
590
|
+
pending
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
describe "#execute_command" do
|
|
594
|
+
context "with a command it does not understand" do
|
|
595
|
+
let(:command) { Punchblock::Component::Output::Pause.new }
|
|
596
|
+
|
|
597
|
+
before { command.request! }
|
|
598
|
+
it "returns a ProtocolError response" do
|
|
599
|
+
subject.execute_command command
|
|
600
|
+
command.response(0.1).should be_a ProtocolError
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
context "with a Stop command" do
|
|
605
|
+
let(:command) { Punchblock::Component::Stop.new }
|
|
606
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
607
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
608
|
+
let :ami_event do
|
|
609
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
610
|
+
'SubEvent' => "Start",
|
|
611
|
+
'Channel' => channel,
|
|
612
|
+
'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"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
before do
|
|
616
|
+
command.request!
|
|
617
|
+
original_command.execute!
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
it "sets the command response to true" do
|
|
621
|
+
mock_call.async.should_receive(:redirect_back)
|
|
622
|
+
subject.execute_command command
|
|
623
|
+
command.response(0.1).should be == true
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
it "sends the correct complete event" do
|
|
627
|
+
mock_call.async.should_receive(:redirect_back)
|
|
628
|
+
subject.execute_command command
|
|
629
|
+
original_command.should_not be_complete
|
|
630
|
+
mock_call.process_ami_event ami_event
|
|
631
|
+
reason.should be_a Punchblock::Event::Complete::Stop
|
|
632
|
+
original_command.should be_complete
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it "redirects the call by unjoining it" do
|
|
636
|
+
mock_call.async.should_receive(:redirect_back)
|
|
637
|
+
subject.execute_command command
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
end
|