punchblock 1.1.0 → 1.2.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.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # [develop](https://github.com/adhearsion/punchblock)
2
2
 
3
+ # [v1.2.0](https://github.com/adhearsion/punchblock/compare/v1.1.0...v1.2.0) - [2012-04-29](https://rubygems.org/gems/punchblock/versions/1.2.0)
4
+ * Feature: Basic support for record component on Asterisk, using MixMonitor. Currently unsupported options include: start_paused, initial_timeout, final_timeout. Hints are additionally not supported, and recordings are stored on the * machine's local filesystem.
5
+
3
6
  # [v1.1.0](https://github.com/adhearsion/punchblock/compare/v1.0.0...v1.1.0) - [2012-04-26](https://rubygems.org/gems/punchblock/versions/1.1.0)
4
7
  * Feature: Implement Reject on Asterisk
5
8
  * Bugfix: No longer generate warnings
@@ -203,6 +203,10 @@ module Punchblock
203
203
  read_attr :uri
204
204
  end
205
205
 
206
+ def uri=(other)
207
+ write_attr :uri, other
208
+ end
209
+
206
210
  def duration
207
211
  read_attr :duration, :to_i
208
212
  end
@@ -110,6 +110,9 @@ module Punchblock
110
110
  case ami_event.name
111
111
  when 'Hangup'
112
112
  pb_logger.trace "Received a Hangup AMI event. Sending End event."
113
+ @components.dup.each_pair do |id, component|
114
+ component.call_ended if component.alive?
115
+ end
113
116
  send_end_event HANGUP_CAUSE_TO_END_REASON[ami_event['Cause'].to_i]
114
117
  when 'AsyncAGI'
115
118
  pb_logger.trace "Received an AsyncAGI event. Looking for matching AGICommand component."
@@ -213,6 +216,8 @@ module Punchblock
213
216
  execute_component Component::Output, command
214
217
  when Punchblock::Component::Input
215
218
  execute_component Component::Input, command
219
+ when Punchblock::Component::Record
220
+ execute_component Component::Record, command
216
221
  else
217
222
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for call #{id}", id
218
223
  end
@@ -9,6 +9,7 @@ module Punchblock
9
9
  autoload :Asterisk
10
10
  autoload :Input
11
11
  autoload :Output
12
+ autoload :Record
12
13
  autoload :StopByRedirect
13
14
 
14
15
  class Component
@@ -22,6 +23,7 @@ module Punchblock
22
23
  def initialize(component_node, call = nil)
23
24
  @component_node, @call = component_node, call
24
25
  @id = UUIDTools::UUID.random_create.to_s
26
+ @complete = false
25
27
  setup
26
28
  pb_logger.debug "Starting up..."
27
29
  end
@@ -33,9 +35,12 @@ module Punchblock
33
35
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command for component #{id}", call_id, id
34
36
  end
35
37
 
36
- def send_complete_event(reason)
38
+ def send_complete_event(reason, recording = nil)
39
+ return if @complete
40
+ @complete = true
37
41
  event = Punchblock::Event::Complete.new.tap do |c|
38
42
  c.reason = reason
43
+ c << recording if recording
39
44
  end
40
45
  send_event event
41
46
  current_actor.terminate!
@@ -48,7 +53,7 @@ module Punchblock
48
53
  if internal
49
54
  @component_node.add_event event
50
55
  else
51
- translator.connection.handle_event event
56
+ translator.handle_pb_event! event
52
57
  end
53
58
  end
54
59
 
@@ -60,6 +65,10 @@ module Punchblock
60
65
  call.id if call
61
66
  end
62
67
 
68
+ def call_ended
69
+ send_complete_event Punchblock::Event::Complete::Hangup.new
70
+ end
71
+
63
72
  private
64
73
 
