punchblock 1.8.2 → 1.9.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/lib/punchblock/component/asterisk/agi/command.rb +0 -11
  5. data/lib/punchblock/connection/asterisk.rb +3 -3
  6. data/lib/punchblock/translator/asterisk/agi_command.rb +40 -0
  7. data/lib/punchblock/translator/asterisk/call.rb +56 -47
  8. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +13 -37
  9. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +24 -41
  10. data/lib/punchblock/translator/asterisk/component/input.rb +2 -1
  11. data/lib/punchblock/translator/asterisk/component/output.rb +16 -21
  12. data/lib/punchblock/translator/asterisk/component/record.rb +11 -19
  13. data/lib/punchblock/translator/asterisk/component.rb +12 -9
  14. data/lib/punchblock/translator/asterisk.rb +16 -22
  15. data/lib/punchblock/translator/dtmf_recognizer.rb +4 -4
  16. data/lib/punchblock/translator/freeswitch/component/input.rb +2 -1
  17. data/lib/punchblock/translator/input_component.rb +2 -2
  18. data/lib/punchblock/version.rb +1 -1
  19. data/punchblock.gemspec +1 -1
  20. data/spec/punchblock/connection/asterisk_spec.rb +8 -7
  21. data/spec/punchblock/translator/asterisk/call_spec.rb +262 -229
  22. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +57 -29
  23. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +40 -46
  24. data/spec/punchblock/translator/asterisk/component/input_spec.rb +7 -7
  25. data/spec/punchblock/translator/asterisk/component/output_spec.rb +84 -53
  26. data/spec/punchblock/translator/asterisk/component/record_spec.rb +55 -83
  27. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +5 -1
  28. data/spec/punchblock/translator/asterisk/component_spec.rb +2 -10
  29. data/spec/punchblock/translator/asterisk_spec.rb +73 -100
  30. metadata +5 -10
@@ -11,8 +11,9 @@ module Punchblock
11
11
 
12
12
  let(:media_engine) { nil }
13
13
  let(:channel) { 'SIP/foo' }
14
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
15
- let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator }
14
+ let(:ami_client) { stub('AMI Client').as_null_object }
15
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
16
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator, ami_client, connection }
16
17
 
17
18
  let :original_command do
18
19
  Punchblock::Component::Record.new command_options
@@ -40,7 +41,7 @@ module Punchblock
40
41
  before { mock_call.stub(:answered?).and_return(true) }
41
42
 
42
43
  it "sets command response to a reference to the component" do
43
- mock_call.async.should_receive(:send_ami_action)
44
+ ami_client.should_receive(:send_action)
44
45
  subject.execute
45
46
  original_command.response(0.1).should be_a Ref
46
47
  original_command.component_id.should be == subject.id
@@ -48,17 +49,15 @@ module Punchblock
48
49
 
49
50
  it "starts a recording via AMI, using the component ID as the filename" do
50
51
  filename = "#{Record::RECORDING_BASE_PATH}/#{subject.id}"
51
- mock_call.async.should_receive(:send_ami_action).once.with('Monitor', 'Channel' => channel, 'File' => filename, 'Format' => 'wav', 'Mix' => true)
52
+ ami_client.should_receive(:send_action).once.with('Monitor', 'Channel' => channel, 'File' => filename, 'Format' => 'wav', 'Mix' => true)
52
53
  subject.execute
53
54
  end
54
55
 
55
56
  it "sends a success complete event when the recording ends" do
56
57
  full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
57
- mock_call.async.should_receive(:send_ami_action)
58
+ ami_client.should_receive(:send_action)
58
59
  subject.execute
59
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
60
- e['Channel'] = channel
61
- end
60
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
62
61
  mock_call.process_ami_event monitor_stop_event
63
62
  reason.should be_a Punchblock::Component::Record::Complete::Success
64
63
  recording.uri.should be == full_filename
@@ -66,17 +65,15 @@ module Punchblock
66
65
  end
67
66
 
68
67
  it "can be called multiple times on the same call" do
