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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +4 -0
  7. data/README.markdown +6 -0
  8. data/Rakefile +16 -0
  9. data/benchmarks/ami_event_name_comparison.rb +14 -0
  10. data/benchmarks/channel.rb +27 -0
  11. data/lib/punchblock/client.rb +2 -6
  12. data/lib/punchblock/command/accept.rb +3 -24
  13. data/lib/punchblock/command/answer.rb +3 -24
  14. data/lib/punchblock/command/dial.rb +24 -76
  15. data/lib/punchblock/command/hangup.rb +3 -19
  16. data/lib/punchblock/command/join.rb +21 -70
  17. data/lib/punchblock/command/mute.rb +3 -3
  18. data/lib/punchblock/command/redirect.rb +6 -39
  19. data/lib/punchblock/command/reject.rb +14 -54
  20. data/lib/punchblock/command/unjoin.rb +8 -40
  21. data/lib/punchblock/command/unmute.rb +3 -3
  22. data/lib/punchblock/command_node.rb +0 -17
  23. data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
  24. data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
  25. data/lib/punchblock/component/component_node.rb +1 -1
  26. data/lib/punchblock/component/input.rb +89 -268
  27. data/lib/punchblock/component/output.rb +106 -154
  28. data/lib/punchblock/component/prompt.rb +51 -0
  29. data/lib/punchblock/component/record.rb +41 -130
  30. data/lib/punchblock/component.rb +1 -0
  31. data/lib/punchblock/connection/asterisk.rb +31 -4
  32. data/lib/punchblock/connection/xmpp.rb +6 -14
  33. data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
  34. data/lib/punchblock/event/active_speaker.rb +2 -10
  35. data/lib/punchblock/event/answered.rb +3 -3
  36. data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
  37. data/lib/punchblock/event/complete.rb +26 -48
  38. data/lib/punchblock/event/dtmf.rb +3 -13
  39. data/lib/punchblock/event/end.rb +10 -11
  40. data/lib/punchblock/event/joined.rb +5 -25
  41. data/lib/punchblock/event/offer.rb +4 -25
  42. data/lib/punchblock/event/ringing.rb +3 -3
  43. data/lib/punchblock/event/unjoined.rb +5 -25
  44. data/lib/punchblock/event.rb +0 -10
  45. data/lib/punchblock/has_headers.rb +20 -26
  46. data/lib/punchblock/rayo_node.rb +46 -23
  47. data/lib/punchblock/ref.rb +39 -18
  48. data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
  49. data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
  50. data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
  51. data/lib/punchblock/translator/asterisk/call.rb +60 -39
  52. data/lib/punchblock/translator/asterisk/channel.rb +41 -0
  53. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
  54. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
  55. data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
  56. data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
  57. data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
  58. data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
  59. data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
  60. data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
  61. data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
  62. data/lib/punchblock/translator/asterisk/component.rb +6 -5
  63. data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
  64. data/lib/punchblock/translator/asterisk.rb +24 -28
  65. data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
  66. data/lib/punchblock/translator/freeswitch/call.rb +15 -14
  67. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
  68. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
  69. data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
  70. data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
  71. data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
  72. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
  73. data/lib/punchblock/translator/freeswitch/component.rb +2 -5
  74. data/lib/punchblock/translator/freeswitch.rb +2 -2
  75. data/lib/punchblock/translator/input_component.rb +33 -13
  76. data/lib/punchblock/uri_list.rb +21 -0
  77. data/lib/punchblock/version.rb +1 -1
  78. data/lib/punchblock.rb +4 -3
  79. data/punchblock.gemspec +7 -3
  80. data/spec/punchblock/client/component_registry_spec.rb +1 -1
  81. data/spec/punchblock/client_spec.rb +10 -26
  82. data/spec/punchblock/command/accept_spec.rb +41 -7
  83. data/spec/punchblock/command/answer_spec.rb +51 -7
  84. data/spec/punchblock/command/dial_spec.rb +56 -14
  85. data/spec/punchblock/command/hangup_spec.rb +41 -7
  86. data/spec/punchblock/command/join_spec.rb +53 -11
  87. data/spec/punchblock/command/mute_spec.rb +19 -4
  88. data/spec/punchblock/command/redirect_spec.rb +40 -10
  89. data/spec/punchblock/command/reject_spec.rb +43 -11
  90. data/spec/punchblock/command/unjoin_spec.rb +40 -9
  91. data/spec/punchblock/command/unmute_spec.rb +19 -4
  92. data/spec/punchblock/command_node_spec.rb +0 -4
  93. data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
  94. data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
  95. data/spec/punchblock/component/component_node_spec.rb +3 -5
  96. data/spec/punchblock/component/input_spec.rb +194 -61
  97. data/spec/punchblock/component/output_spec.rb +194 -62
  98. data/spec/punchblock/component/prompt_spec.rb +132 -0
  99. data/spec/punchblock/component/record_spec.rb +70 -32
  100. data/spec/punchblock/connection/asterisk_spec.rb +17 -3
  101. data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
  102. data/spec/punchblock/connection/xmpp_spec.rb +20 -38
  103. data/spec/punchblock/event/answered_spec.rb +12 -10
  104. data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
  105. data/spec/punchblock/event/complete_spec.rb +15 -19
  106. data/spec/punchblock/event/dtmf_spec.rb +5 -6
  107. data/spec/punchblock/event/end_spec.rb +20 -10
  108. data/spec/punchblock/event/joined_spec.rb +8 -7
  109. data/spec/punchblock/event/offer_spec.rb +41 -12
  110. data/spec/punchblock/event/ringing_spec.rb +12 -10
  111. data/spec/punchblock/event/started_speaking_spec.rb +5 -6
  112. data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
  113. data/spec/punchblock/event/unjoined_spec.rb +7 -7
  114. data/spec/punchblock/ref_spec.rb +86 -9
  115. data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
  116. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
  117. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
  118. data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
  119. data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
  120. data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
  121. data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
  122. data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
  123. data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
  124. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  125. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
  126. data/spec/punchblock/translator/asterisk_spec.rb +20 -24
  127. data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
  128. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
  129. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
  130. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
  131. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
  132. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
  133. data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
  134. data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
  135. data/spec/punchblock/uri_list_spec.rb +49 -0
  136. data/spec/punchblock_spec.rb +11 -1
  137. data/spec/spec_helper.rb +7 -11
  138. data/spec/support/mock_connection_with_event_handler.rb +1 -1
  139. metadata +104 -24
  140. data/lib/punchblock/header.rb +0 -9
  141. data/lib/punchblock/key_value_pair_node.rb +0 -51
  142. 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) { mock('AMI') }
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
- { :ssml => ssml_doc }
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
- { :ssml => ssml_doc }.merge(command_opts)
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::Success
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 :speech" do
133
- let(:command_opts) { { :interrupt_on => :speech } }
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
- { :ssml => ssml_doc }.merge(command_opts)
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[2].should match options
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(/["\\]/) { |m| "\\#{m}" }, '').and_return code: 200, result: 1
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::Success
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
- describe 'ssml' do
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(:command_opts) { { :ssml => nil } }
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 :speech" do
387
- let(:command_opts) { { :interrupt_on => :speech } }
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
- { :ssml => ssml_doc }.merge(command_opts)
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(:command_opts) { { :ssml => nil } }
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) { 'http://foo.com/bar.mp3' }
437
- let :command_options do
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::Success
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 :command_options do
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::Success
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
- :ssml => RubySpeech::SSML.draw { string audio_filename },
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 :command_options do
546
- {
547
- :ssml => RubySpeech::SSML.draw do
548
- audio :src => audio_filename1
549
- audio :src => audio_filename2
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::Success
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 :command_options do
578
- {
579
- :ssml => RubySpeech::SSML.draw do
580
- string "Foo Bar"
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(:send_success).and_return nil
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::Success
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(:send_success).and_return nil
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::Success
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 :speech" do
810
- let(:command_opts) { { :interrupt_on => :speech } }
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
- { :ssml => ssml_doc }.merge(command_opts)
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) { stub('AMI Client').as_null_object }
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 success complete event when the recording ends" do
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::Success
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 complete event" do
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
- complete_reason = original_command.complete_event(0.1).reason
244
- complete_reason.should be_a Punchblock::Event::Complete::Error
245
- complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
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::Success
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) { stub 'Connection' }
19
- let(:ami_client) { stub('AMI Client').as_null_object }
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.tap do |e|
33
- e.target_call_id = call.id
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.tap do |c|
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) { mock 'RubyAMI::Client' }
10
- let(:connection) { mock 'Connection::Asterisk', handle_event: nil }
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 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 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) { mock 'Asterisk::Component::Asterisk::AMIAction', :id => component_id }
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.tap { |c| c.target_call_id = call_id } }
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.tap do |e|
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.id }
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.tap { |c| c.component_id = component.id } }
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 = stub('Asterisk::Call').as_null_object
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) { stub('Asterisk::Component::Asterisk::AMIAction').as_null_object }
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 = mock 'Punchblock::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 :name => 'Newchannel',
342
- :attributes => { :channel => "SIP/101-3f3f",
343
- :state => "Ring",
344
- :callerid => "101",
345
- :uniqueid => "1094154427.10"}
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 = stub('Asterisk::Call').as_null_object
411
- Asterisk::Call.should_receive(:new).once.and_return mock_call
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(:new).never
421
+ Asterisk::Call.should_receive(:new_link).never
426
422
  subject.handle_ami_event ami_event
427
423
  end
428
424
  end