punchblock 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +7 -0
- data/lib/punchblock.rb +1 -1
- data/lib/punchblock/component.rb +2 -0
- data/lib/punchblock/component/input.rb +9 -1
- data/lib/punchblock/component/output.rb +1 -1
- data/lib/punchblock/component/receive_fax.rb +24 -0
- data/lib/punchblock/component/send_fax.rb +62 -0
- data/lib/punchblock/connection/asterisk.rb +1 -1
- data/lib/punchblock/event/complete.rb +15 -1
- data/lib/punchblock/translator/asterisk.rb +30 -15
- data/lib/punchblock/translator/asterisk/call.rb +13 -27
- data/lib/punchblock/translator/asterisk/component.rb +4 -7
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +1 -5
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +8 -9
- data/lib/punchblock/translator/asterisk/component/input.rb +2 -3
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +9 -9
- data/lib/punchblock/translator/asterisk/component/output.rb +134 -39
- data/lib/punchblock/translator/asterisk/component/record.rb +2 -3
- data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +2 -3
- data/lib/punchblock/translator/dtmf_recognizer.rb +2 -4
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +6 -1
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/output.rb +12 -10
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +1 -1
- data/lib/punchblock/version.rb +1 -1
- data/spec/punchblock/component/input_spec.rb +91 -0
- data/spec/punchblock/component/output_spec.rb +1 -2
- data/spec/punchblock/component/receive_fax_spec.rb +111 -0
- data/spec/punchblock/component/send_fax_spec.rb +110 -0
- data/spec/punchblock/connection/asterisk_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/call_spec.rb +53 -79
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +0 -3
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +6 -6
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +3 -3
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +9 -11
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +902 -28
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/component_spec.rb +2 -9
- data/spec/punchblock/translator/asterisk_spec.rb +42 -94
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +5 -5
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +7 -3
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +17 -5
- metadata +67 -61
@@ -81,7 +81,6 @@ module Punchblock
|
|
81
81
|
it 'should send the component node false' do
|
82
82
|
subject.execute
|
83
83
|
original_command.response(1).should be_false
|
84
|
-
subject.should_not be_alive
|
85
84
|
end
|
86
85
|
|
87
86
|
context "which is 'No such channel'" do
|
@@ -90,7 +89,6 @@ module Punchblock
|
|
90
89
|
it "should return an :item_not_found error for the command" do
|
91
90
|
subject.execute
|
92
91
|
original_command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id)
|
93
|
-
subject.should_not be_alive
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
@@ -100,7 +98,6 @@ module Punchblock
|
|
100
98
|
it "should return an :item_not_found error for the command" do
|
101
99
|
subject.execute
|
102
100
|
original_command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id)
|
103
|
-
subject.should_not be_alive
|
104
101
|
end
|
105
102
|
end
|
106
103
|
end
|
@@ -58,7 +58,7 @@ module Punchblock
|
|
58
58
|
context 'for a non-causal action' do
|
59
59
|
it 'should send a complete event to the component node' do
|
60
60
|
ami_client.should_receive(:send_action).once.and_return response
|
61
|
-
subject.
|
61
|
+
subject.should_receive(:send_complete_event).once.with expected_complete_reason
|
62
62
|
subject.execute
|
63
63
|
end
|
64
64
|
end
|
@@ -207,7 +207,7 @@ module Punchblock
|
|
207
207
|
end
|
208
208
|
|
209
209
|
it "sets the command response to true" do
|
210
|
-
call.
|
210
|
+
call.should_receive(:redirect_back).once
|
211
211
|
subject.execute_command command
|
212
212
|
command.response(0.1).should be == true
|
213
213
|
end
|
@@ -219,7 +219,7 @@ module Punchblock
|
|
219
219
|
source_uri: subject.id,
|
220
220
|
target_call_id: call.id
|
221
221
|
|
222
|
-
call.
|
222
|
+
call.should_receive(:redirect_back)
|
223
223
|
subject.execute_command command
|
224
224
|
original_command.should_not be_complete
|
225
225
|
call.process_ami_event ami_event
|
@@ -96,7 +96,7 @@ module Punchblock
|
|
96
96
|
end
|
97
97
|
|
98
98
|
it "should not process further dtmf events" do
|
99
|
-
subject.
|
99
|
+
subject.should_receive(:process_dtmf).never
|
100
100
|
send_ami_events_for_dtmf 3
|
101
101
|
end
|
102
102
|
end
|
@@ -266,7 +266,7 @@ module Punchblock
|
|
266
266
|
end
|
267
267
|
|
268
268
|
it "should not process further dtmf events" do
|
269
|
-
subject.
|
269
|
+
subject.should_receive(:process_dtmf).never
|
270
270
|
send_ami_events_for_dtmf 3
|
271
271
|
end
|
272
272
|
end
|
@@ -331,7 +331,7 @@ module Punchblock
|
|
331
331
|
let(:original_command_opts) { { :initial_timeout => -1 } }
|
332
332
|
|
333
333
|
it "should not start a timer" do
|
334
|
-
subject.
|
334
|
+
subject.should_receive(:begin_initial_timer).never
|
335
335
|
subject.execute
|
336
336
|
end
|
337
337
|
end
|
@@ -340,7 +340,7 @@ module Punchblock
|
|
340
340
|
let(:original_command_opts) { { :initial_timeout => nil } }
|
341
341
|
|
342
342
|
it "should not start a timer" do
|
343
|
-
subject.
|
343
|
+
subject.should_receive(:begin_initial_timer).never
|
344
344
|
subject.execute
|
345
345
|
end
|
346
346
|
end
|
@@ -450,7 +450,7 @@ module Punchblock
|
|
450
450
|
let(:original_command_opts) { { :inter_digit_timeout => -1 } }
|
451
451
|
|
452
452
|
it "should not start a timer" do
|
453
|
-
subject.
|
453
|
+
subject.should_receive(:begin_inter_digit_timer).never
|
454
454
|
subject.execute
|
455
455
|
end
|
456
456
|
end
|
@@ -459,7 +459,7 @@ module Punchblock
|
|
459
459
|
let(:original_command_opts) { { :inter_digit_timeout => nil } }
|
460
460
|
|
461
461
|
it "should not start a timer" do
|
462
|
-
subject.
|
462
|
+
subject.should_receive(:begin_inter_digit_timer).never
|
463
463
|
subject.execute
|
464
464
|
end
|
465
465
|
end
|
@@ -623,13 +623,13 @@ module Punchblock
|
|
623
623
|
end
|
624
624
|
|
625
625
|
it "sets the command response to true" do
|
626
|
-
mock_call.
|
626
|
+
mock_call.should_receive(:redirect_back)
|
627
627
|
subject.execute_command command
|
628
628
|
command.response(0.1).should be == true
|
629
629
|
end
|
630
630
|
|
631
631
|
it "sends the correct complete event" do
|
632
|
-
mock_call.
|
632
|
+
mock_call.should_receive(:redirect_back)
|
633
633
|
subject.execute_command command
|
634
634
|
original_command.should_not be_complete
|
635
635
|
mock_call.process_ami_event ami_event
|
@@ -638,7 +638,7 @@ module Punchblock
|
|
638
638
|
end
|
639
639
|
|
640
640
|
it "redirects the call by unjoining it" do
|
641
|
-
mock_call.
|
641
|
+
mock_call.should_receive(:redirect_back)
|
642
642
|
subject.execute_command command
|
643
643
|
end
|
644
644
|
end
|
@@ -133,22 +133,20 @@ module Punchblock
|
|
133
133
|
context 'with multiple inline documents' do
|
134
134
|
let(:output_command_options) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
|
135
135
|
|
136
|
-
it "should return
|
137
|
-
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(',')
|
138
|
-
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
136
|
+
it "should return an error and not execute any actions" do
|
139
137
|
subject.execute
|
140
|
-
|
138
|
+
error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
|
139
|
+
original_command.response(0.1).should be == error
|
141
140
|
end
|
142
141
|
end
|
143
142
|
|
144
143
|
context 'with multiple documents by URI' do
|
145
144
|
let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}, {url: 'http://example.com/doc2.ssml'}] } }
|
146
145
|
|
147
|
-
it "should return
|
148
|
-
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(',')
|
149
|
-
mock_call.should_receive(:execute_agi_command).once.with('EXEC SynthAndRecog', param).and_return code: 200, result: 1
|
146
|
+
it "should return an error and not execute any actions" do
|
150
147
|
subject.execute
|
151
|
-
|
148
|
+
error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
|
149
|
+
original_command.response(0.1).should be == error
|
152
150
|
end
|
153
151
|
end
|
154
152
|
|
@@ -617,13 +615,13 @@ module Punchblock
|
|
617
615
|
end
|
618
616
|
|
619
617
|
it "sets the command response to true" do
|
620
|
-
mock_call.
|
618
|
+
mock_call.should_receive(:redirect_back)
|
621
619
|
subject.execute_command command
|
622
620
|
command.response(0.1).should be == true
|
623
621
|
end
|
624
622
|
|
625
623
|
it "sends the correct complete event" do
|
626
|
-
mock_call.
|
624
|
+
mock_call.should_receive(:redirect_back)
|
627
625
|
subject.execute_command command
|
628
626
|
original_command.should_not be_complete
|
629
627
|
mock_call.process_ami_event ami_event
|
@@ -632,7 +630,7 @@ module Punchblock
|
|
632
630
|
end
|
633
631
|
|
634
632
|
it "redirects the call by unjoining it" do
|
635
|
-
mock_call.
|
633
|
+
mock_call.should_receive(:redirect_back)
|
636
634
|
subject.execute_command command
|
637
635
|
end
|
638
636
|
end
|
@@ -36,6 +36,13 @@ module Punchblock
|
|
36
36
|
mock_call.stub(:answered?).and_return(value)
|
37
37
|
end
|
38
38
|
|
39
|
+
def expect_mrcpsynth_with_options(options)
|
40
|
+
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
41
|
+
args[0].should be == 'EXEC MRCPSynth'
|
42
|
+
args[1].should match options
|
43
|
+
end.and_return code: 200, result: 1
|
44
|
+
end
|
45
|
+
|
39
46
|
describe '#execute' do
|
40
47
|
before { original_command.request! }
|
41
48
|
|
@@ -167,6 +174,25 @@ module Punchblock
|
|
167
174
|
end
|
168
175
|
|
169
176
|
end
|
177
|
+
|
178
|
+
describe "with multiple documents" do
|
179
|
+
let :first_ssml_doc do
|
180
|
+
RubySpeech::SSML.draw do
|
181
|
+
audio :src => audio_filename
|
182
|
+
end
|
183
|
+
end
|
184
|
+
let :second_ssml_doc do
|
185
|
+
RubySpeech::SSML.draw do
|
186
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
let(:command_opts) { { render_documents: [{value: first_ssml_doc}, {value: second_ssml_doc}] } }
|
190
|
+
|
191
|
+
it "executes Swift with a concatenated version of the documents" do
|
192
|
+
mock_call.should_receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
|
193
|
+
subject.execute
|
194
|
+
end
|
195
|
+
end
|
170
196
|
end
|
171
197
|
|
172
198
|
context 'with a renderer of :unimrcp' do
|
@@ -190,13 +216,6 @@ module Punchblock
|
|
190
216
|
let(:synthstatus) { 'OK' }
|
191
217
|
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
192
218
|
|
193
|
-
def expect_mrcpsynth_with_options(options)
|
194
|
-
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
195
|
-
args[0].should be == 'EXEC MRCPSynth'
|
196
|
-
args[1].should match options
|
197
|
-
end.and_return code: 200, result: 1
|
198
|
-
end
|
199
|
-
|
200
219
|
before { expect_answered }
|
201
220
|
|
202
221
|
it "should execute MRCPSynth" do
|
@@ -265,10 +284,30 @@ module Punchblock
|
|
265
284
|
|
266
285
|
context 'with multiple documents' do
|
267
286
|
let(:command_opts) { { :render_documents => [{:value => ssml_doc}, {:value => ssml_doc}] } }
|
268
|
-
|
287
|
+
|
288
|
+
it "should execute MRCPSynth once with each document" do
|
289
|
+
param = ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')
|
290
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
|
291
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', param).and_return code: 200, result: 1
|
269
292
|
subject.execute
|
270
|
-
|
271
|
-
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'should not execute further output after a stop command' do
|
296
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
297
|
+
sleep 0.5
|
298
|
+
end
|
299
|
+
latch = CountDownLatch.new 1
|
300
|
+
original_command.should_receive(:add_event).once.with do |e|
|
301
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
302
|
+
latch.countdown!
|
303
|
+
end
|
304
|
+
Celluloid::Future.new { subject.execute }
|
305
|
+
sleep 0.2
|
306
|
+
mock_call.should_receive(:redirect_back).ordered
|
307
|
+
stop_command = Punchblock::Component::Stop.new
|
308
|
+
stop_command.request!
|
309
|
+
subject.execute_command stop_command
|
310
|
+
latch.wait(2).should be_true
|
272
311
|
end
|
273
312
|
end
|
274
313
|
end
|
@@ -341,10 +380,40 @@ module Punchblock
|
|
341
380
|
|
342
381
|
context 'set' do
|
343
382
|
let(:command_opts) { { :repeat_times => 2 } }
|
344
|
-
|
383
|
+
|
384
|
+
it "should render the specified number of times" do
|
385
|
+
2.times { expect_mrcpsynth_with_options(//) }
|
345
386
|
subject.execute
|
346
|
-
|
347
|
-
|
387
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
388
|
+
end
|
389
|
+
|
390
|
+
context 'to 0' do
|
391
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
392
|
+
|
393
|
+
it "should render 10,000 the specified number of times" do
|
394
|
+
expect_answered
|
395
|
+
1000.times { expect_mrcpsynth_with_options(//) }
|
396
|
+
subject.execute
|
397
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'should not execute further output after a stop command' do
|
402
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
403
|
+
sleep 0.2
|
404
|
+
end
|
405
|
+
latch = CountDownLatch.new 1
|
406
|
+
original_command.should_receive(:add_event).once.with do |e|
|
407
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
408
|
+
latch.countdown!
|
409
|
+
end
|
410
|
+
Celluloid::Future.new { subject.execute }
|
411
|
+
sleep 0.1
|
412
|
+
mock_call.should_receive(:redirect_back).ordered
|
413
|
+
stop_command = Punchblock::Component::Stop.new
|
414
|
+
stop_command.request!
|
415
|
+
subject.execute_command stop_command
|
416
|
+
latch.wait(2).should be_true
|
348
417
|
end
|
349
418
|
end
|
350
419
|
end
|
@@ -655,6 +724,49 @@ module Punchblock
|
|
655
724
|
original_command.response(0.1).should be == error
|
656
725
|
end
|
657
726
|
end
|
727
|
+
|
728
|
+
context 'with multiple documents' do
|
729
|
+
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
|
730
|
+
|
731
|
+
it "should render each document in turn using a Playback per document" do
|
732
|
+
expect_answered
|
733
|
+
2.times { expect_playback }
|
734
|
+
subject.execute
|
735
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
736
|
+
end
|
737
|
+
|
738
|
+
it 'should not execute further output after a stop command' do
|
739
|
+
expect_answered
|
740
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
741
|
+
sleep 0.2
|
742
|
+
end
|
743
|
+
latch = CountDownLatch.new 1
|
744
|
+
original_command.should_receive(:add_event).once.with do |e|
|
745
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
746
|
+
latch.countdown!
|
747
|
+
end
|
748
|
+
Celluloid::Future.new { subject.execute }
|
749
|
+
sleep 0.1
|
750
|
+
mock_call.should_receive(:redirect_back).ordered
|
751
|
+
stop_command = Punchblock::Component::Stop.new
|
752
|
+
stop_command.request!
|
753
|
+
subject.execute_command stop_command
|
754
|
+
latch.wait(2).should be_true
|
755
|
+
end
|
756
|
+
|
757
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
758
|
+
let(:playbackstatus) { 'FAILED' }
|
759
|
+
|
760
|
+
it "should terminate playback and send an error complete event" do
|
761
|
+
expect_answered
|
762
|
+
mock_call.should_receive(:execute_agi_command).once.and_return code: 200, result: 1
|
763
|
+
subject.execute
|
764
|
+
complete_reason = original_command.complete_event(0.1).reason
|
765
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
766
|
+
complete_reason.details.should == "Terminated due to playback error"
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
658
770
|
end
|
659
771
|
|
660
772
|
describe 'start-offset' do
|
@@ -729,10 +841,42 @@ module Punchblock
|
|
729
841
|
|
730
842
|
context 'set' do
|
731
843
|
let(:command_opts) { { :repeat_times => 2 } }
|
732
|
-
|
844
|
+
|
845
|
+
it "should render the specified number of times" do
|
846
|
+
expect_answered
|
847
|
+
2.times { expect_playback }
|
733
848
|
subject.execute
|
734
|
-
|
735
|
-
|
849
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
850
|
+
end
|
851
|
+
|
852
|
+
context 'to 0' do
|
853
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
854
|
+
|
855
|
+
it "should render 10,000 the specified number of times" do
|
856
|
+
expect_answered
|
857
|
+
1000.times { expect_playback }
|
858
|
+
subject.execute
|
859
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
it 'should not execute further output after a stop command' do
|
864
|
+
expect_answered
|
865
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
866
|
+
sleep 0.2
|
867
|
+
end
|
868
|
+
latch = CountDownLatch.new 1
|
869
|
+
original_command.should_receive(:add_event).once.with do |e|
|
870
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
871
|
+
latch.countdown!
|
872
|
+
end
|
873
|
+
Celluloid::Future.new { subject.execute }
|
874
|
+
sleep 0.1
|
875
|
+
mock_call.should_receive(:redirect_back).ordered
|
876
|
+
stop_command = Punchblock::Component::Stop.new
|
877
|
+
stop_command.request!
|
878
|
+
subject.execute_command stop_command
|
879
|
+
latch.wait(2).should be_true
|
736
880
|
end
|
737
881
|
end
|
738
882
|
end
|
@@ -804,7 +948,7 @@ module Punchblock
|
|
804
948
|
it "does not redirect the call" do
|
805
949
|
expect_answered
|
806
950
|
expect_playback
|
807
|
-
mock_call.
|
951
|
+
mock_call.should_receive(:redirect_back).never
|
808
952
|
subject.execute
|
809
953
|
original_command.response(0.1).should be_a Ref
|
810
954
|
send_ami_events_for_dtmf 1
|
@@ -817,24 +961,24 @@ module Punchblock
|
|
817
961
|
before do
|
818
962
|
expect_answered
|
819
963
|
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
820
|
-
subject.
|
964
|
+
subject.should_receive(:send_finish).and_return nil
|
821
965
|
end
|
822
966
|
|
823
967
|
context "when a DTMF digit is received" do
|
824
968
|
it "sends the correct complete event" do
|
825
|
-
mock_call.
|
969
|
+
mock_call.should_receive :redirect_back
|
826
970
|
subject.execute
|
827
971
|
original_command.response(0.1).should be_a Ref
|
828
972
|
original_command.should_not be_complete
|
829
973
|
send_ami_events_for_dtmf 1
|
830
|
-
mock_call.
|
974
|
+
mock_call.process_ami_event ami_event
|
831
975
|
sleep 0.2
|
832
976
|
original_command.should be_complete
|
833
977
|
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
834
978
|
end
|
835
979
|
|
836
980
|
it "redirects the call back to async AGI" do
|
837
|
-
mock_call.
|
981
|
+
mock_call.should_receive(:redirect_back).once
|
838
982
|
subject.execute
|
839
983
|
original_command.response(0.1).should be_a Ref
|
840
984
|
send_ami_events_for_dtmf 1
|
@@ -848,24 +992,24 @@ module Punchblock
|
|
848
992
|
before do
|
849
993
|
expect_answered
|
850
994
|
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
851
|
-
subject.
|
995
|
+
subject.should_receive(:send_finish).and_return nil
|
852
996
|
end
|
853
997
|
|
854
998
|
context "when a DTMF digit is received" do
|
855
999
|
it "sends the correct complete event" do
|
856
|
-
mock_call.
|
1000
|
+
mock_call.should_receive :redirect_back
|
857
1001
|
subject.execute
|
858
1002
|
original_command.response(0.1).should be_a Ref
|
859
1003
|
original_command.should_not be_complete
|
860
1004
|
send_ami_events_for_dtmf 1
|
861
|
-
mock_call.
|
1005
|
+
mock_call.process_ami_event ami_event
|
862
1006
|
sleep 0.2
|
863
1007
|
original_command.should be_complete
|
864
1008
|
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
865
1009
|
end
|
866
1010
|
|
867
1011
|
it "redirects the call back to async AGI" do
|
868
|
-
mock_call.
|
1012
|
+
mock_call.should_receive(:redirect_back).once
|
869
1013
|
subject.execute
|
870
1014
|
original_command.response(0.1).should be_a Ref
|
871
1015
|
send_ami_events_for_dtmf 1
|
@@ -884,6 +1028,736 @@ module Punchblock
|
|
884
1028
|
end
|
885
1029
|
end
|
886
1030
|
end
|
1031
|
+
|
1032
|
+
context "with a renderer of :native_or_unimrcp" do
|
1033
|
+
def expect_playback(filename = audio_filename)
|
1034
|
+
mock_call.should_receive(:execute_agi_command).ordered.once.with('EXEC Playback', filename).and_return code: 200
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
def expect_playback_noanswer
|
1038
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def expect_mrcpsynth(doc = ssml_doc)
|
1042
|
+
mock_call.should_receive(:execute_agi_command).ordered.once.with('EXEC MRCPSynth', ["\"#{doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
let(:audio_filename) { 'tt-monkeys' }
|
1046
|
+
|
1047
|
+
let :ssml_doc do
|
1048
|
+
RubySpeech::SSML.draw do
|
1049
|
+
audio :src => audio_filename do
|
1050
|
+
string "Foobar"
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
let(:command_opts) { {} }
|
1056
|
+
|
1057
|
+
let :command_options do
|
1058
|
+
{ :render_document => {:value => ssml_doc}, renderer: :native_or_unimrcp }.merge(command_opts)
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
let :original_command do
|
1062
|
+
Punchblock::Component::Output.new command_options
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
let(:playbackstatus) { 'SUCCESS' }
|
1066
|
+
before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
|
1067
|
+
|
1068
|
+
describe 'ssml' do
|
1069
|
+
context 'unset' do
|
1070
|
+
let(:ssml_doc) { nil }
|
1071
|
+
it "should return an error and not execute any actions" do
|
1072
|
+
subject.execute
|
1073
|
+
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
1074
|
+
original_command.response(0.1).should be == error
|
1075
|
+
end
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
context 'with a single audio SSML node' do
|
1079
|
+
let(:audio_filename) { 'tt-monkeys' }
|
1080
|
+
let :ssml_doc do
|
1081
|
+
RubySpeech::SSML.draw language: 'pt-BR' do
|
1082
|
+
audio :src => audio_filename do
|
1083
|
+
voice name: 'frank' do
|
1084
|
+
string "Hello world"
|
1085
|
+
end
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
it 'should playback the audio file using Playback' do
|
1091
|
+
expect_answered
|
1092
|
+
expect_playback
|
1093
|
+
subject.execute
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
it 'should send a complete event when the file finishes playback' do
|
1097
|
+
def mock_call.answered?
|
1098
|
+
true
|
1099
|
+
end
|
1100
|
+
expect_playback
|
1101
|
+
subject.execute
|
1102
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
context "when the audio filename is prefixed by file://" do
|
1106
|
+
let(:audio_filename) { 'file://tt-monkeys' }
|
1107
|
+
|
1108
|
+
it 'should playback the audio file using Playback' do
|
1109
|
+
expect_answered
|
1110
|
+
expect_playback 'tt-monkeys'
|
1111
|
+
subject.execute
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
context "when the audio filename has an extension" do
|
1116
|
+
let(:audio_filename) { 'tt-monkeys.wav' }
|
1117
|
+
|
1118
|
+
it 'should playback the audio file using Playback' do
|
1119
|
+
expect_answered
|
1120
|
+
expect_playback 'tt-monkeys'
|
1121
|
+
subject.execute
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
context "when there are other dots in the filename" do
|
1125
|
+
let(:audio_filename) { 'blue.tt-monkeys.wav' }
|
1126
|
+
|
1127
|
+
it 'should playback the audio file using Playback' do
|
1128
|
+
expect_answered
|
1129
|
+
expect_playback 'blue.tt-monkeys'
|
1130
|
+
subject.execute
|
1131
|
+
end
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
context "when we get a RubyAMI Error" do
|
1136
|
+
it "should send an error complete event" do
|
1137
|
+
expect_answered
|
1138
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
1139
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
1140
|
+
subject.execute
|
1141
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1142
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
1143
|
+
complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
context "when the channel is gone" do
|
1148
|
+
it "should send an error complete event" do
|
1149
|
+
expect_answered
|
1150
|
+
error = ChannelGoneError.new 'FooBar'
|
1151
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
1152
|
+
subject.execute
|
1153
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1154
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
1155
|
+
end
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
1159
|
+
let(:playbackstatus) { 'FAILED' }
|
1160
|
+
|
1161
|
+
let(:synthstatus) { 'SUCCESS' }
|
1162
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
1163
|
+
|
1164
|
+
let :fallback_doc do
|
1165
|
+
RubySpeech::SSML.draw language: 'pt-BR' do
|
1166
|
+
voice name: 'frank' do
|
1167
|
+
string "Hello world"
|
1168
|
+
end
|
1169
|
+
end
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
it "should attempt to render the children of the audio tag via MRCP and then send a complete event" do
|
1173
|
+
expect_answered
|
1174
|
+
expect_playback
|
1175
|
+
expect_mrcpsynth fallback_doc
|
1176
|
+
subject.execute
|
1177
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
1181
|
+
let(:synthstatus) { 'ERROR' }
|
1182
|
+
|
1183
|
+
it "should send an error complete event" do
|
1184
|
+
expect_answered
|
1185
|
+
expect_playback
|
1186
|
+
expect_mrcpsynth fallback_doc
|
1187
|
+
subject.execute
|
1188
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1189
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
1190
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
1191
|
+
end
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
context 'with a single text node without spaces' do
|
1197
|
+
let(:audio_filename) { 'tt-monkeys' }
|
1198
|
+
let :ssml_doc do
|
1199
|
+
RubySpeech::SSML.draw { string audio_filename }
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
it 'should playback the audio file using Playback' do
|
1203
|
+
expect_answered
|
1204
|
+
expect_playback
|
1205
|
+
subject.execute
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
it 'should send a complete event when the file finishes playback' do
|
1209
|
+
expect_answered
|
1210
|
+
expect_playback
|
1211
|
+
subject.execute
|
1212
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
context "when we get a RubyAMI Error" do
|
1216
|
+
it "should send an error complete event" do
|
1217
|
+
expect_answered
|
1218
|
+
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
1219
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
1220
|
+
subject.execute
|
1221
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1222
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
1223
|
+
complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
context "with early media playback" do
|
1228
|
+
it "should play the file with Playback" do
|
1229
|
+
expect_answered false
|
1230
|
+
expect_playback_noanswer
|
1231
|
+
mock_call.should_receive(:send_progress)
|
1232
|
+
subject.execute
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
context "with interrupt_on set to something that is not nil" do
|
1236
|
+
let(:audio_filename) { 'tt-monkeys' }
|
1237
|
+
let :command_options do
|
1238
|
+
{
|
1239
|
+
:render_document => {
|
1240
|
+
:value => RubySpeech::SSML.draw { string audio_filename },
|
1241
|
+
},
|
1242
|
+
:interrupt_on => :any
|
1243
|
+
}
|
1244
|
+
end
|
1245
|
+
it "should return an error when the output is interruptible and it is early media" do
|
1246
|
+
expect_answered false
|
1247
|
+
error = ProtocolError.new.setup 'option error', 'Interrupt digits are not allowed with early media.'
|
1248
|
+
subject.execute
|
1249
|
+
original_command.response(0.1).should be == error
|
1250
|
+
end
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
context 'with multiple audio SSML nodes' do
|
1256
|
+
let(:audio_filename1) { 'foo' }
|
1257
|
+
let(:audio_filename2) { 'bar' }
|
1258
|
+
let(:audio_filename3) { 'baz' }
|
1259
|
+
let :ssml_doc do
|
1260
|
+
RubySpeech::SSML.draw do
|
1261
|
+
audio :src => audio_filename1 do
|
1262
|
+
string "Fallback 1"
|
1263
|
+
end
|
1264
|
+
audio :src => audio_filename2 do
|
1265
|
+
string "Fallback 2"
|
1266
|
+
end
|
1267
|
+
audio :src => audio_filename3 do
|
1268
|
+
string "Fallback 3"
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
it 'should playback all audio files using Playback' do
|
1274
|
+
latch = CountDownLatch.new 2
|
1275
|
+
expect_playback audio_filename1
|
1276
|
+
expect_playback audio_filename2
|
1277
|
+
expect_playback audio_filename3
|
1278
|
+
expect_answered
|
1279
|
+
subject.execute
|
1280
|
+
latch.wait 2
|
1281
|
+
sleep 2
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
it 'should send a complete event after the final file has finished playback' do
|
1285
|
+
expect_answered
|
1286
|
+
expect_playback audio_filename1
|
1287
|
+
expect_playback audio_filename2
|
1288
|
+
expect_playback audio_filename3
|
1289
|
+
latch = CountDownLatch.new 1
|
1290
|
+
original_command.should_receive(:add_event).once.with do |e|
|
1291
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1292
|
+
latch.countdown!
|
1293
|
+
end
|
1294
|
+
subject.execute
|
1295
|
+
latch.wait(2).should be_true
|
1296
|
+
end
|
1297
|
+
|
1298
|
+
it 'should not execute further output after a stop command' do
|
1299
|
+
expect_answered
|
1300
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
1301
|
+
sleep 0.2
|
1302
|
+
end
|
1303
|
+
latch = CountDownLatch.new 1
|
1304
|
+
original_command.should_receive(:add_event).once.with do |e|
|
1305
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1306
|
+
latch.countdown!
|
1307
|
+
end
|
1308
|
+
Celluloid::Future.new { subject.execute }
|
1309
|
+
sleep 0.1
|
1310
|
+
mock_call.should_receive(:redirect_back).ordered
|
1311
|
+
stop_command = Punchblock::Component::Stop.new
|
1312
|
+
stop_command.request!
|
1313
|
+
subject.execute_command stop_command
|
1314
|
+
latch.wait(2).should be_true
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
1318
|
+
let(:synthstatus) { 'SUCCESS' }
|
1319
|
+
before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
|
1320
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
1321
|
+
|
1322
|
+
let :fallback_doc do
|
1323
|
+
RubySpeech::SSML.draw do
|
1324
|
+
string "Fallback 2"
|
1325
|
+
end
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
1329
|
+
expect_answered
|
1330
|
+
expect_playback audio_filename1
|
1331
|
+
expect_playback audio_filename2
|
1332
|
+
expect_mrcpsynth fallback_doc
|
1333
|
+
expect_playback audio_filename3
|
1334
|
+
subject.execute
|
1335
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1336
|
+
end
|
1337
|
+
|
1338
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
1339
|
+
let(:synthstatus) { 'ERROR' }
|
1340
|
+
|
1341
|
+
it "should terminate playback and send an error complete event" do
|
1342
|
+
expect_answered
|
1343
|
+
expect_playback audio_filename1
|
1344
|
+
expect_playback audio_filename2
|
1345
|
+
expect_mrcpsynth fallback_doc
|
1346
|
+
subject.execute
|
1347
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1348
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
1349
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
end
|
1353
|
+
end
|
1354
|
+
|
1355
|
+
context "with an SSML document containing top-level elements other than <audio/>" do
|
1356
|
+
let :ssml_doc do
|
1357
|
+
RubySpeech::SSML.draw do
|
1358
|
+
voice name: 'Paul' do
|
1359
|
+
string "Foo Bar"
|
1360
|
+
end
|
1361
|
+
end
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
|
1365
|
+
|
1366
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
1367
|
+
expect_answered
|
1368
|
+
expect_mrcpsynth ssml_doc
|
1369
|
+
subject.execute
|
1370
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1371
|
+
end
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
context "with mixed TTS and audio tags" do
|
1375
|
+
let :ssml_doc do
|
1376
|
+
RubySpeech::SSML.draw do
|
1377
|
+
voice name: 'Paul' do
|
1378
|
+
string "Foo Bar"
|
1379
|
+
end
|
1380
|
+
audio src: 'tt-monkeys'
|
1381
|
+
voice name: 'Frank' do
|
1382
|
+
string "Doo Dah"
|
1383
|
+
end
|
1384
|
+
string 'tt-weasels'
|
1385
|
+
end
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
let :first_doc do
|
1389
|
+
RubySpeech::SSML.draw do
|
1390
|
+
voice name: 'Paul' do
|
1391
|
+
string "Foo Bar"
|
1392
|
+
end
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
let :second_doc do
|
1397
|
+
RubySpeech::SSML.draw do
|
1398
|
+
voice name: 'Frank' do
|
1399
|
+
string "Doo Dah"
|
1400
|
+
end
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return 'SUCCESS' }
|
1405
|
+
|
1406
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
1407
|
+
expect_answered
|
1408
|
+
expect_mrcpsynth first_doc
|
1409
|
+
expect_playback 'tt-monkeys'
|
1410
|
+
expect_mrcpsynth second_doc
|
1411
|
+
expect_playback 'tt-weasels'
|
1412
|
+
subject.execute
|
1413
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1414
|
+
end
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
context 'with multiple documents' do
|
1418
|
+
let :second_ssml_doc do
|
1419
|
+
RubySpeech::SSML.draw do
|
1420
|
+
audio :src => 'two.wav' do
|
1421
|
+
string "Bazzz"
|
1422
|
+
end
|
1423
|
+
end
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
let :third_ssml_doc do
|
1427
|
+
RubySpeech::SSML.draw do
|
1428
|
+
audio :src => 'three.wav' do
|
1429
|
+
string "Barrrr"
|
1430
|
+
end
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
|
1434
|
+
let(:command_opts) { { render_documents: [{value: ssml_doc}, {value: second_ssml_doc}, {value: third_ssml_doc}] } }
|
1435
|
+
|
1436
|
+
it "should render each document in turn using a Playback per document" do
|
1437
|
+
expect_answered
|
1438
|
+
expect_playback
|
1439
|
+
expect_playback 'two'
|
1440
|
+
expect_playback 'three'
|
1441
|
+
subject.execute
|
1442
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
it 'should not execute further output after a stop command' do
|
1446
|
+
expect_answered
|
1447
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
1448
|
+
sleep 0.2
|
1449
|
+
end
|
1450
|
+
latch = CountDownLatch.new 1
|
1451
|
+
original_command.should_receive(:add_event).once.with do |e|
|
1452
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1453
|
+
latch.countdown!
|
1454
|
+
end
|
1455
|
+
Celluloid::Future.new { subject.execute }
|
1456
|
+
sleep 0.1
|
1457
|
+
mock_call.should_receive(:redirect_back).ordered
|
1458
|
+
stop_command = Punchblock::Component::Stop.new
|
1459
|
+
stop_command.request!
|
1460
|
+
subject.execute_command stop_command
|
1461
|
+
latch.wait(2).should be_true
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
1465
|
+
let(:synthstatus) { 'SUCCESS' }
|
1466
|
+
before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return 'SUCCESS', 'FAILED', 'SUCCESS' }
|
1467
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
1468
|
+
|
1469
|
+
let :fallback_doc do
|
1470
|
+
RubySpeech::SSML.draw do
|
1471
|
+
string "Bazzz"
|
1472
|
+
end
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
it "should attempt to render the document via MRCP and then send a complete event" do
|
1476
|
+
expect_answered
|
1477
|
+
expect_playback
|
1478
|
+
expect_playback 'two'
|
1479
|
+
expect_mrcpsynth fallback_doc
|
1480
|
+
expect_playback 'three'
|
1481
|
+
subject.execute
|
1482
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
context "and the SYNTHSTATUS variable is set to 'ERROR'" do
|
1486
|
+
let(:synthstatus) { 'ERROR' }
|
1487
|
+
|
1488
|
+
it "should terminate playback and send an error complete event" do
|
1489
|
+
expect_answered
|
1490
|
+
expect_playback
|
1491
|
+
expect_playback 'two'
|
1492
|
+
expect_mrcpsynth fallback_doc
|
1493
|
+
subject.execute
|
1494
|
+
complete_reason = original_command.complete_event(0.1).reason
|
1495
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
1496
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
1497
|
+
end
|
1498
|
+
end
|
1499
|
+
end
|
1500
|
+
end
|
1501
|
+
end
|
1502
|
+
|
1503
|
+
describe 'start-offset' do
|
1504
|
+
context 'unset' do
|
1505
|
+
let(:command_opts) { { :start_offset => nil } }
|
1506
|
+
it 'should not pass any options to Playback' do
|
1507
|
+
expect_answered
|
1508
|
+
expect_playback
|
1509
|
+
subject.execute
|
1510
|
+
end
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
context 'set' do
|
1514
|
+
let(:command_opts) { { :start_offset => 10 } }
|
1515
|
+
it "should return an error and not execute any actions" do
|
1516
|
+
subject.execute
|
1517
|
+
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
|
1518
|
+
original_command.response(0.1).should be == error
|
1519
|
+
end
|
1520
|
+
end
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
describe 'start-paused' do
|
1524
|
+
context 'false' do
|
1525
|
+
let(:command_opts) { { :start_paused => false } }
|
1526
|
+
it 'should not pass any options to Playback' do
|
1527
|
+
expect_answered
|
1528
|
+
expect_playback
|
1529
|
+
subject.execute
|
1530
|
+
end
|
1531
|
+
end
|
1532
|
+
|
1533
|
+
context 'true' do
|
1534
|
+
let(:command_opts) { { :start_paused => true } }
|
1535
|
+
it "should return an error and not execute any actions" do
|
1536
|
+
subject.execute
|
1537
|
+
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
|
1538
|
+
original_command.response(0.1).should be == error
|
1539
|
+
end
|
1540
|
+
end
|
1541
|
+
end
|
1542
|
+
|
1543
|
+
describe 'repeat-interval' do
|
1544
|
+
context 'unset' do
|
1545
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
1546
|
+
it 'should not pass any options to Playback' do
|
1547
|
+
expect_answered
|
1548
|
+
expect_playback
|
1549
|
+
subject.execute
|
1550
|
+
end
|
1551
|
+
end
|
1552
|
+
|
1553
|
+
context 'set' do
|
1554
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
1555
|
+
it "should return an error and not execute any actions" do
|
1556
|
+
subject.execute
|
1557
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
|
1558
|
+
original_command.response(0.1).should be == error
|
1559
|
+
end
|
1560
|
+
end
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
describe 'repeat-times' do
|
1564
|
+
context 'unset' do
|
1565
|
+
let(:command_opts) { { :repeat_times => nil } }
|
1566
|
+
it 'should not pass any options to Playback' do
|
1567
|
+
expect_answered
|
1568
|
+
expect_playback
|
1569
|
+
subject.execute
|
1570
|
+
end
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
context 'set' do
|
1574
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
1575
|
+
|
1576
|
+
it "should render the specified number of times" do
|
1577
|
+
expect_answered
|
1578
|
+
2.times { expect_playback }
|
1579
|
+
subject.execute
|
1580
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1581
|
+
end
|
1582
|
+
|
1583
|
+
context 'to 0' do
|
1584
|
+
let(:command_opts) { { :repeat_times => 0 } }
|
1585
|
+
|
1586
|
+
it "should render 10,000 the specified number of times" do
|
1587
|
+
expect_answered
|
1588
|
+
1000.times { expect_playback }
|
1589
|
+
subject.execute
|
1590
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1591
|
+
end
|
1592
|
+
end
|
1593
|
+
|
1594
|
+
it 'should not execute further output after a stop command' do
|
1595
|
+
expect_answered
|
1596
|
+
mock_call.should_receive(:execute_agi_command).once.ordered.and_return do
|
1597
|
+
sleep 0.2
|
1598
|
+
end
|
1599
|
+
latch = CountDownLatch.new 1
|
1600
|
+
original_command.should_receive(:add_event).once.with do |e|
|
1601
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1602
|
+
latch.countdown!
|
1603
|
+
end
|
1604
|
+
Celluloid::Future.new { subject.execute }
|
1605
|
+
sleep 0.1
|
1606
|
+
mock_call.should_receive(:redirect_back).ordered
|
1607
|
+
stop_command = Punchblock::Component::Stop.new
|
1608
|
+
stop_command.request!
|
1609
|
+
subject.execute_command stop_command
|
1610
|
+
latch.wait(2).should be_true
|
1611
|
+
end
|
1612
|
+
end
|
1613
|
+
end
|
1614
|
+
|
1615
|
+
describe 'max-time' do
|
1616
|
+
context 'unset' do
|
1617
|
+
let(:command_opts) { { :max_time => nil } }
|
1618
|
+
it 'should not pass any options to Playback' do
|
1619
|
+
expect_answered
|
1620
|
+
expect_playback
|
1621
|
+
subject.execute
|
1622
|
+
end
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
context 'set' do
|
1626
|
+
let(:command_opts) { { :max_time => 30 } }
|
1627
|
+
it "should return an error and not execute any actions" do
|
1628
|
+
subject.execute
|
1629
|
+
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
|
1630
|
+
original_command.response(0.1).should be == error
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
describe 'voice' do
|
1636
|
+
context 'unset' do
|
1637
|
+
let(:command_opts) { { :voice => nil } }
|
1638
|
+
it 'should not pass the v option to Playback' do
|
1639
|
+
expect_answered
|
1640
|
+
expect_playback
|
1641
|
+
subject.execute
|
1642
|
+
end
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
context 'set' do
|
1646
|
+
let(:command_opts) { { :voice => 'alison' } }
|
1647
|
+
it "should return an error and not execute any actions" do
|
1648
|
+
subject.execute
|
1649
|
+
error = ProtocolError.new.setup 'option error', 'A voice value is unsupported on Asterisk.'
|
1650
|
+
original_command.response(0.1).should be == error
|
1651
|
+
end
|
1652
|
+
end
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
describe 'interrupt_on' do
|
1656
|
+
def ami_event_for_dtmf(digit, position)
|
1657
|
+
RubyAMI::Event.new 'DTMF',
|
1658
|
+
'Digit' => digit.to_s,
|
1659
|
+
'Start' => position == :start ? 'Yes' : 'No',
|
1660
|
+
'End' => position == :end ? 'Yes' : 'No'
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
def send_ami_events_for_dtmf(digit)
|
1664
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :start)
|
1665
|
+
mock_call.process_ami_event ami_event_for_dtmf(digit, :end)
|
1666
|
+
end
|
1667
|
+
|
1668
|
+
let(:reason) { original_command.complete_event(5).reason }
|
1669
|
+
let(:channel) { "SIP/1234-00000000" }
|
1670
|
+
let :ami_event do
|
1671
|
+
RubyAMI::Event.new 'AsyncAGI',
|
1672
|
+
'SubEvent' => "Start",
|
1673
|
+
'Channel' => channel,
|
1674
|
+
'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"
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
context "set to nil" do
|
1678
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
1679
|
+
it "does not redirect the call" do
|
1680
|
+
expect_answered
|
1681
|
+
expect_playback
|
1682
|
+
mock_call.should_receive(:redirect_back).never
|
1683
|
+
subject.execute
|
1684
|
+
original_command.response(0.1).should be_a Ref
|
1685
|
+
send_ami_events_for_dtmf 1
|
1686
|
+
end
|
1687
|
+
end
|
1688
|
+
|
1689
|
+
context "set to :any" do
|
1690
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
1691
|
+
|
1692
|
+
before do
|
1693
|
+
expect_answered
|
1694
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
1695
|
+
subject.should_receive(:send_finish).and_return nil
|
1696
|
+
end
|
1697
|
+
|
1698
|
+
context "when a DTMF digit is received" do
|
1699
|
+
it "sends the correct complete event" do
|
1700
|
+
mock_call.should_receive :redirect_back
|
1701
|
+
subject.execute
|
1702
|
+
original_command.response(0.1).should be_a Ref
|
1703
|
+
original_command.should_not be_complete
|
1704
|
+
send_ami_events_for_dtmf 1
|
1705
|
+
mock_call.process_ami_event ami_event
|
1706
|
+
sleep 0.2
|
1707
|
+
original_command.should be_complete
|
1708
|
+
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
it "redirects the call back to async AGI" do
|
1712
|
+
mock_call.should_receive(:redirect_back).once
|
1713
|
+
subject.execute
|
1714
|
+
original_command.response(0.1).should be_a Ref
|
1715
|
+
send_ami_events_for_dtmf 1
|
1716
|
+
end
|
1717
|
+
end
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
context "set to :dtmf" do
|
1721
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
1722
|
+
|
1723
|
+
before do
|
1724
|
+
expect_answered
|
1725
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
1726
|
+
subject.should_receive(:send_finish).and_return nil
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
context "when a DTMF digit is received" do
|
1730
|
+
it "sends the correct complete event" do
|
1731
|
+
mock_call.should_receive :redirect_back
|
1732
|
+
subject.execute
|
1733
|
+
original_command.response(0.1).should be_a Ref
|
1734
|
+
original_command.should_not be_complete
|
1735
|
+
send_ami_events_for_dtmf 1
|
1736
|
+
mock_call.process_ami_event ami_event
|
1737
|
+
sleep 0.2
|
1738
|
+
original_command.should be_complete
|
1739
|
+
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
1740
|
+
end
|
1741
|
+
|
1742
|
+
it "redirects the call back to async AGI" do
|
1743
|
+
mock_call.should_receive(:redirect_back).once
|
1744
|
+
subject.execute
|
1745
|
+
original_command.response(0.1).should be_a Ref
|
1746
|
+
send_ami_events_for_dtmf 1
|
1747
|
+
end
|
1748
|
+
end
|
1749
|
+
end
|
1750
|
+
|
1751
|
+
context "set to :voice" do
|
1752
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
1753
|
+
it "should return an error and not execute any actions" do
|
1754
|
+
subject.execute
|
1755
|
+
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
1756
|
+
original_command.response(0.1).should be == error
|
1757
|
+
end
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
end
|
887
1761
|
end
|
888
1762
|
|
889
1763
|
describe "#execute_command" do
|
@@ -915,13 +1789,13 @@ module Punchblock
|
|
915
1789
|
end
|
916
1790
|
|
917
1791
|
it "sets the command response to true" do
|
918
|
-
mock_call.
|
1792
|
+
mock_call.should_receive(:redirect_back)
|
919
1793
|
subject.execute_command command
|
920
1794
|
command.response(0.1).should be == true
|
921
1795
|
end
|
922
1796
|
|
923
1797
|
it "sends the correct complete event" do
|
924
|
-
mock_call.
|
1798
|
+
mock_call.should_receive(:redirect_back)
|
925
1799
|
subject.execute_command command
|
926
1800
|
original_command.should_not be_complete
|
927
1801
|
mock_call.process_ami_event ami_event
|
@@ -930,7 +1804,7 @@ module Punchblock
|
|
930
1804
|
end
|
931
1805
|
|
932
1806
|
it "redirects the call by unjoining it" do
|
933
|
-
mock_call.
|
1807
|
+
mock_call.should_receive(:redirect_back)
|
934
1808
|
subject.execute_command command
|
935
1809
|
end
|
936
1810
|
end
|