65
74
  def translator
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ module Translator
5
+ class Asterisk
6
+ module Component
7
+ class Record < Component
8
+ RECORDING_BASE_PATH = '/var/punchblock/record'
9
+
10
+ def setup
11
+ @complete_reason = nil
12
+ end
13
+
14
+ def execute
15
+ max_duration = @component_node.max_duration || -1
16
+
17
+ raise OptionError, 'A start-paused value of true is unsupported.' if @component_node.start_paused
18
+ raise OptionError, 'An initial-timeout value is unsupported.' if @component_node.initial_timeout && @component_node.initial_timeout != -1
19
+ raise OptionError, 'A final-timeout value is unsupported.' if @component_node.final_timeout && @component_node.final_timeout != -1
20
+ raise OptionError, 'A max-duration value that is negative (and not -1) is invalid.' unless max_duration >= -1
21
+
22
+ @format = @component_node.format || 'wav'
23
+
24
+ @call.answer_if_not_answered
25
+
26
+ component = current_actor
27
+ call.register_handler :ami, :name => 'MonitorStop' do |event|
28
+ component.finished
29
+ end
30
+
31
+ send_ref
32
+
33
+ if @component_node.start_beep
34
+ pb_logger.debug "Playing a beep via STREAM FILE before recording"
35
+ @call.send_agi_action! 'STREAM FILE', 'beep', '""' do
36
+ component.signal! :beep_finished
37
+ end
38
+ wait :beep_finished
39
+ end
40
+
41
+ call.send_ami_action! 'Monitor', 'Channel' => call.channel, 'File' => filename, 'Format' => @format, 'Mix' => true
42
+ unless max_duration == -1
43
+ after max_duration/1000 do
44
+ pb_logger.trace "Max duration encountered, stopping recording"
45
+ call.send_ami_action! 'StopMonitor', 'Channel' => call.channel
46
+ end
47
+ end
48
+ rescue OptionError => e
49
+ with_error 'option error', e.message
50
+ end
51
+
52
+ def execute_command(command)
53
+ case command
54
+ when Punchblock::Component::Stop
55
+ command.response = true
56
+ a = current_actor
57
+ call.send_ami_action! 'StopMonitor', 'Channel' => call.channel do |complete_event|
58
+ @complete_reason = stop_reason
59
+ end
60
+ when Punchblock::Component::Record::Pause
61
+ a = current_actor
62
+ call.send_ami_action! 'PauseMonitor', 'Channel' => call.channel do |complete_event|
63
+ command.response = true
64
+ end
65
+ when Punchblock::Component::Record::Resume
66
+ a = current_actor
67
+ call.send_ami_action! 'ResumeMonitor', 'Channel' => call.channel do |complete_event|
68
+ command.response = true
69
+ end
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ def finished
76
+ send_complete_event(@complete_reason || success_reason)
77
+ end
78
+
79
+ private
80
+
81
+ def filename
82
+ File.join RECORDING_BASE_PATH, id
83
+ end
84
+
85
+ def recording
86
+ Punchblock::Component::Record::Recording.new :uri => "file://#{filename}.#{@format}"
87
+ end
88
+
89
+ def stop_reason
90
+ Punchblock::Event::Complete::Stop.new
91
+ end
92
+
93
+ def success_reason
94
+ Punchblock::Component::Record::Complete::Success.new
95
+ end
96
+
97
+ def send_complete_event(reason)
98
+ super reason, recording
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Punchblock
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -235,6 +235,21 @@ module Punchblock
235
235
  subject.should_not be_alive
236
236
  end
237
237
 
238
+ it "should cause all components to send complete events before sending end event" do
239
+ subject.expects :answer_if_not_answered
240
+ comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf
241
+ comp_command.request!
242
+ component = subject.execute_command comp_command
243
+ comp_command.response(0.1).should be_a Ref
244
+ expected_complete_event = Punchblock::Event::Complete.new :target_call_id => subject.id, :component_id => component.id
245
+ expected_complete_event.reason = Punchblock::Event::Complete::Hangup.new
246
+ expected_end_event = Punchblock::Event::End.new :reason => :hangup, :target_call_id => subject.id
247
+ end_sequence = sequence 'end events'
248
+ translator.expects(:handle_pb_event!).with(expected_complete_event).once.in_sequence(end_sequence)
249
+ translator.expects(:handle_pb_event!).with(expected_end_event).once.in_sequence(end_sequence)
250
+ subject.process_ami_event ami_event
251
+ end
252
+
238
253
  context "with an undefined cause" do