69
- mock_call.async.should_receive(:send_ami_action).twice
68
+ ami_client.should_receive(:send_action).twice
70
69
  subject.execute
71
70
 
72
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
73
- e['Channel'] = channel
74
- end
71
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
75
72
 
76
73
  mock_call.process_ami_event monitor_stop_event
77
74
 
78
- (Record.new original_command, mock_call).execute
79
- (Punchblock::Component::Record.new command_options).request!
75
+ Record.new(original_command, mock_call).execute
76
+ Punchblock::Component::Record.new(command_options).request!
80
77
  mock_call.process_ami_event monitor_stop_event
81
78
  end
82
79
 
@@ -84,7 +81,7 @@ module Punchblock
84
81
  context "set to nil" do
85
82
  let(:command_options) { { :start_paused => nil } }
86
83
  it "should execute normally" do
87
- mock_call.async.should_receive(:send_ami_action).once
84
+ ami_client.should_receive(:send_action).once
88
85
  subject.execute
89
86
  original_command.response(0.1).should be_a Ref
90
87
  end
@@ -93,7 +90,7 @@ module Punchblock
93
90
  context "set to false" do
94
91
  let(:command_options) { { :start_paused => false } }
95
92
  it "should execute normally" do
96
- mock_call.async.should_receive(:send_ami_action).once
93
+ ami_client.should_receive(:send_action).once
97
94
  subject.execute
98
95
  original_command.response(0.1).should be_a Ref
99
96
  end
@@ -102,7 +99,7 @@ module Punchblock
102
99
  context "set to true" do
103
100
  let(:command_options) { { :start_paused => true } }
104
101
  it "should return an error and not execute any actions" do
105
- mock_call.async.should_receive(:send_agi_action).never
102
+ mock_call.should_receive(:execute_agi_command).never
106
103
  subject.execute
107
104
  error = ProtocolError.new.setup 'option error', 'A start-paused value of true is unsupported.'
108
105
  original_command.response(0.1).should be == error
@@ -114,7 +111,7 @@ module Punchblock
114
111
  context "set to nil" do
115
112
  let(:command_options) { { :initial_timeout => nil } }
116
113
  it "should execute normally" do
117
- mock_call.async.should_receive(:send_ami_action).once
114
+ ami_client.should_receive(:send_action).once
118
115
  subject.execute
119
116
  original_command.response(0.1).should be_a Ref
120
117
  end
@@ -123,7 +120,7 @@ module Punchblock
123
120
  context "set to -1" do
124
121
  let(:command_options) { { :initial_timeout => -1 } }
125
122
  it "should execute normally" do
126
- mock_call.async.should_receive(:send_ami_action).once
123
+ ami_client.should_receive(:send_action).once
127
124
  subject.execute
128
125
  original_command.response(0.1).should be_a Ref
129
126
  end
@@ -132,7 +129,7 @@ module Punchblock
132
129
  context "set to a positive number" do
133
130
  let(:command_options) { { :initial_timeout => 10 } }
134
131
  it "should return an error and not execute any actions" do
135
- mock_call.async.should_receive(:send_agi_action).never
132
+ mock_call.should_receive(:execute_agi_command).never
136
133
  subject.execute
137
134
  error = ProtocolError.new.setup 'option error', 'An initial-timeout value is unsupported.'
138
135
  original_command.response(0.1).should be == error
@@ -144,7 +141,7 @@ module Punchblock
144
141
  context "set to nil" do
145
142
  let(:command_options) { { :final_timeout => nil } }
146
143
  it "should execute normally" do
147
- mock_call.async.should_receive(:send_ami_action).once
144
+ ami_client.should_receive(:send_action).once
148
145
  subject.execute
149
146
  original_command.response(0.1).should be_a Ref
150
147
  end
@@ -153,7 +150,7 @@ module Punchblock
153
150
  context "set to -1" do
154
151
  let(:command_options) { { :final_timeout => -1 } }
155
152
  it "should execute normally" do
156
- mock_call.async.should_receive(:send_ami_action).once
153
+ ami_client.should_receive(:send_action).once
157
154
  subject.execute
