punchblock 0.8.4 → 0.9.0
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.
- data/.travis.yml +9 -0
- data/CHANGELOG.md +5 -0
- data/Rakefile +0 -6
- data/lib/punchblock.rb +0 -6
- data/lib/punchblock/command/dial.rb +1 -0
- data/lib/punchblock/connection/xmpp.rb +2 -1
- data/lib/punchblock/translator/asterisk/call.rb +12 -10
- data/lib/punchblock/translator/asterisk/component.rb +2 -0
- data/lib/punchblock/translator/asterisk/component/asterisk.rb +0 -2
- data/lib/punchblock/translator/asterisk/component/input.rb +114 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +92 -0
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +0 -1
- data/spec/punchblock/translator/asterisk/call_spec.rb +29 -3
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +282 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +491 -0
- metadata +46 -60
- data/lib/punchblock/component/tropo.rb +0 -9
- data/lib/punchblock/component/tropo/conference.rb +0 -331
- data/lib/punchblock/translator/asterisk/component/asterisk/input.rb +0 -116
- data/lib/punchblock/translator/asterisk/component/asterisk/output.rb +0 -94
- data/spec/punchblock/component/tropo/conference_spec.rb +0 -361
- data/spec/punchblock/translator/asterisk/component/asterisk/input_spec.rb +0 -284
- data/spec/punchblock/translator/asterisk/component/asterisk/output_spec.rb +0 -493
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
module Punchblock
|
|
4
|
-
module Translator
|
|
5
|
-
class Asterisk
|
|
6
|
-
module Component
|
|
7
|
-
module Asterisk
|
|
8
|
-
describe Input do
|
|
9
|
-
let(:connection) do
|
|
10
|
-
mock_connection_with_event_handler do |event|
|
|
11
|
-
command.add_event event
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
let(:media_engine) { nil }
|
|
15
|
-
let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
|
|
16
|
-
let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
|
|
17
|
-
let(:command_options) { {} }
|
|
18
|
-
|
|
19
|
-
let :command do
|
|
20
|
-
Punchblock::Component::Input.new command_options
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
let :grammar do
|
|
24
|
-
RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
|
|
25
|
-
rule id: 'digit' do
|
|
26
|
-
one_of do
|
|
27
|
-
0.upto(9) { |d| item { d.to_s } }
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
rule id: 'pin', scope: 'public' do
|
|
32
|
-
item repeat: '2' do
|
|
33
|
-
ruleref uri: '#digit'
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
subject { Input.new command, call }
|
|
40
|
-
|
|
41
|
-
describe '#execute' do
|
|
42
|
-
before { command.request! }
|
|
43
|
-
|
|
44
|
-
context 'with a media engine of :unimrcp' do
|
|
45
|
-
pending
|
|
46
|
-
let(:media_engine) { :unimrcp }
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
context 'with a media engine of :asterisk' do
|
|
50
|
-
let(:media_engine) { :asterisk }
|
|
51
|
-
|
|
52
|
-
let(:command_opts) { {} }
|
|
53
|
-
|
|
54
|
-
let :command_options do
|
|
55
|
-
{ :mode => :dtmf, :grammar => { :value => grammar } }.merge(command_opts)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def ami_event_for_dtmf(digit, position)
|
|
59
|
-
RubyAMI::Event.new('DTMF').tap do |e|
|
|
60
|
-
e['Digit'] = digit.to_s
|
|
61
|
-
e['Start'] = position == :start ? 'Yes' : 'No'
|
|
62
|
-
e['End'] = position == :end ? 'Yes' : 'No'
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def send_ami_events_for_dtmf(digit)
|
|
67
|
-
call.process_ami_event ami_event_for_dtmf(digit, :start)
|
|
68
|
-
call.process_ami_event ami_event_for_dtmf(digit, :end)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
let(:reason) { command.complete_event(5).reason }
|
|
72
|
-
|
|
73
|
-
describe "receiving DTMF events" do
|
|
74
|
-
before do
|
|
75
|
-
subject.execute
|
|
76
|
-
expected_event
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
context "when a match is found" do
|
|
80
|
-
before do
|
|
81
|
-
send_ami_events_for_dtmf 1
|
|
82
|
-
send_ami_events_for_dtmf 2
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
let :expected_event do
|
|
86
|
-
Punchblock::Component::Input::Complete::Success.new :mode => :dtmf,
|
|
87
|
-
:confidence => 1,
|
|
88
|
-
:utterance => '12',
|
|
89
|
-
:interpretation => 'dtmf-1 dtmf-2',
|
|
90
|
-
:component_id => subject.id,
|
|
91
|
-
:call_id => call.id
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
it "should send a success complete event with the relevant data" do
|
|
95
|
-
reason.should == expected_event
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
context "when the match is invalid" do
|
|
100
|
-
before do
|
|
101
|
-
send_ami_events_for_dtmf 1
|
|
102
|
-
send_ami_events_for_dtmf '#'
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
let :expected_event do
|
|
106
|
-
Punchblock::Component::Input::Complete::NoMatch.new :component_id => subject.id,
|
|
107
|
-
:call_id => call.id
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
it "should send a nomatch complete event" do
|
|
111
|
-
reason.should == expected_event
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
describe 'grammar' do
|
|
117
|
-
context 'unset' do
|
|
118
|
-
let(:command_opts) { { :grammar => nil } }
|
|
119
|
-
it "should return an error and not execute any actions" do
|
|
120
|
-
subject.execute
|
|
121
|
-
error = ProtocolError.new 'option error', 'A grammar document is required.'
|
|
122
|
-
command.response(0.1).should == error
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
describe 'mode' do
|
|
128
|
-
context 'unset' do
|
|
129
|
-
let(:command_opts) { { :mode => nil } }
|
|
130
|
-
it "should return an error and not execute any actions" do
|
|
131
|
-
subject.execute
|
|
132
|
-
error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
|
|
133
|
-
command.response(0.1).should == error
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
context 'any' do
|
|
138
|
-
let(:command_opts) { { :mode => :any } }
|
|
139
|
-
it "should return an error and not execute any actions" do
|
|
140
|
-
subject.execute
|
|
141
|
-
error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
|
|
142
|
-
command.response(0.1).should == error
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
context 'speech' do
|
|
147
|
-
let(:command_opts) { { :mode => :speech } }
|
|
148
|
-
it "should return an error and not execute any actions" do
|
|
149
|
-
subject.execute
|
|
150
|
-
error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
|
|
151
|
-
command.response(0.1).should == error
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
describe 'terminator' do
|
|
157
|
-
pending
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
describe 'recognizer' do
|
|
161
|
-
pending
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
describe 'initial-timeout' do
|
|
165
|
-
context 'a positive number' do
|
|
166
|
-
let(:command_opts) { { :initial_timeout => 1000 } }
|
|
167
|
-
|
|
168
|
-
it "should not cause a NoInput if first input is received in time" do
|
|
169
|
-
subject.execute
|
|
170
|
-
send_ami_events_for_dtmf 1
|
|
171
|
-
sleep 1.5
|
|
172
|
-
send_ami_events_for_dtmf 2
|
|
173
|
-
reason.should be_a Punchblock::Component::Input::Complete::Success
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
it "should cause a NoInput complete event to be sent after the timeout" do
|
|
177
|
-
subject.execute
|
|
178
|
-
sleep 1.5
|
|
179
|
-
send_ami_events_for_dtmf 1
|
|
180
|
-
send_ami_events_for_dtmf 2
|
|
181
|
-
reason.should be_a Punchblock::Component::Input::Complete::NoInput
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
context '-1' do
|
|
186
|
-
let(:command_opts) { { :initial_timeout => -1 } }
|
|
187
|
-
|
|
188
|
-
it "should not start a timer" do
|
|
189
|
-
subject.wrapped_object.expects(:begin_initial_timer).never
|
|
190
|
-
subject.execute
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
context 'unset' do
|
|
195
|
-
let(:command_opts) { { :initial_timeout => nil } }
|
|
196
|
-
|
|
197
|
-
it "should not start a timer" do
|
|
198
|
-
subject.wrapped_object.expects(:begin_initial_timer).never
|
|
199
|
-
subject.execute
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
context 'a negative number other than -1' do
|
|
204
|
-
let(:command_opts) { { :initial_timeout => -1000 } }
|
|
205
|
-
|
|
206
|
-
it "should return an error and not execute any actions" do
|
|
207
|
-
subject.execute
|
|
208
|
-
error = ProtocolError.new 'option error', 'An initial timeout value that is negative (and not -1) is invalid.'
|
|
209
|
-
command.response(0.1).should == error
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
describe 'inter-digit-timeout' do
|
|
215
|
-
context 'a positive number' do
|
|
216
|
-
let(:command_opts) { { :inter_digit_timeout => 1000 } }
|
|
217
|
-
|
|
218
|
-
it "should not prevent a Match if input is received in time" do
|
|
219
|
-
subject.execute
|
|
220
|
-
sleep 1.5
|
|
221
|
-
send_ami_events_for_dtmf 1
|
|
222
|
-
sleep 0.5
|
|
223
|
-
send_ami_events_for_dtmf 2
|
|
224
|
-
reason.should be_a Punchblock::Component::Input::Complete::Success
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
it "should cause a NoMatch complete event to be sent after the timeout" do
|
|
228
|
-
subject.execute
|
|
229
|
-
sleep 1.5
|
|
230
|
-
send_ami_events_for_dtmf 1
|
|
231
|
-
sleep 1.5
|
|
232
|
-
send_ami_events_for_dtmf 2
|
|
233
|
-
reason.should be_a Punchblock::Component::Input::Complete::NoMatch
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
context '-1' do
|
|
238
|
-
let(:command_opts) { { :inter_digit_timeout => -1 } }
|
|
239
|
-
|
|
240
|
-
it "should not start a timer" do
|
|
241
|
-
subject.wrapped_object.expects(:begin_inter_digit_timer).never
|
|
242
|
-
subject.execute
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
context 'unset' do
|
|
247
|
-
let(:command_opts) { { :inter_digit_timeout => nil } }
|
|
248
|
-
|
|
249
|
-
it "should not start a timer" do
|
|
250
|
-
subject.wrapped_object.expects(:begin_inter_digit_timer).never
|
|
251
|
-
subject.execute
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
context 'a negative number other than -1' do
|
|
256
|
-
let(:command_opts) { { :inter_digit_timeout => -1000 } }
|
|
257
|
-
|
|
258
|
-
it "should return an error and not execute any actions" do
|
|
259
|
-
subject.execute
|
|
260
|
-
error = ProtocolError.new 'option error', 'An inter-digit timeout value that is negative (and not -1) is invalid.'
|
|
261
|
-
command.response(0.1).should == error
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
describe 'sensitivity' do
|
|
267
|
-
pending
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
describe 'min-confidence' do
|
|
271
|
-
pending
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
describe 'max-silence' do
|
|
275
|
-
pending
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
end
|
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
module Punchblock
|
|
4
|
-
module Translator
|
|
5
|
-
class Asterisk
|
|
6
|
-
module Component
|
|
7
|
-
module Asterisk
|
|
8
|
-
describe Output do
|
|
9
|
-
let(:connection) do
|
|
10
|
-
mock_connection_with_event_handler do |event|
|
|
11
|
-
command.add_event event
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
let(:media_engine) { nil }
|
|
15
|
-
let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
|
|
16
|
-
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
|
|
17
|
-
|
|
18
|
-
let :command do
|
|
19
|
-
Punchblock::Component::Output.new command_options
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
let :ssml_doc do
|
|
23
|
-
RubySpeech::SSML.draw do
|
|
24
|
-
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
let :command_options do
|
|
29
|
-
{ :ssml => ssml_doc }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
subject { Output.new command, mock_call }
|
|
33
|
-
|
|
34
|
-
describe '#execute' do
|
|
35
|
-
before { command.request! }
|
|
36
|
-
|
|
37
|
-
context 'with a media engine of :unimrcp' do
|
|
38
|
-
let(:media_engine) { :unimrcp }
|
|
39
|
-
|
|
40
|
-
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
41
|
-
|
|
42
|
-
let :ssml_doc do
|
|
43
|
-
RubySpeech::SSML.draw do
|
|
44
|
-
audio :src => audio_filename
|
|
45
|
-
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
let(:command_opts) { {} }
|
|
50
|
-
|
|
51
|
-
let :command_options do
|
|
52
|
-
{ :ssml => ssml_doc }.merge(command_opts)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def expect_mrcpsynth_with_options(options)
|
|
56
|
-
mock_call.expects(:send_agi_action!).once.with do |*args|
|
|
57
|
-
args[0].should == 'EXEC MRCPSynth'
|
|
58
|
-
args[2].should match options
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "should execute MRCPSynth" do
|
|
63
|
-
mock_call.expects(:send_agi_action!).once.with 'EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, ''
|
|
64
|
-
subject.execute
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it 'should send a complete event when MRCPSynth completes' do
|
|
68
|
-
def mock_call.send_agi_action!(*args, &block)
|
|
69
|
-
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
70
|
-
end
|
|
71
|
-
subject.execute
|
|
72
|
-
command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
describe 'ssml' do
|
|
76
|
-
context 'unset' do
|
|
77
|
-
let(:command_opts) { { :ssml => nil } }
|
|
78
|
-
it "should return an error and not execute any actions" do
|
|
79
|
-
subject.execute
|
|
80
|
-
error = ProtocolError.new 'option error', 'An SSML document is required.'
|
|
81
|
-
command.response(0.1).should == error
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
describe 'start-offset' do
|
|
87
|
-
context 'unset' do
|
|
88
|
-
let(:command_opts) { { :start_offset => nil } }
|
|
89
|
-
it 'should not pass any options to MRCPSynth' do
|
|
90
|
-
expect_mrcpsynth_with_options(//)
|
|
91
|
-
subject.execute
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
context 'set' do
|
|
96
|
-
let(:command_opts) { { :start_offset => 10 } }
|
|
97
|
-
it "should return an error and not execute any actions" do
|
|
98
|
-
subject.execute
|
|
99
|
-
error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
100
|
-
command.response(0.1).should == error
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
describe 'start-paused' do
|
|
106
|
-
context 'false' do
|
|
107
|
-
let(:command_opts) { { :start_paused => false } }
|
|
108
|
-
it 'should not pass any options to MRCPSynth' do
|
|
109
|
-
expect_mrcpsynth_with_options(//)
|
|
110
|
-
subject.execute
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
context 'true' do
|
|
115
|
-
let(:command_opts) { { :start_paused => true } }
|
|
116
|
-
it "should return an error and not execute any actions" do
|
|
117
|
-
subject.execute
|
|
118
|
-
error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
119
|
-
command.response(0.1).should == error
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
describe 'repeat-interval' do
|
|
125
|
-
context 'unset' do
|
|
126
|
-
let(:command_opts) { { :repeat_interval => nil } }
|
|
127
|
-
it 'should not pass any options to MRCPSynth' do
|
|
128
|
-
expect_mrcpsynth_with_options(//)
|
|
129
|
-
subject.execute
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
context 'set' do
|
|
134
|
-
let(:command_opts) { { :repeat_interval => 10 } }
|
|
135
|
-
it "should return an error and not execute any actions" do
|
|
136
|
-
subject.execute
|
|
137
|
-
error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
138
|
-
command.response(0.1).should == error
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
describe 'repeat-times' do
|
|
144
|
-
context 'unset' do
|
|
145
|
-
let(:command_opts) { { :repeat_times => nil } }
|
|
146
|
-
it 'should not pass any options to MRCPSynth' do
|
|
147
|
-
expect_mrcpsynth_with_options(//)
|
|
148
|
-
subject.execute
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
context 'set' do
|
|
153
|
-
let(:command_opts) { { :repeat_times => 2 } }
|
|
154
|
-
it "should return an error and not execute any actions" do
|
|
155
|
-
subject.execute
|
|
156
|
-
error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
|
|
157
|
-
command.response(0.1).should == error
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
describe 'max-time' do
|
|
163
|
-
context 'unset' do
|
|
164
|
-
let(:command_opts) { { :max_time => nil } }
|
|
165
|
-
it 'should not pass any options to MRCPSynth' do
|
|
166
|
-
expect_mrcpsynth_with_options(//)
|
|
167
|
-
subject.execute
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
context 'set' do
|
|
172
|
-
let(:command_opts) { { :max_time => 30 } }
|
|
173
|
-
it "should return an error and not execute any actions" do
|
|
174
|
-
subject.execute
|
|
175
|
-
error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
176
|
-
command.response(0.1).should == error
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
describe 'voice' do
|
|
182
|
-
context 'unset' do
|
|
183
|
-
let(:command_opts) { { :voice => nil } }
|
|
184
|
-
it 'should not pass the v option to MRCPSynth' do
|
|
185
|
-
expect_mrcpsynth_with_options(//)
|
|
186
|
-
subject.execute
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
context 'set' do
|
|
191
|
-
let(:command_opts) { { :voice => 'alison' } }
|
|
192
|
-
it 'should pass the v option to MRCPSynth' do
|
|
193
|
-
expect_mrcpsynth_with_options(/v=alison/)
|
|
194
|
-
subject.execute
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
describe 'interrupt_on' do
|
|
200
|
-
context "set to nil" do
|
|
201
|
-
let(:command_opts) { { :interrupt_on => nil } }
|
|
202
|
-
it "should not pass the i option to MRCPSynth" do
|
|
203
|
-
expect_mrcpsynth_with_options(//)
|
|
204
|
-
subject.execute
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
context "set to :any" do
|
|
209
|
-
let(:command_opts) { { :interrupt_on => :any } }
|
|
210
|
-
it "should pass the i option to MRCPSynth" do
|
|
211
|
-
expect_mrcpsynth_with_options(/i=any/)
|
|
212
|
-
subject.execute
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
context "set to :dtmf" do
|
|
217
|
-
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
218
|
-
it "should pass the i option to MRCPSynth" do
|
|
219
|
-
expect_mrcpsynth_with_options(/i=any/)
|
|
220
|
-
subject.execute
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
context "set to :speech" do
|
|
225
|
-
let(:command_opts) { { :interrupt_on => :speech } }
|
|
226
|
-
it "should return an error and not execute any actions" do
|
|
227
|
-
subject.execute
|
|
228
|
-
error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
229
|
-
command.response(0.1).should == error
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
context 'with a media engine of :asterisk' do
|
|
236
|
-
let(:media_engine) { :asterisk }
|
|
237
|
-
|
|
238
|
-
def expect_stream_file_with_options(options = nil)
|
|
239
|
-
mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename, options do |*args|
|
|
240
|
-
args[2].should == options
|
|
241
|
-
subject.continue!
|
|
242
|
-
true
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
247
|
-
|
|
248
|
-
let :ssml_doc do
|
|
249
|
-
RubySpeech::SSML.draw do
|
|
250
|
-
audio :src => audio_filename
|
|
251
|
-
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
let(:command_opts) { {} }
|
|
256
|
-
|
|
257
|
-
let :command_options do
|
|
258
|
-
{ :ssml => ssml_doc }.merge(command_opts)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
let :command do
|
|
262
|
-
Punchblock::Component::Output.new command_options
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
describe 'ssml' do
|
|
266
|
-
context 'unset' do
|
|
267
|
-
let(:command_opts) { { :ssml => nil } }
|
|
268
|
-
it "should return an error and not execute any actions" do
|
|
269
|
-
subject.execute
|
|
270
|
-
error = ProtocolError.new 'option error', 'An SSML document is required.'
|
|
271
|
-
command.response(0.1).should == error
|
|
272
|
-
end
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
context 'with a single audio SSML node' do
|
|
276
|
-
let(:audio_filename) { 'http://foo.com/bar.mp3' }
|
|
277
|
-
let :command_options do
|
|
278
|
-
{
|
|
279
|
-
:ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
|
|
280
|
-
}
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
it 'should playback the audio file using STREAM FILE' do
|
|
284
|
-
expect_stream_file_with_options
|
|
285
|
-
subject.execute
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it 'should send a complete event when the file finishes playback' do
|
|
289
|
-
def mock_call.send_agi_action!(*args, &block)
|
|
290
|
-
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
291
|
-
end
|
|
292
|
-
subject.execute
|
|
293
|
-
command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
context 'with multiple audio SSML nodes' do
|
|
298
|
-
let(:audio_filename1) { 'http://foo.com/bar.mp3' }
|
|
299
|
-
let(:audio_filename2) { 'http://foo.com/baz.mp3' }
|
|
300
|
-
let :command_options do
|
|
301
|
-
{
|
|
302
|
-
:ssml => RubySpeech::SSML.draw do
|
|
303
|
-
audio :src => audio_filename1
|
|
304
|
-
audio :src => audio_filename2
|
|
305
|
-
end
|
|
306
|
-
}
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
it 'should playback each audio file using STREAM FILE' do
|
|
310
|
-
latch = CountDownLatch.new 2
|
|
311
|
-
mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename1, nil do
|
|
312
|
-
subject.continue
|
|
313
|
-
true
|
|
314
|
-
latch.countdown!
|
|
315
|
-
end
|
|
316
|
-
mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename2, nil do
|
|
317
|
-
subject.continue
|
|
318
|
-
true
|
|
319
|
-
latch.countdown!
|
|
320
|
-
end
|
|
321
|
-
subject.execute
|
|
322
|
-
latch.wait 2
|
|
323
|
-
sleep 2
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
it 'should send a complete event after the final file has finished playback' do
|
|
327
|
-
def mock_call.send_agi_action!(*args, &block)
|
|
328
|
-
block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
|
|
329
|
-
end
|
|
330
|
-
command.expects(:add_event).once.with do |e|
|
|
331
|
-
e.reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
332
|
-
end
|
|
333
|
-
subject.execute
|
|
334
|
-
end
|
|
335
|
-
end
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
describe 'start-offset' do
|
|
339
|
-
context 'unset' do
|
|
340
|
-
let(:command_opts) { { :start_offset => nil } }
|
|
341
|
-
it 'should not pass any options to STREAM FILE' do
|
|
342
|
-
expect_stream_file_with_options
|
|
343
|
-
subject.execute
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
context 'set' do
|
|
348
|
-
let(:command_opts) { { :start_offset => 10 } }
|
|
349
|
-
it "should return an error and not execute any actions" do
|
|
350
|
-
subject.execute
|
|
351
|
-
error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
|
|
352
|
-
command.response(0.1).should == error
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
describe 'start-paused' do
|
|
358
|
-
context 'false' do
|
|
359
|
-
let(:command_opts) { { :start_paused => false } }
|
|
360
|
-
it 'should not pass any options to STREAM FILE' do
|
|
361
|
-
expect_stream_file_with_options
|
|
362
|
-
subject.execute
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
context 'true' do
|
|
367
|
-
let(:command_opts) { { :start_paused => true } }
|
|
368
|
-
it "should return an error and not execute any actions" do
|
|
369
|
-
subject.execute
|
|
370
|
-
error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
|
|
371
|
-
command.response(0.1).should == error
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
describe 'repeat-interval' do
|
|
377
|
-
context 'unset' do
|
|
378
|
-
let(:command_opts) { { :repeat_interval => nil } }
|
|
379
|
-
it 'should not pass any options to STREAM FILE' do
|
|
380
|
-
expect_stream_file_with_options
|
|
381
|
-
subject.execute
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
|
|
385
|
-
context 'set' do
|
|
386
|
-
let(:command_opts) { { :repeat_interval => 10 } }
|
|
387
|
-
it "should return an error and not execute any actions" do
|
|
388
|
-
subject.execute
|
|
389
|
-
error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
|
390
|
-
command.response(0.1).should == error
|
|
391
|
-
end
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
describe 'repeat-times' do
|
|
396
|
-
context 'unset' do
|
|
397
|
-
let(:command_opts) { { :repeat_times => nil } }
|
|
398
|
-
it 'should not pass any options to STREAM FILE' do
|
|
399
|
-
expect_stream_file_with_options
|
|
400
|
-
subject.execute
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
context 'set' do
|
|
405
|
-
let(:command_opts) { { :repeat_times => 2 } }
|
|
406
|
-
it "should return an error and not execute any actions" do
|
|
407
|
-
subject.execute
|
|
408
|
-
error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
|
|
409
|
-
command.response(0.1).should == error
|
|
410
|
-
end
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
describe 'max-time' do
|
|
415
|
-
context 'unset' do
|
|
416
|
-
let(:command_opts) { { :max_time => nil } }
|
|
417
|
-
it 'should not pass any options to STREAM FILE' do
|
|
418
|
-
expect_stream_file_with_options
|
|
419
|
-
subject.execute
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
context 'set' do
|
|
424
|
-
let(:command_opts) { { :max_time => 30 } }
|
|
425
|
-
it "should return an error and not execute any actions" do
|
|
426
|
-
subject.execute
|
|
427
|
-
error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
|
|
428
|
-
command.response(0.1).should == error
|
|
429
|
-
end
|
|
430
|
-
end
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
describe 'voice' do
|
|
434
|
-
context 'unset' do
|
|
435
|
-
let(:command_opts) { { :voice => nil } }
|
|
436
|
-
it 'should not pass the v option to STREAM FILE' do
|
|
437
|
-
expect_stream_file_with_options
|
|
438
|
-
subject.execute
|
|
439
|
-
end
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
context 'set' do
|
|
443
|
-
let(:command_opts) { { :voice => 'alison' } }
|
|
444
|
-
it "should return an error and not execute any actions" do
|
|
445
|
-
subject.execute
|
|
446
|
-
error = ProtocolError.new 'option error', 'A voice value is unsupported on Asterisk.'
|
|
447
|
-
command.response(0.1).should == error
|
|
448
|
-
end
|
|
449
|
-
end
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
describe 'interrupt_on' do
|
|
453
|
-
context "set to nil" do
|
|
454
|
-
let(:command_opts) { { :interrupt_on => nil } }
|
|
455
|
-
it "should not pass any digits to STREAM FILE" do
|
|
456
|
-
expect_stream_file_with_options
|
|
457
|
-
subject.execute
|
|
458
|
-
end
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
context "set to :any" do
|
|
462
|
-
let(:command_opts) { { :interrupt_on => :any } }
|
|
463
|
-
it "should pass all digits to STREAM FILE" do
|
|
464
|
-
expect_stream_file_with_options '0123456789*#'
|
|
465
|
-
subject.execute
|
|
466
|
-
end
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
context "set to :dtmf" do
|
|
470
|
-
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
471
|
-
it "should pass all digits to STREAM FILE" do
|
|
472
|
-
expect_stream_file_with_options '0123456789*#'
|
|
473
|
-
subject.execute
|
|
474
|
-
end
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
context "set to :speech" do
|
|
478
|
-
let(:command_opts) { { :interrupt_on => :speech } }
|
|
479
|
-
it "should return an error and not execute any actions" do
|
|
480
|
-
subject.execute
|
|
481
|
-
error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
482
|
-
command.response(0.1).should == error
|
|
483
|
-
end
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
end
|
|
487
|
-
end
|
|
488
|
-
end
|
|
489
|
-
end
|
|
490
|
-
end
|
|
491
|
-
end
|
|
492
|
-
end
|
|
493
|
-
end
|