239
254
  let(:cause) { '0' }
240
255
  let(:cause_txt) { 'Undefined' }
@@ -736,7 +751,7 @@ module Punchblock
736
751
 
737
752
  let(:mock_action) { mock 'Component::Asterisk::Output', :id => 'foo' }
738
753
 
739
- it 'should create an AGI command component actor and execute it asynchronously' do
754
+ it 'should create an Output component and execute it asynchronously' do
740
755
  Component::Output.expects(:new).once.with(command, subject).returns mock_action
741
756
  mock_action.expects(:internal=).never
742
757
  mock_action.expects(:execute!).once
@@ -751,7 +766,7 @@ module Punchblock
751
766
 
752
767
  let(:mock_action) { mock 'Component::Asterisk::Input', :id => 'foo' }
753
768
 
754
- it 'should create an AGI command component actor and execute it asynchronously' do
769
+ it 'should create an Input component and execute it asynchronously' do
755
770
  Component::Input.expects(:new).once.with(command, subject).returns mock_action
756
771
  mock_action.expects(:internal=).never
757
772
  mock_action.expects(:execute!).once
@@ -759,6 +774,21 @@ module Punchblock
759
774
  end
760
775
  end
761
776
 
777
+ context 'with a Record component' do
778
+ let :command do
779
+ Punchblock::Component::Record.new
780
+ end
781
+
782
+ let(:mock_action) { mock 'Component::Asterisk::Record', :id => 'foo' }
783
+
784
+ it 'should create a Record component and execute it asynchronously' do
785
+ Component::Record.expects(:new).once.with(command, subject).returns mock_action
786
+ mock_action.expects(:internal=).never
787
+ mock_action.expects(:execute!).once
788
+ subject.execute_command command
789
+ end
790
+ end
791
+
762
792
  context 'with a component command' do
763
793
  let(:component_id) { 'foobar' }
764
794
 
@@ -789,7 +819,7 @@ module Punchblock
789
819
 
790
820
  context 'with a command we do not understand' do
791
821
  let :command do
792
- Punchblock::Component::Record.new
822
+ Punchblock::Command::Mute.new
793
823
  end
794
824
 
795
825
  it 'sends an error in response to the command' do
@@ -116,10 +116,10 @@ module Punchblock
116
116
  it 'should send a complete event' do
117
117
  subject.handle_ami_event ami_event
118
118
 
119
- command.should be_complete
120
-
121
119
  complete_event = command.complete_event 0.5
122
120
 
121
+ command.should be_complete
122
+
123
123
  complete_event.component_id.should be == component_id.to_s
124
124
  complete_event.reason.should be == expected_complete_reason
125
125
  end