158
155
  original_command.response(0.1).should be_a Ref
159
156
  end
@@ -162,7 +159,7 @@ module Punchblock
162
159
  context "set to a positive number" do
163
160
  let(:command_options) { { :final_timeout => 10 } }
164
161
  it "should return an error and not execute any actions" do
165
- mock_call.async.should_receive(:send_agi_action).never
162
+ mock_call.should_receive(:execute_agi_command).never
166
163
  subject.execute
167
164
  error = ProtocolError.new.setup 'option error', 'A final-timeout value is unsupported.'
168
165
  original_command.response(0.1).should be == error
@@ -174,17 +171,15 @@ module Punchblock
174
171
  context "set to nil" do
175
172
  let(:command_options) { { :format => nil } }
176
173
  it "should execute as 'wav'" do
177
- mock_call.async.should_receive(:send_ami_action).once.with('Monitor', hash_including('Format' => 'wav'))
174
+ ami_client.should_receive(:send_action).once.with('Monitor', hash_including('Format' => 'wav'))
178
175
  subject.execute
179
176
  original_command.response(0.1).should be_a Ref
180
177
  end
181
178
 
182
179
  it "provides the correct filename in the recording" do
183
- mock_call.async.should_receive(:send_ami_action)
180
+ ami_client.should_receive(:send_action)
184
181
  subject.execute
185
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
186
- e['Channel'] = channel
187
- end
182
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
188
183
  mock_call.process_ami_event monitor_stop_event
189
184
  recording.uri.should match(/.*\.wav$/)
190
185
  end
@@ -193,17 +188,15 @@ module Punchblock
193
188
  context "set to 'mp3'" do
194
189
  let(:command_options) { { :format => 'mp3' } }
195
190
  it "should execute as 'mp3'" do
196
- mock_call.async.should_receive(:send_ami_action).once.with('Monitor', hash_including('Format' => 'mp3'))
191
+ ami_client.should_receive(:send_action).once.with('Monitor', hash_including('Format' => 'mp3'))
197
192
  subject.execute
198
193
  original_command.response(0.1).should be_a Ref
199
194
  end
200
195
 
201
196
  it "provides the correct filename in the recording" do
202
- mock_call.async.should_receive(:send_ami_action)
197
+ ami_client.should_receive(:send_action)
203
198
  subject.execute
204
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
205
- e['Channel'] = channel
206
- end
199
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
207
200
  mock_call.process_ami_event monitor_stop_event
208
201
  recording.uri.should match(/.*\.mp3$/)
209
202
  end
@@ -214,8 +207,8 @@ module Punchblock
214
207
  context "set to nil" do
215
208
  let(:command_options) { { :start_beep => nil } }
216
209
  it "should execute normally" do
217
- mock_call.async.should_receive(:send_agi_action).never.with('STREAM FILE', 'beep', '""')
218
- mock_call.async.should_receive(:send_ami_action).once
210
+ mock_call.should_receive(:execute_agi_command).never.with('STREAM FILE', 'beep', '""')
211
+ ami_client.should_receive(:send_action).once
219
212
  subject.execute
220
213
  original_command.response(0.1).should be_a Ref
221
214
  end
@@ -224,8 +217,8 @@ module Punchblock
224
217
  context "set to false" do
225
218
  let(:command_options) { { :start_beep => false } }
226
219
  it "should execute normally" do
227
- mock_call.async.should_receive(:send_agi_action).never.with('STREAM FILE', 'beep', '""')
228
- mock_call.async.should_receive(:send_ami_action).once
220
+ mock_call.should_receive(:execute_agi_command).never.with('STREAM FILE', 'beep', '""')
221
+ ami_client.should_receive(:send_action).once
229
222
  subject.execute
230
223
  original_command.response(0.1).should be_a Ref
231
224
  end
@@ -235,21 +228,22 @@ module Punchblock
235
228
  let(:command_options) { { :start_beep => true } }
236
229
 
237
230
  it "should play a beep before recording" do
