punchblock 1.9.4 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -10,7 +10,7 @@ module Punchblock
|
|
10
10
|
include HasMockCallbackConnection
|
11
11
|
|
12
12
|
let(:media_engine) { nil }
|
13
|
-
let(:ami_client) {
|
13
|
+
let(:ami_client) { double('AMI') }
|
14
14
|
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
|
15
15
|
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
|
16
16
|
|
@@ -25,7 +25,7 @@ module Punchblock
|
|
25
25
|
end
|
26
26
|
|
27
27
|
let :command_options do
|
28
|
-
{ :
|
28
|
+
{ :render_document => {:value => ssml_doc} }
|
29
29
|
end
|
30
30
|
|
31
31
|
subject { Output.new original_command, mock_call }
|
@@ -62,7 +62,7 @@ module Punchblock
|
|
62
62
|
let(:command_opts) { {} }
|
63
63
|
|
64
64
|
let :command_options do
|
65
|
-
{ :
|
65
|
+
{ :render_document => {:value => ssml_doc} }.merge(command_opts)
|
66
66
|
end
|
67
67
|
|
68
68
|
def ssml_with_options(prefix = '', postfix = '')
|
@@ -80,7 +80,7 @@ module Punchblock
|
|
80
80
|
it 'should send a complete event when Swift completes' do
|
81
81
|
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
82
82
|
subject.execute
|
83
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::
|
83
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
84
84
|
end
|
85
85
|
|
86
86
|
context "when we get a RubyAMI Error" do
|
@@ -94,6 +94,16 @@ module Punchblock
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
context "when the channel is gone" do
|
98
|
+
it "should send an error complete event" do
|
99
|
+
error = ChannelGoneError.new 'FooBar'
|
100
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
101
|
+
subject.execute
|
102
|
+
complete_reason = original_command.complete_event(0.1).reason
|
103
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
97
107
|
context "when the call is not answered" do
|
98
108
|
before { expect_answered false }
|
99
109
|
|
@@ -129,8 +139,8 @@ module Punchblock
|
|
129
139
|
end
|
130
140
|
end
|
131
141
|
|
132
|
-
context "set to :
|
133
|
-
let(:command_opts) { { :interrupt_on => :
|
142
|
+
context "set to :voice" do
|
143
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
134
144
|
it "should return an error and not execute any actions" do
|
135
145
|
subject.execute
|
136
146
|
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
@@ -174,43 +184,30 @@ module Punchblock
|
|
174
184
|
let(:command_opts) { {} }
|
175
185
|
|
176
186
|
let :command_options do
|
177
|
-
{ :
|
187
|
+
{ :render_document => {:value => ssml_doc} }.merge(command_opts)
|
178
188
|
end
|
179
189
|
|
190
|
+
let(:synthstatus) { 'OK' }
|
191
|
+
before { mock_call.stub(:channel_var).with('SYNTHSTATUS').and_return synthstatus }
|
192
|
+
|
180
193
|
def expect_mrcpsynth_with_options(options)
|
181
194
|
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
182
195
|
args[0].should be == 'EXEC MRCPSynth'
|
183
|
-
args[
|
196
|
+
args[1].should match options
|
184
197
|
end.and_return code: 200, result: 1
|
185
198
|
end
|
186
199
|
|
187
200
|
before { expect_answered }
|
188
201
|
|
189
202
|
it "should execute MRCPSynth" do
|
190
|
-
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(
|
203
|
+
mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', ["\"#{ssml_doc.to_s.squish.gsub('"', '\"')}\"", ''].join(',')).and_return code: 200, result: 1
|
191
204
|
subject.execute
|
192
205
|
end
|
193
206
|
|
194
|
-
context "when the SSML document contains commas" do
|
195
|
-
let :ssml_doc do
|
196
|
-
RubySpeech::SSML.draw do
|
197
|
-
string "this, here, is a test"
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
it 'should escape TTS strings containing a comma' do
|
202
|
-
mock_call.should_receive(:execute_agi_command).once.with do |*args|
|
203
|
-
args[0].should be == 'EXEC MRCPSynth'
|
204
|
-
args[1].should match(/this\\, here\\, is a test/)
|
205
|
-
end.and_return code: 200, result: 1
|
206
|
-
subject.execute
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
207
|
it 'should send a complete event when MRCPSynth completes' do
|
211
208
|
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
212
209
|
subject.execute
|
213
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::
|
210
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
214
211
|
end
|
215
212
|
|
216
213
|
context "when we get a RubyAMI Error" do
|
@@ -224,6 +221,16 @@ module Punchblock
|
|
224
221
|
end
|
225
222
|
end
|
226
223
|
|
224
|
+
context "when the channel is gone" do
|
225
|
+
it "should send an error complete event" do
|
226
|
+
error = ChannelGoneError.new 'FooBar'
|
227
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
228
|
+
subject.execute
|
229
|
+
complete_reason = original_command.complete_event(0.1).reason
|
230
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
227
234
|
context "when the call is not answered" do
|
228
235
|
before { expect_answered false }
|
229
236
|
|
@@ -234,15 +241,36 @@ module Punchblock
|
|
234
241
|
end
|
235
242
|
end
|
236
243
|
|
237
|
-
|
244
|
+
context "when the SYNTHSTATUS variable is set to 'ERROR'" do
|
245
|
+
let(:synthstatus) { 'ERROR' }
|
246
|
+
|
247
|
+
it "should send an error complete event" do
|
248
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
249
|
+
subject.execute
|
250
|
+
complete_reason = original_command.complete_event(0.1).reason
|
251
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
252
|
+
complete_reason.details.should == "Terminated due to UniMRCP error"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe 'document' do
|
238
257
|
context 'unset' do
|
239
|
-
let(:
|
258
|
+
let(:ssml_doc) { nil }
|
240
259
|
it "should return an error and not execute any actions" do
|
241
260
|
subject.execute
|
242
261
|
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
243
262
|
original_command.response(0.1).should be == error
|
244
263
|
end
|
245
264
|
end
|
265
|
+
|
266
|
+
context 'with multiple documents' do
|
267
|
+
let(:command_opts) { { :render_documents => [{:value => ssml_doc}, {:value => ssml_doc}] } }
|
268
|
+
it "should return an error and not execute any actions" do
|
269
|
+
subject.execute
|
270
|
+
error = ProtocolError.new.setup 'option error', 'Only a single document is supported.'
|
271
|
+
original_command.response(0.1).should be == error
|
272
|
+
end
|
273
|
+
end
|
246
274
|
end
|
247
275
|
|
248
276
|
describe 'start-offset' do
|
@@ -383,8 +411,8 @@ module Punchblock
|
|
383
411
|
end
|
384
412
|
end
|
385
413
|
|
386
|
-
context "set to :
|
387
|
-
let(:command_opts) { { :interrupt_on => :
|
414
|
+
context "set to :voice" do
|
415
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
388
416
|
it "should return an error and not execute any actions" do
|
389
417
|
subject.execute
|
390
418
|
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
@@ -415,16 +443,19 @@ module Punchblock
|
|
415
443
|
let(:command_opts) { {} }
|
416
444
|
|
417
445
|
let :command_options do
|
418
|
-
{ :
|
446
|
+
{ :render_document => {:value => ssml_doc} }.merge(command_opts)
|
419
447
|
end
|
420
448
|
|
421
449
|
let :original_command do
|
422
450
|
Punchblock::Component::Output.new command_options
|
423
451
|
end
|
424
452
|
|
453
|
+
let(:playbackstatus) { 'SUCCESS' }
|
454
|
+
before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
|
455
|
+
|
425
456
|
describe 'ssml' do
|
426
457
|
context 'unset' do
|
427
|
-
let(:
|
458
|
+
let(:ssml_doc) { nil }
|
428
459
|
it "should return an error and not execute any actions" do
|
429
460
|
subject.execute
|
430
461
|
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
@@ -433,11 +464,9 @@ module Punchblock
|
|
433
464
|
end
|
434
465
|
|
435
466
|
context 'with a single audio SSML node' do
|
436
|
-
let(:audio_filename) { '
|
437
|
-
let :
|
438
|
-
{
|
439
|
-
:ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
|
440
|
-
}
|
467
|
+
let(:audio_filename) { 'tt-monkeys' }
|
468
|
+
let :ssml_doc do
|
469
|
+
RubySpeech::SSML.draw { audio :src => audio_filename }
|
441
470
|
end
|
442
471
|
|
443
472
|
it 'should playback the audio file using Playback' do
|
@@ -452,7 +481,17 @@ module Punchblock
|
|
452
481
|
end
|
453
482
|
expect_playback
|
454
483
|
subject.execute
|
455
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::
|
484
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
485
|
+
end
|
486
|
+
|
487
|
+
context "when the audio filename is prefixed by file://" do
|
488
|
+
let(:audio_filename) { 'file://tt-monkeys' }
|
489
|
+
|
490
|
+
it 'should playback the audio file using Playback' do
|
491
|
+
expect_answered
|
492
|
+
expect_playback 'tt-monkeys'
|
493
|
+
subject.execute
|
494
|
+
end
|
456
495
|
end
|
457
496
|
|
458
497
|
context "when we get a RubyAMI Error" do
|
@@ -466,14 +505,36 @@ module Punchblock
|
|
466
505
|
complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
|
467
506
|
end
|
468
507
|
end
|
508
|
+
|
509
|
+
context "when the channel is gone" do
|
510
|
+
it "should send an error complete event" do
|
511
|
+
expect_answered
|
512
|
+
error = ChannelGoneError.new 'FooBar'
|
513
|
+
mock_call.should_receive(:execute_agi_command).and_raise error
|
514
|
+
subject.execute
|
515
|
+
complete_reason = original_command.complete_event(0.1).reason
|
516
|
+
complete_reason.should be_a Punchblock::Event::Complete::Hangup
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
context "when the PLAYBACKSTATUS variable is set to 'FAILED'" do
|
521
|
+
let(:playbackstatus) { 'FAILED' }
|
522
|
+
|
523
|
+
it "should send an error complete event" do
|
524
|
+
expect_answered
|
525
|
+
mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
|
526
|
+
subject.execute
|
527
|
+
complete_reason = original_command.complete_event(0.1).reason
|
528
|
+
complete_reason.should be_a Punchblock::Event::Complete::Error
|
529
|
+
complete_reason.details.should == "Terminated due to playback error"
|
530
|
+
end
|
531
|
+
end
|
469
532
|
end
|
470
533
|
|
471
534
|
context 'with a single text node without spaces' do
|
472
535
|
let(:audio_filename) { 'tt-monkeys' }
|
473
|
-
let :
|
474
|
-
{
|
475
|
-
:ssml => RubySpeech::SSML.draw { string audio_filename }
|
476
|
-
}
|
536
|
+
let :ssml_doc do
|
537
|
+
RubySpeech::SSML.draw { string audio_filename }
|
477
538
|
end
|
478
539
|
|
479
540
|
it 'should playback the audio file using Playback' do
|
@@ -486,7 +547,7 @@ module Punchblock
|
|
486
547
|
expect_answered
|
487
548
|
expect_playback
|
488
549
|
subject.execute
|
489
|
-
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::
|
550
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Finish
|
490
551
|
end
|
491
552
|
|
492
553
|
context "when we get a RubyAMI Error" do
|
@@ -513,7 +574,9 @@ module Punchblock
|
|
513
574
|
let(:audio_filename) { 'tt-monkeys' }
|
514
575
|
let :command_options do
|
515
576
|
{
|
516
|
-
:
|
577
|
+
:render_document => {
|
578
|
+
:value => RubySpeech::SSML.draw { string audio_filename },
|
579
|
+
},
|
517
580
|
:interrupt_on => :any
|
518
581
|
}
|
519
582
|
end
|
@@ -527,28 +590,14 @@ module Punchblock
|
|
527
590
|
end
|
528
591
|
end
|
529
592
|
|
530
|
-
context 'with a string (not SSML)' do
|
531
|
-
let :command_options do
|
532
|
-
{ :text => 'Foo Bar' }
|
533
|
-
end
|
534
|
-
|
535
|
-
it "should return an unrenderable document error" do
|
536
|
-
subject.execute
|
537
|
-
error = ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.'
|
538
|
-
original_command.response(0.1).should be == error
|
539
|
-
end
|
540
|
-
end
|
541
|
-
|
542
593
|
context 'with multiple audio SSML nodes' do
|
543
594
|
let(:audio_filename1) { 'http://foo.com/bar.mp3' }
|
544
595
|
let(:audio_filename2) { 'http://foo.com/baz.mp3' }
|
545
|
-
let :
|
546
|
-
|
547
|
-
:
|
548
|
-
|
549
|
-
|
550
|
-
end
|
551
|
-
}
|
596
|
+
let :ssml_doc do
|
597
|
+
RubySpeech::SSML.draw do
|
598
|
+
audio :src => audio_filename1
|
599
|
+
audio :src => audio_filename2
|
600
|
+
end
|
552
601
|
end
|
553
602
|
|
554
603
|
it 'should playback all audio files using Playback' do
|
@@ -565,7 +614,7 @@ module Punchblock
|
|
565
614
|
expect_playback [audio_filename1, audio_filename2].join('&')
|
566
615
|
latch = CountDownLatch.new 1
|
567
616
|
original_command.should_receive(:add_event).once.with do |e|
|
568
|
-
e.reason.should be_a Punchblock::Component::Output::Complete::
|
617
|
+
e.reason.should be_a Punchblock::Component::Output::Complete::Finish
|
569
618
|
latch.countdown!
|
570
619
|
end
|
571
620
|
subject.execute
|
@@ -574,12 +623,10 @@ module Punchblock
|
|
574
623
|
end
|
575
624
|
|
576
625
|
context "with an SSML document containing elements other than <audio/>" do
|
577
|
-
let :
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
end
|
582
|
-
}
|
626
|
+
let :ssml_doc do
|
627
|
+
RubySpeech::SSML.draw do
|
628
|
+
string "Foo Bar"
|
629
|
+
end
|
583
630
|
end
|
584
631
|
|
585
632
|
it "should return an unrenderable document error" do
|
@@ -750,7 +797,7 @@ module Punchblock
|
|
750
797
|
before do
|
751
798
|
expect_answered
|
752
799
|
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
753
|
-
subject.wrapped_object.should_receive(:
|
800
|
+
subject.wrapped_object.should_receive(:send_finish).and_return nil
|
754
801
|
end
|
755
802
|
|
756
803
|
context "when a DTMF digit is received" do
|
@@ -763,7 +810,7 @@ module Punchblock
|
|
763
810
|
mock_call.async.process_ami_event ami_event
|
764
811
|
sleep 0.2
|
765
812
|
original_command.should be_complete
|
766
|
-
reason.should be_a Punchblock::Component::Output::Complete::
|
813
|
+
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
767
814
|
end
|
768
815
|
|
769
816
|
it "redirects the call back to async AGI" do
|
@@ -781,7 +828,7 @@ module Punchblock
|
|
781
828
|
before do
|
782
829
|
expect_answered
|
783
830
|
mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
|
784
|
-
subject.wrapped_object.should_receive(:
|
831
|
+
subject.wrapped_object.should_receive(:send_finish).and_return nil
|
785
832
|
end
|
786
833
|
|
787
834
|
context "when a DTMF digit is received" do
|
@@ -794,7 +841,7 @@ module Punchblock
|
|
794
841
|
mock_call.async.process_ami_event ami_event
|
795
842
|
sleep 0.2
|
796
843
|
original_command.should be_complete
|
797
|
-
reason.should be_a Punchblock::Component::Output::Complete::
|
844
|
+
reason.should be_a Punchblock::Component::Output::Complete::Finish
|
798
845
|
end
|
799
846
|
|
800
847
|
it "redirects the call back to async AGI" do
|
@@ -806,8 +853,8 @@ module Punchblock
|
|
806
853
|
end
|
807
854
|
end
|
808
855
|
|
809
|
-
context "set to :
|
810
|
-
let(:command_opts) { { :interrupt_on => :
|
856
|
+
context "set to :voice" do
|
857
|
+
let(:command_opts) { { :interrupt_on => :voice } }
|
811
858
|
it "should return an error and not execute any actions" do
|
812
859
|
subject.execute
|
813
860
|
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
@@ -830,13 +877,16 @@ module Punchblock
|
|
830
877
|
let(:command_opts) { {:renderer => :asterisk} }
|
831
878
|
|
832
879
|
let :command_options do
|
833
|
-
{ :
|
880
|
+
{ :render_document => {:value => ssml_doc} }.merge(command_opts)
|
834
881
|
end
|
835
882
|
|
836
883
|
let :original_command do
|
837
884
|
Punchblock::Component::Output.new command_options
|
838
885
|
end
|
839
886
|
|
887
|
+
let(:playbackstatus) { 'SUCCESS' }
|
888
|
+
before { mock_call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus }
|
889
|
+
|
840
890
|
it "should use the media renderer set and not the platform default" do
|
841
891
|
expect_answered
|
842
892
|
mock_call.should_receive(:execute_agi_command).once.with 'EXEC Playback', audio_filename
|
@@ -11,7 +11,7 @@ module Punchblock
|
|
11
11
|
|
12
12
|
let(:media_engine) { nil }
|
13
13
|
let(:channel) { 'SIP/foo' }
|
14
|
-
let(:ami_client) {
|
14
|
+
let(:ami_client) { double('AMI Client').as_null_object }
|
15
15
|
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
|
16
16
|
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator, ami_client, connection }
|
17
17
|
|
@@ -53,13 +53,13 @@ module Punchblock
|
|
53
53
|
subject.execute
|
54
54
|
end
|
55
55
|
|
56
|
-
it "sends a
|
56
|
+
it "sends a max duration complete event when the recording ends" do
|
57
57
|
full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
|
58
58
|
ami_client.should_receive(:send_action)
|
59
59
|
subject.execute
|
60
60
|
monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
|
61
61
|
mock_call.process_ami_event monitor_stop_event
|
62
|
-
reason.should be_a Punchblock::Component::Record::Complete::
|
62
|
+
reason.should be_a Punchblock::Component::Record::Complete::MaxDuration
|
63
63
|
recording.uri.should be == full_filename
|
64
64
|
original_command.should be_complete
|
65
65
|
end
|
@@ -235,14 +235,23 @@ module Punchblock
|
|
235
235
|
end
|
236
236
|
|
237
237
|
context "when we get a RubyAMI Error" do
|
238
|
-
it "should send an error
|
238
|
+
it "should send an error response" do
|
239
239
|
error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
|
240
240
|
mock_call.should_receive(:execute_agi_command).and_raise error
|
241
241
|
ami_client.should_receive(:send_action).never
|
242
242
|
subject.execute
|
243
|
-
|
244
|
-
|
245
|
-
|
243
|
+
error = ProtocolError.new.setup :platform_error, "Terminated due to AMI error 'FooBar'"
|
244
|
+
original_command.response(0.1).should be == error
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when the channel is no longer available" do
|
249
|
+
it "should send an error complete event" do
|
250
|
+
mock_call.should_receive(:execute_agi_command).and_raise ChannelGoneError
|
251
|
+
ami_client.should_receive(:send_action).never
|
252
|
+
subject.execute
|
253
|
+
error = ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id)
|
254
|
+
original_command.response(0.1).should be == error
|
246
255
|
end
|
247
256
|
end
|
248
257
|
end
|
@@ -297,7 +306,7 @@ module Punchblock
|
|
297
306
|
monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
|
298
307
|
mock_call.process_ami_event monitor_stop_event
|
299
308
|
|
300
|
-
reason.should be_a Punchblock::Component::Record::Complete::
|
309
|
+
reason.should be_a Punchblock::Component::Record::Complete::MaxDuration
|
301
310
|
recording.uri.should be == full_filename
|
302
311
|
original_command.should be_complete
|
303
312
|
end
|
@@ -15,8 +15,8 @@ module Punchblock
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
let(:connection) {
|
19
|
-
let(:ami_client) {
|
18
|
+
let(:connection) { double 'Connection' }
|
19
|
+
let(:ami_client) { double('AMI Client').as_null_object }
|
20
20
|
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
|
21
21
|
let(:mock_call) { Call.new 'SIP/foo', translator, ami_client, connection }
|
22
22
|
|
@@ -29,10 +29,8 @@ module Punchblock
|
|
29
29
|
end
|
30
30
|
|
31
31
|
let :expected_event do
|
32
|
-
Punchblock::Event::Complete.new
|
33
|
-
|
34
|
-
e.component_id = subject.id
|
35
|
-
end
|
32
|
+
Punchblock::Event::Complete.new target_call_id: call.id,
|
33
|
+
component_id: subject.id
|
36
34
|
end
|
37
35
|
|
38
36
|
it "should send the event to the connection" do
|
@@ -46,9 +44,7 @@ module Punchblock
|
|
46
44
|
|
47
45
|
let(:reason) { Punchblock::Event::Complete::Stop.new }
|
48
46
|
let :expected_event do
|
49
|
-
Punchblock::Event::Complete.new
|
50
|
-
c.reason = Punchblock::Event::Complete::Stop.new
|
51
|
-
end
|
47
|
+
Punchblock::Event::Complete.new reason: reason
|
52
48
|
end
|
53
49
|
|
54
50
|
it "should send a complete event with the specified reason" do
|
@@ -6,8 +6,8 @@ require 'ostruct'
|
|
6
6
|
module Punchblock
|
7
7
|
module Translator
|
8
8
|
describe Asterisk do
|
9
|
-
let(:ami_client) {
|
10
|
-
let(:connection) {
|
9
|
+
let(:ami_client) { double 'RubyAMI::Client' }
|
10
|
+
let(:connection) { double 'Connection::Asterisk', handle_event: nil }
|
11
11
|
let(:media_engine) { :asterisk }
|
12
12
|
|
13
13
|
let(:translator) { Asterisk.new ami_client, connection, media_engine }
|
@@ -112,20 +112,20 @@ module Punchblock
|
|
112
112
|
|
113
113
|
it 'should make the call inaccessible by ID' do
|
114
114
|
subject.call_with_id(call_id).should be call
|
115
|
-
subject.deregister_call
|
115
|
+
subject.deregister_call call_id, channel
|
116
116
|
subject.call_with_id(call_id).should be_nil
|
117
117
|
end
|
118
118
|
|
119
119
|
it 'should make the call inaccessible by channel' do
|
120
120
|
subject.call_for_channel(channel).should be call
|
121
|
-
subject.deregister_call
|
121
|
+
subject.deregister_call call_id, channel
|
122
122
|
subject.call_for_channel(channel).should be_nil
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
126
|
describe '#register_component' do
|
127
127
|
let(:component_id) { 'abc123' }
|
128
|
-
let(:component) {
|
128
|
+
let(:component) { double 'Asterisk::Component::Asterisk::AMIAction', :id => component_id }
|
129
129
|
|
130
130
|
it 'should make the component accessible by ID' do
|
131
131
|
subject.register_component component
|
@@ -135,7 +135,7 @@ module Punchblock
|
|
135
135
|
|
136
136
|
describe '#execute_call_command' do
|
137
137
|
let(:call_id) { 'abc123' }
|
138
|
-
let(:command) { Command::Answer.new
|
138
|
+
let(:command) { Command::Answer.new target_call_id: call_id }
|
139
139
|
|
140
140
|
context "with a known call ID" do
|
141
141
|
let(:call) { Translator::Asterisk::Call.new 'SIP/foo', subject, ami_client, connection }
|
@@ -153,16 +153,13 @@ module Punchblock
|
|
153
153
|
end
|
154
154
|
|
155
155
|
let :end_error_event do
|
156
|
-
Punchblock::Event::End.new
|
157
|
-
e.target_call_id = call_id
|
158
|
-
e.reason = :error
|
159
|
-
end
|
156
|
+
Punchblock::Event::End.new reason: :error, target_call_id: call_id
|
160
157
|
end
|
161
158
|
|
162
159
|
context "for an outgoing call which began executing but crashed" do
|
163
160
|
let(:dial_command) { Command::Dial.new :to => 'SIP/1234', :from => 'abc123' }
|
164
161
|
|
165
|
-
let(:call_id) { dial_command.response.
|
162
|
+
let(:call_id) { dial_command.response.call_id }
|
166
163
|
|
167
164
|
before do
|
168
165
|
subject.async.should_receive(:execute_global_command)
|
@@ -238,7 +235,7 @@ module Punchblock
|
|
238
235
|
let(:component_node) { Component::Output.new }
|
239
236
|
let(:component) { Translator::Asterisk::Component::Output.new(component_node, call) }
|
240
237
|
|
241
|
-
let(:command) { Component::Stop.new
|
238
|
+
let(:command) { Component::Stop.new component_id: component.id }
|
242
239
|
|
243
240
|
before do
|
244
241
|
command.request!
|
@@ -281,7 +278,7 @@ module Punchblock
|
|
281
278
|
end
|
282
279
|
|
283
280
|
it 'should instruct the call to send a dial' do
|
284
|
-
mock_call =
|
281
|
+
mock_call = double('Asterisk::Call').as_null_object
|
285
282
|
Asterisk::Call.should_receive(:new_link).once.and_return mock_call
|
286
283
|
mock_call.async.should_receive(:dial).once.with command
|
287
284
|
subject.execute_global_command command
|
@@ -293,7 +290,7 @@ module Punchblock
|
|
293
290
|
Component::Asterisk::AMI::Action.new :name => 'Status', :params => { :channel => 'foo' }
|
294
291
|
end
|
295
292
|
|
296
|
-
let(:mock_action) {
|
293
|
+
let(:mock_action) { double('Asterisk::Component::Asterisk::AMIAction').as_null_object }
|
297
294
|
|
298
295
|
it 'should create a component actor and execute it asynchronously' do
|
299
296
|
Asterisk::Component::Asterisk::AMIAction.should_receive(:new).once.with(command, subject, ami_client).and_return mock_action
|
@@ -322,7 +319,7 @@ module Punchblock
|
|
322
319
|
|
323
320
|
describe '#handle_pb_event' do
|
324
321
|
it 'should forward the event to the connection' do
|
325
|
-
event =
|
322
|
+
event = double 'Punchblock::Event'
|
326
323
|
subject.connection.should_receive(:handle_event).once.with event
|
327
324
|
subject.handle_pb_event event
|
328
325
|
end
|
@@ -338,11 +335,11 @@ module Punchblock
|
|
338
335
|
end
|
339
336
|
|
340
337
|
let :expected_pb_event do
|
341
|
-
Event::Asterisk::AMI::Event.new :
|
342
|
-
:
|
343
|
-
|
344
|
-
|
345
|
-
|
338
|
+
Event::Asterisk::AMI::Event.new name: 'Newchannel',
|
339
|
+
headers: { 'Channel' => "SIP/101-3f3f",
|
340
|
+
'State' => "Ring",
|
341
|
+
'Callerid' => "101",
|
342
|
+
'Uniqueid' => "1094154427.10"}
|
346
343
|
end
|
347
344
|
|
348
345
|
it 'should create a Punchblock AMI event object and pass it to the connection' do
|
@@ -407,9 +404,8 @@ module Punchblock
|
|
407
404
|
end
|
408
405
|
|
409
406
|
it 'should instruct the call to send an offer' do
|
410
|
-
mock_call =
|
411
|
-
Asterisk::Call.should_receive(:
|
412
|
-
subject.wrapped_object.should_receive(:link)
|
407
|
+
mock_call = double('Asterisk::Call').as_null_object
|
408
|
+
Asterisk::Call.should_receive(:new_link).once.and_return mock_call
|
413
409
|
mock_call.async.should_receive(:send_offer).once
|
414
410
|
subject.handle_ami_event ami_event
|
415
411
|
end
|
@@ -422,7 +418,7 @@ module Punchblock
|
|
422
418
|
end
|
423
419
|
|
424
420
|
it "should not create a new call" do
|
425
|
-
Asterisk::Call.should_receive(:
|
421
|
+
Asterisk::Call.should_receive(:new_link).never
|
426
422
|
subject.handle_ami_event ami_event
|
427
423
|
end
|
428
424
|
end
|