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,652 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Asterisk
|
|
8
|
+
module Component
|
|
9
|
+
describe MRCPNativePrompt 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(:audio_filename) { 'http://example.com/hello.mp3' }
|
|
56
|
+
|
|
57
|
+
let :output_command_options do
|
|
58
|
+
{ render_document: {value: [audio_filename], content_type: 'text/uri-list'} }.merge(output_command_opts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
let(:input_command_opts) { {} }
|
|
62
|
+
|
|
63
|
+
let :input_command_options do
|
|
64
|
+
{ grammar: {value: grammar} }.merge(input_command_opts)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
let(:command_options) { {} }
|
|
68
|
+
|
|
69
|
+
let :output_command do
|
|
70
|
+
Punchblock::Component::Output.new output_command_options
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
let :input_command do
|
|
74
|
+
Punchblock::Component::Input.new input_command_options
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
let :original_command do
|
|
78
|
+
Punchblock::Component::Prompt.new output_command, input_command, command_options
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
let(:recog_status) { 'OK' }
|
|
82
|
+
let(:recog_completion_cause) { '000' }
|
|
83
|
+
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" }
|
|
84
|
+
|
|
85
|
+
subject { described_class.new original_command, mock_call }
|
|
86
|
+
|
|
87
|
+
before do
|
|
88
|
+
original_command.request!
|
|
89
|
+
{
|
|
90
|
+
'RECOG_STATUS' => recog_status,
|
|
91
|
+
'RECOG_COMPLETION_CAUSE' => recog_completion_cause,
|
|
92
|
+
'RECOG_RESULT' => recog_result
|
|
93
|
+
}.each do |var, val|
|
|
94
|
+
mock_call.stub(:channel_var).with(var).and_return val
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context 'with an invalid recognizer' do
|
|
99
|
+
let(:input_command_opts) { { recognizer: 'foobar' } }
|
|
100
|
+
|
|
101
|
+
it "should return an error and not execute any actions" do
|
|
102
|
+
subject.execute
|
|
103
|
+
error = ProtocolError.new.setup 'option error', 'The recognizer foobar is unsupported.'
|
|
104
|
+
original_command.response(0.1).should be == error
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
[:asterisk].each do |recognizer|
|
|
109
|
+
context "with a recognizer #{recognizer.inspect}" do
|
|
110
|
+
let(:input_command_opts) { { recognizer: recognizer } }
|
|
111
|
+
|
|
112
|
+
it "should return an error and not execute any actions" do
|
|
113
|
+
subject.execute
|
|
114
|
+
error = ProtocolError.new.setup 'option error', "The recognizer #{recognizer} is unsupported."
|
|
115
|
+
original_command.response(0.1).should be == error
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def expect_mrcprecog_with_options(options)
|
|
121
|
+
expect_app_with_options 'MRCPRecog', options
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def expect_app_with_options(app, options)
|
|
125
|
+
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
|
126
|
+
args[0].should be == "EXEC #{app}"
|
|
127
|
+
args[1].should match options
|
|
128
|
+
end.and_return code: 200, result: 1
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe 'Output#document' do
|
|
132
|
+
context 'with multiple inline documents' do
|
|
133
|
+
let(:output_command_options) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
|
|
134
|
+
|
|
135
|
+
it "should return an error and not execute any actions" do
|
|
136
|
+
subject.execute
|
|
137
|
+
error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
|
|
138
|
+
original_command.response(0.1).should be == error
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
context 'with a document by URI' do
|
|
143
|
+
let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}] } }
|
|
144
|
+
|
|
145
|
+
it "should return an error and not execute any actions" do
|
|
146
|
+
subject.execute
|
|
147
|
+
error = ProtocolError.new.setup 'option error', 'Only inline documents are allowed.'
|
|
148
|
+
original_command.response(0.1).should be == error
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
context 'with a urilist > size 1' do
|
|
153
|
+
let(:output_command_options) { { render_documents: [{content_type: 'text/uri-list', value: ['http://example.com/hello.mp3', 'http://example.com/goodbye.mp3']}] } }
|
|
154
|
+
|
|
155
|
+
it "should return an error and not execute any actions" do
|
|
156
|
+
subject.execute
|
|
157
|
+
error = ProtocolError.new.setup 'option error', 'Only one audio file is allowed.'
|
|
158
|
+
original_command.response(0.1).should be == error
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'unset' do
|
|
163
|
+
let(:output_command_options) { {} }
|
|
164
|
+
|
|
165
|
+
it "should return an error and not execute any actions" do
|
|
166
|
+
subject.execute
|
|
167
|
+
error = ProtocolError.new.setup 'option error', 'A document is required.'
|
|
168
|
+
original_command.response(0.1).should be == error
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
describe 'Output#renderer' do
|
|
174
|
+
[nil, :asterisk].each do |renderer|
|
|
175
|
+
context renderer.to_s do
|
|
176
|
+
let(:output_command_opts) { { renderer: renderer } }
|
|
177
|
+
|
|
178
|
+
it "should return a ref and execute MRCPRecog" do
|
|
179
|
+
param = ["\"#{grammar.to_doc.to_s.squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
|
|
180
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
|
|
181
|
+
subject.execute
|
|
182
|
+
original_command.response(0.1).should be_a Ref
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context "when MRCPRecog completes" do
|
|
186
|
+
context "with a match" do
|
|
187
|
+
let :expected_nlsml do
|
|
188
|
+
RubySpeech::NLSML.draw do
|
|
189
|
+
interpretation grammar: 'session:grammar-0', confidence: 0.43 do
|
|
190
|
+
input 'Hello', mode: :speech
|
|
191
|
+
instance 'Hello'
|
|
192
|
+
end
|
|
193
|
+
end.root
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'should send a match complete event' do
|
|
197
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
|
|
198
|
+
|
|
199
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
200
|
+
subject.execute
|
|
201
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
context "with a nomatch cause" do
|
|
206
|
+
let(:recog_completion_cause) { '001' }
|
|
207
|
+
|
|
208
|
+
it 'should send a nomatch complete event' do
|
|
209
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::NoMatch.new
|
|
210
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
211
|
+
subject.execute
|
|
212
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context "with a noinput cause" do
|
|
217
|
+
let(:recog_completion_cause) { '002' }
|
|
218
|
+
|
|
219
|
+
it 'should send a nomatch complete event' do
|
|
220
|
+
expected_complete_reason = Punchblock::Component::Input::Complete::NoInput.new
|
|
221
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
222
|
+
subject.execute
|
|
223
|
+
original_command.complete_event(0.1).reason.should == expected_complete_reason
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context "when the RECOG_STATUS variable is set to 'ERROR'" do
|
|
228
|
+
let(:recog_status) { 'ERROR' }
|
|
229
|
+
|
|
230
|
+
it "should send an error complete event" do
|
|
231
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
|
232
|
+
subject.execute
|
|
233
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
234
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
|
235
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
context "when we get a RubyAMI Error" do
|
|
241
|
+
it "should send an error complete event" do
|
|
242
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
|
243
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
|
244
|
+
subject.execute
|
|
245
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
246
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
|
247
|
+
complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
context "when the channel is gone" do
|
|
252
|
+
it "should send an error complete event" do
|
|
253
|
+
error = ChannelGoneError.new 'FooBar'
|
|
254
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
|
255
|
+
subject.execute
|
|
256
|
+
complete_reason = original_command.complete_event(0.1).reason
|
|
257
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
[:foobar, :swift, :unimrcp].each do |renderer|
|
|
264
|
+
context renderer do
|
|
265
|
+
let(:output_command_opts) { { renderer: renderer } }
|
|
266
|
+
|
|
267
|
+
it "should return an error and not execute any actions" do
|
|
268
|
+
subject.execute
|
|
269
|
+
error = ProtocolError.new.setup 'option error', "The renderer #{renderer} is unsupported."
|
|
270
|
+
original_command.response(0.1).should be == error
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
describe 'barge_in' do
|
|
277
|
+
context 'unset' do
|
|
278
|
+
let(:command_options) { { barge_in: nil } }
|
|
279
|
+
|
|
280
|
+
it 'should pass the b=1 option to MRCPRecog' do
|
|
281
|
+
expect_mrcprecog_with_options(/b=1/)
|
|
282
|
+
subject.execute
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
context 'true' do
|
|
287
|
+
let(:command_options) { { barge_in: true } }
|
|
288
|
+
|
|
289
|
+
it 'should pass the b=1 option to MRCPRecog' do
|
|
290
|
+
expect_mrcprecog_with_options(/b=1/)
|
|
291
|
+
subject.execute
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
context 'false' do
|
|
296
|
+
let(:command_options) { { barge_in: false } }
|
|
297
|
+
|
|
298
|
+
it 'should pass the b=0 option to MRCPRecog' do
|
|
299
|
+
expect_mrcprecog_with_options(/b=0/)
|
|
300
|
+
subject.execute
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
describe 'Output#voice' do
|
|
306
|
+
context 'unset' do
|
|
307
|
+
let(:output_command_opts) { { voice: nil } }
|
|
308
|
+
|
|
309
|
+
it 'should not pass any options to MRCPRecog' do
|
|
310
|
+
expect_mrcprecog_with_options(//)
|
|
311
|
+
subject.execute
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
context 'set' do
|
|
316
|
+
let(:output_command_opts) { { voice: 'alison' } }
|
|
317
|
+
|
|
318
|
+
it 'should not pass any options to MRCPRecog' do
|
|
319
|
+
expect_mrcprecog_with_options(//)
|
|
320
|
+
subject.execute
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
describe 'Output#start-offset' do
|
|
326
|
+
context 'unset' do
|
|
327
|
+
let(:output_command_opts) { { start_offset: nil } }
|
|
328
|
+
it 'should not pass any options to MRCPRecog' do
|
|
329
|
+
expect_mrcprecog_with_options(//)
|
|
330
|
+
subject.execute
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
context 'set' do
|
|
335
|
+
let(:output_command_opts) { { start_offset: 10 } }
|
|
336
|
+
it "should return an error and not execute any actions" do
|
|
337
|
+
subject.execute
|
|
338
|
+
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
339
|
+
original_command.response(0.1).should be == error
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
describe 'Output#start-paused' do
|
|
345
|
+
context 'false' do
|
|
346
|
+
let(:output_command_opts) { { start_paused: false } }
|
|
347
|
+
it 'should not pass any options to MRCPRecog' do
|
|
348
|
+
expect_mrcprecog_with_options(//)
|
|
349
|
+
subject.execute
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
context 'true' do
|
|
354
|
+
let(:output_command_opts) { { start_paused: true } }
|
|
355
|
+
it "should return an error and not execute any actions" do
|
|
356
|
+
subject.execute
|
|
357
|
+
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
358
|
+
original_command.response(0.1).should be == error
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
describe 'Output#repeat-interval' do
|
|
364
|
+
context 'unset' do
|
|
365
|
+
let(:output_command_opts) { { repeat_interval: nil } }
|
|
366
|
+
it 'should not pass any options to MRCPRecog' do
|
|
367
|
+
expect_mrcprecog_with_options(//)
|
|
368
|
+
subject.execute
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
context 'set' do
|
|
373
|
+
let(:output_command_opts) { { repeat_interval: 10 } }
|
|
374
|
+
it "should return an error and not execute any actions" do
|
|
375
|
+
subject.execute
|
|
376
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
377
|
+
original_command.response(0.1).should be == error
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
describe 'Output#repeat-times' do
|
|
383
|
+
context 'unset' do
|
|
384
|
+
let(:output_command_opts) { { repeat_times: nil } }
|
|
385
|
+
it 'should not pass any options to MRCPRecog' do
|
|
386
|
+
expect_mrcprecog_with_options(//)
|
|
387
|
+
subject.execute
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
context 'set' do
|
|
392
|
+
let(:output_command_opts) { { repeat_times: 2 } }
|
|
393
|
+
it "should return an error and not execute any actions" do
|
|
394
|
+
subject.execute
|
|
395
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.'
|
|
396
|
+
original_command.response(0.1).should be == error
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
describe 'Output#max-time' do
|
|
402
|
+
context 'unset' do
|
|
403
|
+
let(:output_command_opts) { { max_time: nil } }
|
|
404
|
+
it 'should not pass any options to MRCPRecog' do
|
|
405
|
+
expect_mrcprecog_with_options(//)
|
|
406
|
+
subject.execute
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
context 'set' do
|
|
411
|
+
let(:output_command_opts) { { max_time: 30 } }
|
|
412
|
+
it "should return an error and not execute any actions" do
|
|
413
|
+
subject.execute
|
|
414
|
+
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
415
|
+
original_command.response(0.1).should be == error
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
describe 'Output#interrupt_on' do
|
|
421
|
+
context 'unset' do
|
|
422
|
+
let(:output_command_opts) { { interrupt_on: nil } }
|
|
423
|
+
it 'should not pass any options to MRCPRecog' do
|
|
424
|
+
expect_mrcprecog_with_options(//)
|
|
425
|
+
subject.execute
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
context 'set' do
|
|
430
|
+
let(:output_command_opts) { { interrupt_on: :dtmf } }
|
|
431
|
+
it "should return an error and not execute any actions" do
|
|
432
|
+
subject.execute
|
|
433
|
+
error = ProtocolError.new.setup 'option error', 'A interrupt_on value is unsupported on Asterisk.'
|
|
434
|
+
original_command.response(0.1).should be == error
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
describe 'Input#grammar' do
|
|
440
|
+
context 'with multiple inline grammars' do
|
|
441
|
+
let(:input_command_options) { { grammars: [{value: voice_grammar}, {value: dtmf_grammar}] } }
|
|
442
|
+
|
|
443
|
+
it "should return a ref and execute MRCPRecog" do
|
|
444
|
+
param = ["\"#{[voice_grammar.to_doc.to_s, dtmf_grammar.to_doc.to_s].join(',').squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
|
|
445
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
|
|
446
|
+
subject.execute
|
|
447
|
+
original_command.response(0.1).should be_a Ref
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
context 'with multiple grammars by URI' do
|
|
452
|
+
let(:input_command_options) { { grammars: [{url: 'http://example.com/grammar1.grxml'}, {url: 'http://example.com/grammar2.grxml'}] } }
|
|
453
|
+
|
|
454
|
+
it "should return a ref and execute MRCPRecog" do
|
|
455
|
+
param = ["\"#{"http://example.com/grammar1.grxml,http://example.com/grammar2.grxml".squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
|
|
456
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
|
|
457
|
+
subject.execute
|
|
458
|
+
original_command.response(0.1).should be_a Ref
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
context 'unset' do
|
|
463
|
+
let(:input_command_options) { {} }
|
|
464
|
+
|
|
465
|
+
it "should return an error and not execute any actions" do
|
|
466
|
+
subject.execute
|
|
467
|
+
error = ProtocolError.new.setup 'option error', 'A grammar is required.'
|
|
468
|
+
original_command.response(0.1).should be == error
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
describe 'Input#initial-timeout' do
|
|
474
|
+
context 'a positive number' do
|
|
475
|
+
let(:input_command_opts) { { initial_timeout: 1000 } }
|
|
476
|
+
|
|
477
|
+
it 'should pass the nit option to MRCPRecog' do
|
|
478
|
+
expect_mrcprecog_with_options(/nit=1000/)
|
|
479
|
+
subject.execute
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
context '-1' do
|
|
484
|
+
let(:input_command_opts) { { initial_timeout: -1 } }
|
|
485
|
+
|
|
486
|
+
it 'should not pass any options to MRCPRecog' do
|
|
487
|
+
expect_mrcprecog_with_options(//)
|
|
488
|
+
subject.execute
|
|
489
|
+
end
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
context 'unset' do
|
|
493
|
+
let(:input_command_opts) { { initial_timeout: nil } }
|
|
494
|
+
|
|
495
|
+
it 'should not pass any options to MRCPRecog' do
|
|
496
|
+
expect_mrcprecog_with_options(//)
|
|
497
|
+
subject.execute
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
context 'a negative number other than -1' do
|
|
502
|
+
let(:input_command_opts) { { initial_timeout: -1000 } }
|
|
503
|
+
|
|
504
|
+
it "should return an error and not execute any actions" do
|
|
505
|
+
subject.execute
|
|
506
|
+
error = ProtocolError.new.setup 'option error', 'An initial-timeout value must be -1 or a positive integer.'
|
|
507
|
+
original_command.response(0.1).should be == error
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
describe 'Input#inter-digit-timeout' do
|
|
513
|
+
context 'a positive number' do
|
|
514
|
+
let(:input_command_opts) { { inter_digit_timeout: 1000 } }
|
|
515
|
+
|
|
516
|
+
it 'should pass the dit option to MRCPRecog' do
|
|
517
|
+
expect_mrcprecog_with_options(/dit=1000/)
|
|
518
|
+
subject.execute
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
context '-1' do
|
|
523
|
+
let(:input_command_opts) { { inter_digit_timeout: -1 } }
|
|
524
|
+
|
|
525
|
+
it 'should not pass any options to MRCPRecog' do
|
|
526
|
+
expect_mrcprecog_with_options(//)
|
|
527
|
+
subject.execute
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
context 'unset' do
|
|
532
|
+
let(:input_command_opts) { { inter_digit_timeout: nil } }
|
|
533
|
+
|
|
534
|
+
it 'should not pass any options to MRCPRecog' do
|
|
535
|
+
expect_mrcprecog_with_options(//)
|
|
536
|
+
subject.execute
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
context 'a negative number other than -1' do
|
|
541
|
+
let(:input_command_opts) { { inter_digit_timeout: -1000 } }
|
|
542
|
+
|
|
543
|
+
it "should return an error and not execute any actions" do
|
|
544
|
+
subject.execute
|
|
545
|
+
error = ProtocolError.new.setup 'option error', 'An inter-digit-timeout value must be -1 or a positive integer.'
|
|
546
|
+
original_command.response(0.1).should be == error
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
describe 'Input#mode' do
|
|
552
|
+
pending
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
describe 'Input#terminator' do
|
|
556
|
+
context 'a string' do
|
|
557
|
+
let(:input_command_opts) { { terminator: '#' } }
|
|
558
|
+
|
|
559
|
+
it 'should pass the dttc option to SynthAndRecog' do
|
|
560
|
+
expect_mrcprecog_with_options(/dttc=#/)
|
|
561
|
+
subject.execute
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
context 'unset' do
|
|
566
|
+
let(:input_command_opts) { { terminator: nil } }
|
|
567
|
+
|
|
568
|
+
it 'should not pass any options to SynthAndRecog' do
|
|
569
|
+
expect_mrcprecog_with_options(//)
|
|
570
|
+
subject.execute
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
describe 'Input#recognizer' do
|
|
576
|
+
pending
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
describe 'Input#sensitivity' do
|
|
580
|
+
pending
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
describe 'Input#min-confidence' do
|
|
584
|
+
pending
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
describe 'Input#max-silence' do
|
|
588
|
+
pending
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
describe 'Input#match-content-type' do
|
|
592
|
+
pending
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
describe 'Input#language' do
|
|
596
|
+
pending
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
describe "#execute_command" do
|
|
600
|
+
context "with a command it does not understand" do
|
|
601
|
+
let(:command) { Punchblock::Component::Output::Pause.new }
|
|
602
|
+
|
|
603
|
+
before { command.request! }
|
|
604
|
+
it "returns a ProtocolError response" do
|
|
605
|
+
subject.execute_command command
|
|
606
|
+
command.response(0.1).should be_a ProtocolError
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
context "with a Stop command" do
|
|
611
|
+
let(:command) { Punchblock::Component::Stop.new }
|
|
612
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
613
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
614
|
+
let :ami_event do
|
|
615
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
616
|
+
'SubEvent' => "Start",
|
|
617
|
+
'Channel' => channel,
|
|
618
|
+
'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"
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
before do
|
|
622
|
+
command.request!
|
|
623
|
+
original_command.execute!
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
it "sets the command response to true" do
|
|
627
|
+
mock_call.async.should_receive(:redirect_back)
|
|
628
|
+
subject.execute_command command
|
|
629
|
+
command.response(0.1).should be == true
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
it "sends the correct complete event" do
|
|
633
|
+
mock_call.async.should_receive(:redirect_back)
|
|
634
|
+
subject.execute_command command
|
|
635
|
+
original_command.should_not be_complete
|
|
636
|
+
mock_call.process_ami_event ami_event
|
|
637
|
+
reason.should be_a Punchblock::Event::Complete::Stop
|
|
638
|
+
original_command.should be_complete
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
it "redirects the call by unjoining it" do
|
|
642
|
+
mock_call.async.should_receive(:redirect_back)
|
|
643
|
+
subject.execute_command command
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
end
|