punchblock 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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