@@ -0,0 +1,421 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ describe Record do
10
+ let(:connection) do
11
+ mock_connection_with_event_handler do |event|
12
+ original_command.add_event event
13
+ end
14
+ end
15
+ let(:media_engine) { nil }
16
+ let(:channel) { 'SIP/foo' }
17
+ let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
18
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator }
19
+
20
+ let :original_command do
21
+ Punchblock::Component::Record.new command_options
22
+ end
23
+
24
+ let :command_options do
25
+ {}
26
+ end
27
+
28
+ subject { Record.new original_command, mock_call }
29
+
30
+ describe '#execute' do
31
+ let(:reason) { original_command.complete_event(5).reason }
32
+ let(:recording) { original_command.complete_event(5).recording }
33
+
34
+ before { original_command.request! }
35
+
36
+ it "calls answer_if_not_answered on the call" do
37
+ mock_call.expects :send_ami_action!
38
+ mock_call.expects :answer_if_not_answered
39
+ subject.execute
40
+ end
41
+
42
+ before { mock_call.stubs :answer_if_not_answered }
43
+
44
+ it "sets command response to a reference to the component" do
45
+ mock_call.expects(:send_ami_action!)
46
+ subject.execute
47
+ original_command.response(0.1).should be_a Ref
48
+ original_command.component_id.should be == subject.id
49
+ end
50
+
51
+ it "starts a recording via AMI, using the component ID as the filename" do
52
+ filename = "#{Record::RECORDING_BASE_PATH}/#{subject.id}"
53
+ mock_call.expects(:send_ami_action!).once.with('Monitor', 'Channel' => channel, 'File' => filename, 'Format' => 'wav', 'Mix' => true)
54
+ subject.execute
55
+ end
56
+
57
+ it "sends a success complete event when the recording ends" do
58
+ full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
59
+ mock_call.expects(:send_ami_action!)
60
+ subject.execute
61
+ monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
62
+ e['Channel'] = channel
63
+ end
64
+ mock_call.process_ami_event monitor_stop_event
65
+ reason.should be_a Punchblock::Component::Record::Complete::Success
66
+ recording.uri.should be == full_filename
67
+ original_command.should be_complete
68
+ end
69
+
70
+ describe 'start_paused' do
71
+ context "set to nil" do
72
+ let(:command_options) { { :start_paused => nil } }
73
+ it "should execute normally" do
74
+ mock_call.expects(:send_ami_action!).once
75
+ subject.execute
76
+ original_command.response(0.1).should be_a Ref
77
+ end
78
+ end
79
+
80
+ context "set to false" do
81
+ let(:command_options) { { :start_paused => false } }
82
+ it "should execute normally" do
83
+ mock_call.expects(:send_ami_action!).once
84
+ subject.execute
85
+ original_command.response(0.1).should be_a Ref
86
+ end
87
+ end
88
+
89
+ context "set to true" do
90
+ let(:command_options) { { :start_paused => true } }
91
+ it "should return an error and not execute any actions" do
92
+ mock_call.expects(:send_agi_action!).never
93
+ subject.execute
94
+ error = ProtocolError.new.setup 'option error', 'A start-paused value of true is unsupported.'
95
+ original_command.response(0.1).should be == error
96
+ end
97
+ end
98
+ end
99
+
100
+ describe 'initial_timeout' do
101
+ context "set to nil" do
102
+ let(:command_options) { { :initial_timeout => nil } }
103
+ it "should execute normally" do
104
+ mock_call.expects(:send_ami_action!).once
105
+ subject.execute
106
+ original_command.response(0.1).should be_a Ref
107
+ end
108
+ end
109
+
110
+ context "set to -1" do
111
+ let(:command_options) { { :initial_timeout => -1 } }
112
+ it "should execute normally" do
113
+ mock_call.expects(:send_ami_action!).once
114
+ subject.execute
115
+ original_command.response(0.1).should be_a Ref
116
+ end
117
+ end
118
+
119
+ context "set to a positive number" do
120
+ let(:command_options) { { :initial_timeout => 10 } }
121
+ it "should return an error and not execute any actions" do
122
+ mock_call.expects(:send_agi_action!).never
123
+ subject.execute
124
+ error = ProtocolError.new.setup 'option error', 'An initial-timeout value is unsupported.'
125
+ original_command.response(0.1).should be == error
126
+ end
127
+ end
128
+ end
129
+
130
+ describe 'final_timeout' do
131
+ context "set to nil" do
132
+ let(:command_options) { { :final_timeout => nil } }
133
+ it "should execute normally" do
134
+ mock_call.expects(:send_ami_action!).once
135
+ subject.execute
136
+ original_command.response(0.1).should be_a Ref
137
+ end
138
+ end
139
+
140
+ context "set to -1" do
141
+ let(:command_options) { { :final_timeout => -1 } }
142
+ it "should execute normally" do
143
+ mock_call.expects(:send_ami_action!).once
144
+ subject.execute
145
+ original_command.response(0.1).should be_a Ref
146
+ end
147
+ end
148
+
149
+ context "set to a positive number" do
150
+ let(:command_options) { { :final_timeout => 10 } }
151
+ it "should return an error and not execute any actions" do
152
+ mock_call.expects(:send_agi_action!).never
153
+ subject.execute
154
+ error = ProtocolError.new.setup 'option error', 'A final-timeout value is unsupported.'
155
+ original_command.response(0.1).should be == error
156
+ end
157
+ end
158
+ end
159
+
160
+ describe 'format' do
161
+ context "set to nil" do
162
+ let(:command_options) { { :format => nil } }
163
+ it "should execute as 'wav'" do
164
+ mock_call.expects(:send_ami_action!).once.with('Monitor', has_entry('Format' => 'wav'))
165
+ subject.execute
166
+ original_command.response(0.1).should be_a Ref
167
+ end
168
+
169
+ it "provides the correct filename in the recording" do
170
+ mock_call.expects(:send_ami_action!)
171
+ subject.execute
172
+ monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
173
+ e['Channel'] = channel
174
+ end
175
+ mock_call.process_ami_event monitor_stop_event
176
+ recording.uri.should match(/.*\.wav$/)
177
+ end
178
+ end
179
+
180
+ context "set to 'mp3'" do
181
+ let(:command_options) { { :format => 'mp3' } }
182
+ it "should execute as 'mp3'" do
183
+ mock_call.expects(:send_ami_action!).once.with('Monitor', has_entry('Format' => 'mp3'))
184
+ subject.execute
185
+ original_command.response(0.1).should be_a Ref
186
+ end
187
+
188
+ it "provides the correct filename in the recording" do
189
+ mock_call.expects(:send_ami_action!)
190
+ subject.execute
191
+ monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
192
+ e['Channel'] = channel
193
+ end
194
+ mock_call.process_ami_event monitor_stop_event
195
+ recording.uri.should match(/.*\.mp3$/)
196
+ end
197
+ end
198
+ end
199
+
200
+ describe 'start_beep' do
201
+ context "set to nil" do
202
+ let(:command_options) { { :start_beep => nil } }
203
+ it "should execute normally" do
204
+ mock_call.expects(:send_agi_action!).never.with('STREAM FILE', 'beep', '""')
205
+ mock_call.expects(:send_ami_action!).once
206
+ subject.execute
207
+ original_command.response(0.1).should be_a Ref
208
+ end
209
+ end
210
+
211
+ context "set to false" do
212
+ let(:command_options) { { :start_beep => false } }
213
+ it "should execute normally" do
214
+ mock_call.expects(:send_agi_action!).never.with('STREAM FILE', 'beep', '""')
215
+ mock_call.expects(:send_ami_action!).once
216
+ subject.execute
217
+ original_command.response(0.1).should be_a Ref
218
+ end
219
+ end
220
+
221
+ context "set to true" do
222
+ let(:command_options) { { :start_beep => true } }
223
+
224
+ it "should play a beep before recording" do
225
+ execute_seq = sequence 'beep then record'
226
+ subject.wrapped_object.expects(:wait).once
227
+ mock_call.expects(:send_agi_action!).once.with('STREAM FILE', 'beep', '""').in_sequence(execute_seq)
228
+ mock_call.expects(:send_ami_action!).once.in_sequence(execute_seq)
229
+ subject.execute
230
+ original_command.response(0.1).should be_a Ref
231
+ end
232
+
233
+ it "should wait for the beep to finish before starting recording" do
234
+ def mock_call.send_agi_action!(*args)
235
+ yield
236
+ end
237
+ mock_call.expects(:send_ami_action!).once
238
+ subject.execute
239
+ original_command.response(0.1).should be_a Ref
240
+ end
241
+ end
242
+ end
243
+
244
+ describe 'max_duration' do
245
+ context "set to nil" do
246
+ let(:command_options) { { :max_duration => nil } }
247
+ it "should execute normally" do
248
+ mock_call.expects(:send_ami_action!).once
249
+ subject.execute
250
+ original_command.response(0.1).should be_a Ref
251
+ end
252
+ end
253
+
254
+ context "set to -1" do
255
+ let(:command_options) { { :max_duration => -1 } }
256
+ it "should execute normally" do
257
+ mock_call.expects(:send_ami_action!).once
258
+ subject.execute
259
+ original_command.response(0.1).should be_a Ref
260
+ end
261
+ end
262
+
263
+ context 'a negative number other than -1' do
264
+ let(:command_options) { { :max_duration => -1000 } }
265
+
266
+ it "should return an error and not execute any actions" do
267
+ subject.execute
268
+ error = ProtocolError.new.setup 'option error', 'A max-duration value that is negative (and not -1) is invalid.'
269
+ original_command.response(0.1).should be == error
270
+ end
271
+ end
272
+
273
+ context 'a positive number' do
274
+ let(:reason) { original_command.complete_event(5).reason }
275
+ let(:recording) { original_command.complete_event(5).recording }
276
+ let(:command_options) { { :max_duration => 1000 } }
277
+
278
+ it "executes a StopMonitor action" do
279
+ mock_call.expects :send_ami_action!
280
+ mock_call.expects(:send_ami_action!).once.with('StopMonitor', 'Channel' => channel)
281
+ subject.execute
282
+ sleep 1.2
283
+ end
284
+
285
+ it "sends the correct complete event" do
286
+ def mock_call.send_ami_action!(*args, &block)
287
+ block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
288
+ end
289
+ full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
290
+ subject.execute
291
+ sleep 1.2
292
+
293
+ monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
294
+ e['Channel'] = channel
295
+ end
296
+ mock_call.process_ami_event monitor_stop_event
297
+
298
+ reason.should be_a Punchblock::Component::Record::Complete::Success
299
+ recording.uri.should be == full_filename
300
+ original_command.should be_complete
301
+ end
302
+ end
303
+ end
304
+ end
305
+
306
+ describe "#execute_command" do
307
+ let(:reason) { original_command.complete_event(5).reason }
308
+ let(:recording) { original_command.complete_event(5).recording }
309
+
310
+ context "with a command it does not understand" do
311
+ let(:command) { Punchblock::Component::Output::Pause.new }
312
+
313
+ before { command.request! }
314
+ it "returns a ProtocolError response" do
315
+ subject.execute_command command
316
+ command.response(0.1).should be_a ProtocolError
317
+ end
318
+ end
319
+
320
+ context "with a Stop command" do
321
+ let(:command) { Punchblock::Component::Stop.new }
322
+
323
+ before do
324
+ mock_call.expects :answer_if_not_answered
325
+ mock_call.expects :send_ami_action!
326
+ command.request!
327
+ original_command.request!
328
+ subject.execute
329
+ end
330
+
331
+ let :send_stop_event do
332
+ monitor_stop_event = RubyAMI::Event.new('MonitorStop').tap do |e|
333
+ e['Channel'] = channel
334
+ end
335
+ mock_call.process_ami_event monitor_stop_event
336
+ end
337
+
338
+ it "sets the command response to true" do
339
+ mock_call.expects :send_ami_action!
340
+ subject.execute_command command
341
+ send_stop_event
342
+ command.response(0.1).should be == true
343
+ end
344
+
345
+ it "executes a StopMonitor action" do
346
+ mock_call.expects(:send_ami_action!).once.with('StopMonitor', 'Channel' => channel)
347
+ subject.execute_command command
348
+ end
349
+
350
+ it "sends the correct complete event" do
351
+ mock_call.instance_exec do
352
+ class << self
353
+ 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.
354
+ end
355
+
356
+ def send_ami_action!(*args, &block)
357
+ block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
358
+ end
359
+ end
360
+
361
+ full_filename = "file://#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
362
+ subject.execute_command command
363
+ send_stop_event
364
+ reason.should be_a Punchblock::Event::Complete::Stop
365
+ recording.uri.should be == full_filename
366
+ original_command.should be_complete
367
+ end
368
+ end
369
+
370
+ context "with a Pause command" do
371
+ let(:command) { Punchblock::Component::Record::Pause.new }
372
+
373
+ before do
374
+ command.request!
375
+ original_command.request!
376
+ original_command.execute!
377
+ end
378
+
379
+ it "sets the command response to true" do
380
+ def mock_call.send_ami_action!(*args, &block)
381
+ block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
382
+ end
383
+ subject.execute_command command
384
+ command.response(0.1).should be == true
385
+ end
386
+
387
+ it "pauses the recording via AMI" do
388
+ mock_call.expects(:send_ami_action!).once.with('PauseMonitor', 'Channel' => channel)
389
+ subject.execute_command command
390
+ end
391
+ end
392
+
393
+ context "with a Resume command" do
394
+ let(:command) { Punchblock::Component::Record::Resume.new }
395
+
396
+ before do
397
+ command.request!
398
+ original_command.request!
399
+ original_command.execute!
400
+ end
401
+
402
+ it "sets the command response to true" do
403
+ def mock_call.send_ami_action!(*args, &block)
404
+ block.call Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new if block
405
+ end
406
+ subject.execute_command command
407
+ command.response(0.1).should be == true
408
+ end
409
+
410
+ it "resumes the recording via AMI" do
411
+ mock_call.expects(:send_ami_action!).once.with('ResumeMonitor', 'Channel' => channel)
412
+ subject.execute_command command
413
+ end
414
+ end
415
+ end
416
+
417
+ end
418
+ end
419
+ end
420
+ end
421
+ end
@@ -71,6 +71,13 @@ module Punchblock
71
71
  end
