punchblock 1.9.4 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.markdown +6 -0
- data/Rakefile +16 -0
- data/benchmarks/ami_event_name_comparison.rb +14 -0
- data/benchmarks/channel.rb +27 -0
- data/lib/punchblock/client.rb +2 -6
- data/lib/punchblock/command/accept.rb +3 -24
- data/lib/punchblock/command/answer.rb +3 -24
- data/lib/punchblock/command/dial.rb +24 -76
- data/lib/punchblock/command/hangup.rb +3 -19
- data/lib/punchblock/command/join.rb +21 -70
- data/lib/punchblock/command/mute.rb +3 -3
- data/lib/punchblock/command/redirect.rb +6 -39
- data/lib/punchblock/command/reject.rb +14 -54
- data/lib/punchblock/command/unjoin.rb +8 -40
- data/lib/punchblock/command/unmute.rb +3 -3
- data/lib/punchblock/command_node.rb +0 -17
- data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
- data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
- data/lib/punchblock/component/component_node.rb +1 -1
- data/lib/punchblock/component/input.rb +89 -268
- data/lib/punchblock/component/output.rb +106 -154
- data/lib/punchblock/component/prompt.rb +51 -0
- data/lib/punchblock/component/record.rb +41 -130
- data/lib/punchblock/component.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +31 -4
- data/lib/punchblock/connection/xmpp.rb +6 -14
- data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
- data/lib/punchblock/event/active_speaker.rb +2 -10
- data/lib/punchblock/event/answered.rb +3 -3
- data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
- data/lib/punchblock/event/complete.rb +26 -48
- data/lib/punchblock/event/dtmf.rb +3 -13
- data/lib/punchblock/event/end.rb +10 -11
- data/lib/punchblock/event/joined.rb +5 -25
- data/lib/punchblock/event/offer.rb +4 -25
- data/lib/punchblock/event/ringing.rb +3 -3
- data/lib/punchblock/event/unjoined.rb +5 -25
- data/lib/punchblock/event.rb +0 -10
- data/lib/punchblock/has_headers.rb +20 -26
- data/lib/punchblock/rayo_node.rb +46 -23
- data/lib/punchblock/ref.rb +39 -18
- data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
- data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
- data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
- data/lib/punchblock/translator/asterisk/call.rb +60 -39
- data/lib/punchblock/translator/asterisk/channel.rb +41 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
- data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
- data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
- data/lib/punchblock/translator/asterisk/component.rb +6 -5
- data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
- data/lib/punchblock/translator/asterisk.rb +24 -28
- data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
- data/lib/punchblock/translator/freeswitch/call.rb +15 -14
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component.rb +2 -5
- data/lib/punchblock/translator/freeswitch.rb +2 -2
- data/lib/punchblock/translator/input_component.rb +33 -13
- data/lib/punchblock/uri_list.rb +21 -0
- data/lib/punchblock/version.rb +1 -1
- data/lib/punchblock.rb +4 -3
- data/punchblock.gemspec +7 -3
- data/spec/punchblock/client/component_registry_spec.rb +1 -1
- data/spec/punchblock/client_spec.rb +10 -26
- data/spec/punchblock/command/accept_spec.rb +41 -7
- data/spec/punchblock/command/answer_spec.rb +51 -7
- data/spec/punchblock/command/dial_spec.rb +56 -14
- data/spec/punchblock/command/hangup_spec.rb +41 -7
- data/spec/punchblock/command/join_spec.rb +53 -11
- data/spec/punchblock/command/mute_spec.rb +19 -4
- data/spec/punchblock/command/redirect_spec.rb +40 -10
- data/spec/punchblock/command/reject_spec.rb +43 -11
- data/spec/punchblock/command/unjoin_spec.rb +40 -9
- data/spec/punchblock/command/unmute_spec.rb +19 -4
- data/spec/punchblock/command_node_spec.rb +0 -4
- data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
- data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
- data/spec/punchblock/component/component_node_spec.rb +3 -5
- data/spec/punchblock/component/input_spec.rb +194 -61
- data/spec/punchblock/component/output_spec.rb +194 -62
- data/spec/punchblock/component/prompt_spec.rb +132 -0
- data/spec/punchblock/component/record_spec.rb +70 -32
- data/spec/punchblock/connection/asterisk_spec.rb +17 -3
- data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
- data/spec/punchblock/connection/xmpp_spec.rb +20 -38
- data/spec/punchblock/event/answered_spec.rb +12 -10
- data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
- data/spec/punchblock/event/complete_spec.rb +15 -19
- data/spec/punchblock/event/dtmf_spec.rb +5 -6
- data/spec/punchblock/event/end_spec.rb +20 -10
- data/spec/punchblock/event/joined_spec.rb +8 -7
- data/spec/punchblock/event/offer_spec.rb +41 -12
- data/spec/punchblock/event/ringing_spec.rb +12 -10
- data/spec/punchblock/event/started_speaking_spec.rb +5 -6
- data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
- data/spec/punchblock/event/unjoined_spec.rb +7 -7
- data/spec/punchblock/ref_spec.rb +86 -9
- data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
- data/spec/punchblock/translator/asterisk_spec.rb +20 -24
- data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
- data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
- data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
- data/spec/punchblock/uri_list_spec.rb +49 -0
- data/spec/punchblock_spec.rb +11 -1
- data/spec/spec_helper.rb +7 -11
- data/spec/support/mock_connection_with_event_handler.rb +1 -1
- metadata +104 -24
- data/lib/punchblock/header.rb +0 -9
- data/lib/punchblock/key_value_pair_node.rb +0 -51
- data/spec/punchblock/header_spec.rb +0 -11
|
@@ -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
|