238
- subject.wrapped_object.should_receive(:wait).once
239
- mock_call.async.should_receive(:send_agi_action).once.with('STREAM FILE', 'beep', '""').ordered
240
- mock_call.async.should_receive(:send_ami_action).once.ordered
231
+ mock_call.should_receive(:execute_agi_command).once.with('STREAM FILE', 'beep', '""').ordered.and_return code: 200
232
+ ami_client.should_receive(:send_action).once.ordered
241
233
  subject.execute
242
234
  original_command.response(0.1).should be_a Ref
243
235
  end
244
236
 
245
- it "should wait for the beep to finish before starting recording" do
246
- async_proxy = mock_call.async
247
- def async_proxy.send_agi_action(*args)
248
- yield
237
+ context "when we get a RubyAMI Error" do
238
+ it "should send an error complete event" do
239
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
240
+ mock_call.should_receive(:execute_agi_command).and_raise error
241
+ ami_client.should_receive(:send_action).never
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'"
249
246
  end
250
- mock_call.async.should_receive(:send_ami_action).once
251
- subject.execute
252
- original_command.response(0.1).should be_a Ref
253
247
  end
254
248
  end
255
249
  end
@@ -258,7 +252,7 @@ module Punchblock
258
252
  context "set to nil" do
259
253
  let(:command_options) { { :max_duration => nil } }
260
254
  it "should execute normally" do
261
- mock_call.async.should_receive(:send_ami_action).once
255
+ ami_client.should_receive(:send_action).once
262
256
  subject.execute
263
257
  original_command.response(0.1).should be_a Ref
264
258
  end
@@ -267,7 +261,7 @@ module Punchblock
267
261
  context "set to -1" do
268
262
  let(:command_options) { { :max_duration => -1 } }
269
263
  it "should execute normally" do
270
- mock_call.async.should_receive(:send_ami_action).once
264
+ ami_client.should_receive(:send_action).once
271
265
  subject.execute
272
266
  original_command.response(0.1).should be_a Ref
273
267
  end
@@ -289,24 +283,18 @@ module Punchblock
289
283
  let(:command_options) { { :max_duration => 1000 } }
290
284
 
291
285
  it "executes a StopMonitor action" do
292
- mock_call.async.should_receive :send_ami_action
293
- mock_call.async.should_receive(:send_ami_action).once.with('StopMonitor', 'Channel' => channel)
286
+ ami_client.should_receive :send_action
287
+ ami_client.should_receive(:send_action).once.with('StopMonitor', 'Channel' => channel)
294
288
  subject.execute
295
289
  sleep 1.2
296
290
  end
297
291
 
298
292
  it "sends the correct complete event" do
299
- async_proxy = mock_call.async
300
- def async_proxy.send_ami_action(*args, &block)
301
- block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
302
- end
303
293
  full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
304
294
  subject.execute
305
295
  sleep 1.2
306
296
 
307
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
308
- e['Channel'] = channel
309
- end
297
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
310
298
  mock_call.process_ami_event monitor_stop_event
311
299
 
312
300
  reason.should be_a Punchblock::Component::Record::Complete::Success
@@ -335,7 +323,7 @@ module Punchblock
335
323
  let(:command) { Punchblock::Component::Stop.new }
336
324
 
337
325
  before do
338
- mock_call.async.should_receive :send_ami_action
326
+ ami_client.should_receive :send_action
339
327
  mock_call.should_receive(:answered?).and_return(true)
340
328
  command.request!
341
329
  original_command.request!
@@ -343,34 +331,24 @@ module Punchblock
343
331
  end
344
332
 
345
333
  let :send_stop_event do
346
- monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
347
- e['Channel'] = channel
348
- end
334
+ monitor_stop_event = RubyAMI::Event.new 'MonitorStop', 'Channel' => channel
349
335
  mock_call.process_ami_event monitor_stop_event
350
336
  end
351
337
 
352
338
  it "sets the command response to true" do
353
- mock_call.async.should_receive :send_ami_action
339
+ ami_client.should_receive :send_action
354
340
  subject.execute_command command