72
72
  end
73
73
 
74
+ describe "#call_ended" do
75
+ it "should send a complete event with the call hangup reason" do
76
+ subject.wrapped_object.expects(:send_complete_event).once.with Punchblock::Event::Complete::Hangup.new
77
+ subject.call_ended
78
+ end
79
+ end
80
+
74
81
  describe '#execute_command' do
75
82
  before do
76
83
  component_command.request!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: punchblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-04-26 00:00:00.000000000 Z
14
+ date: 2012-04-29 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: niceogiri
@@ -401,6 +401,7 @@ files:
401
401
  - lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb
402
402
  - lib/punchblock/translator/asterisk/component/input.rb
403
403
  - lib/punchblock/translator/asterisk/component/output.rb
404
+ - lib/punchblock/translator/asterisk/component/record.rb
404
405
  - lib/punchblock/translator/asterisk/component/stop_by_redirect.rb
405
406
  - lib/punchblock/version.rb
406
407
  - punchblock.gemspec
@@ -445,6 +446,7 @@ files:
445
446
  - spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb
446
447
  - spec/punchblock/translator/asterisk/component/input_spec.rb
447
448
  - spec/punchblock/translator/asterisk/component/output_spec.rb
449
+ - spec/punchblock/translator/asterisk/component/record_spec.rb
448
450
  - spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb
449
451
  - spec/punchblock/translator/asterisk/component_spec.rb
450
452
  - spec/punchblock/translator/asterisk_spec.rb
@@ -465,7 +467,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
465
467
  version: '0'
466
468
  segments:
467
469
  - 0
468
- hash: 2228525083493052617
470
+ hash: 621112647086219314
469
471
  required_rubygems_version: !ruby/object:Gem::Requirement
470
472
  none: false
471
473
  requirements:
@@ -520,6 +522,7 @@ test_files:
520
522
  - spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb
521
523
  - spec/punchblock/translator/asterisk/component/input_spec.rb
522
524
  - spec/punchblock/translator/asterisk/component/output_spec.rb
525
+ - spec/punchblock/translator/asterisk/component/record_spec.rb
523
526
  - spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb
524
527
  - spec/punchblock/translator/asterisk/component_spec.rb
525
528
  - spec/punchblock/translator/asterisk_spec.rb