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.
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