355
341
  send_stop_event
356
342
  command.response(0.1).should be == true
357
343
  end
358
344
 
359
345
  it "executes a StopMonitor action" do
360
- mock_call.async.should_receive(:send_ami_action).once.with('StopMonitor', 'Channel' => channel)
346
+ ami_client.should_receive(:send_action).once.with('StopMonitor', 'Channel' => channel)
361
347
  subject.execute_command command
362
348
  end
363
349
 
364
350
  it "sends the correct complete event" do
365
- mock_call.async.instance_exec do
366
- class << self
367
- undef :send_ami_action # This is here because mocha has already defined #send_ami_action above. We need to undef it to prevent a warning on redefinition.
368
- end
369
-
370
- def send_ami_action(*args, &block)
371
- block.call ::Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
372
- end
373
- end
351
+ ami_client.should_receive(:send_action).and_return RubyAMI::Response.new
374
352
 
375
353
  full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
376
354
  subject.execute_command command
@@ -391,16 +369,13 @@ module Punchblock
391
369
  end
392
370
 
393
371
  it "sets the command response to true" do
394
- async_proxy = mock_call.async
395
- def async_proxy.send_ami_action(*args, &block)
396
- block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
397
- end
372
+ ami_client.should_receive(:send_action).and_return RubyAMI::Response.new
398
373
  subject.execute_command command
399
374
  command.response(0.1).should be == true
400
375
  end
401
376
 
402
377
  it "pauses the recording via AMI" do
403
- mock_call.async.should_receive(:send_ami_action).once.with('PauseMonitor', 'Channel' => channel)
378
+ ami_client.should_receive(:send_action).once.with('PauseMonitor', 'Channel' => channel)
404
379
  subject.execute_command command
405
380
  end
406
381
  end
@@ -415,16 +390,13 @@ module Punchblock
415
390
  end
416
391
 
417
392
  it "sets the command response to true" do
418
- async_proxy = mock_call.async
419
- def async_proxy.send_ami_action(*args, &block)
420
- block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
421
- end
393
+ ami_client.should_receive(:send_action).and_return RubyAMI::Response.new
422
394
  subject.execute_command command
423
395
  command.response(0.1).should be == true
424
396
  end
425
397
 
426
398
  it "resumes the recording via AMI" do
427
- mock_call.async.should_receive(:send_ami_action).once.with('ResumeMonitor', 'Channel' => channel)
399
+ ami_client.should_receive(:send_action).once.with('ResumeMonitor', 'Channel' => channel)
428
400
  subject.execute_command command
429
401
  end
430
402
  end
@@ -15,7 +15,11 @@ module Punchblock
15
15
  end
16
16
  end
17
17
 
18
- let(:mock_call) { Call.new 'SIP/foo', mock('Translator') }
18
+ let(:connection) { stub 'Connection' }
19
+ let(:ami_client) { stub('AMI Client').as_null_object }
20
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
21
+ let(:mock_call) { Call.new 'SIP/foo', translator, ami_client, connection }
22
+
19
23
  subject { MockComponent.new Hash.new, mock_call }
20
24
 
21
25
  describe "#execute_command" do
@@ -13,7 +13,8 @@ module Punchblock
13
13
  describe Component do
14
14
  let(:connection) { Punchblock::Connection::Asterisk.new }
15
15
  let(:translator) { connection.translator }
16
- let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
16
+ let(:ami_client) { connection.ami_client }
17
+ let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
17
18
  let(:command) { Punchblock::Component::Input.new }
18
19
 
19
20
  subject { Component.new command, call }
@@ -38,15 +39,6 @@ module Punchblock
38
39
  connection.should_receive(:handle_event).once.with expected_event
39
40
  subject.send_event event
40
41
  end
41
-
42
- context "when marked internal" do
43
- before { subject.internal = true }
44
-
45
- it "should add the event to the command" do
46
- command.should_receive(:add_event).once.with expected_event
47
- subject.send_event event
48
- end
49
- end
50
42
  end
51
43
 
52
44
  describe "#send_complete_event" do