punchblock 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/lib/punchblock.rb +1 -1
  3. data/lib/punchblock/connection.rb +1 -0
  4. data/lib/punchblock/connection/asterisk.rb +0 -1
  5. data/lib/punchblock/connection/freeswitch.rb +49 -0
  6. data/lib/punchblock/event/offer.rb +1 -1
  7. data/lib/punchblock/translator.rb +5 -0
  8. data/lib/punchblock/translator/asterisk.rb +16 -28
  9. data/lib/punchblock/translator/asterisk/call.rb +4 -21
  10. data/lib/punchblock/translator/asterisk/component.rb +0 -5
  11. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +0 -3
  12. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +0 -1
  13. data/lib/punchblock/translator/asterisk/component/input.rb +7 -97
  14. data/lib/punchblock/translator/asterisk/component/output.rb +0 -4
  15. data/lib/punchblock/translator/asterisk/component/record.rb +0 -2
  16. data/lib/punchblock/translator/freeswitch.rb +153 -0
  17. data/lib/punchblock/translator/freeswitch/call.rb +265 -0
  18. data/lib/punchblock/translator/freeswitch/component.rb +92 -0
  19. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +57 -0
  20. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +17 -0
  21. data/lib/punchblock/translator/freeswitch/component/input.rb +29 -0
  22. data/lib/punchblock/translator/freeswitch/component/output.rb +56 -0
  23. data/lib/punchblock/translator/freeswitch/component/record.rb +79 -0
  24. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +26 -0
  25. data/lib/punchblock/translator/input_component.rb +108 -0
  26. data/lib/punchblock/version.rb +1 -1
  27. data/punchblock.gemspec +3 -2
  28. data/spec/punchblock/connection/freeswitch_spec.rb +90 -0
  29. data/spec/punchblock/translator/asterisk/call_spec.rb +23 -2
  30. data/spec/punchblock/translator/asterisk/component/input_spec.rb +3 -3
  31. data/spec/punchblock/translator/asterisk_spec.rb +1 -1
  32. data/spec/punchblock/translator/freeswitch/call_spec.rb +922 -0
  33. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +279 -0
  34. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +312 -0
  35. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +369 -0
  36. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +373 -0
  37. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +285 -0
  38. data/spec/punchblock/translator/freeswitch/component_spec.rb +118 -0
  39. data/spec/punchblock/translator/freeswitch_spec.rb +597 -0
  40. data/spec/punchblock_spec.rb +11 -0
  41. data/spec/spec_helper.rb +1 -0
  42. metadata +52 -7
@@ -0,0 +1,369 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Freeswitch
8
+ module Component
9
+ describe Output 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(:translator) { Punchblock::Translator::Freeswitch.new connection }
16
+ let(:mock_call) { Punchblock::Translator::Freeswitch::Call.new 'foo', translator }
17
+
18
+ let :original_command do
19
+ Punchblock::Component::Output.new command_options
20
+ end
21
+
22
+ let :ssml_doc do
23
+ RubySpeech::SSML.draw do
24
+ say_as(:interpret_as => :cardinal) { 'FOO' }
25
+ end
26
+ end
27
+
28
+ let :command_options do
29
+ { :ssml => ssml_doc }
30
+ end
31
+
32
+ subject { Output.new original_command, mock_call }
33
+
34
+ describe '#execute' do
35
+ before { original_command.request! }
36
+ def expect_playback(filename = audio_filename)
37
+ subject.wrapped_object.expects(:application).once.with 'playback', "file_string://#{filename}"
38
+ end
39
+
40
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
41
+
42
+ let :ssml_doc do
43
+ RubySpeech::SSML.draw do
44
+ audio :src => audio_filename
45
+ end
46
+ end
47
+
48
+ let(:command_opts) { {} }
49
+
50
+ let :command_options do
51
+ { :ssml => ssml_doc }.merge(command_opts)
52
+ end
53
+
54
+ let :original_command do
55
+ Punchblock::Component::Output.new command_options
56
+ end
57
+
58
+ describe 'ssml' do
59
+ context 'unset' do
60
+ let(:command_opts) { { :ssml => nil } }
61
+ it "should return an error and not execute any actions" do
62
+ subject.execute
63
+ error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
64
+ original_command.response(0.1).should be == error
65
+ end
66
+ end
67
+
68
+ context 'with a string (not SSML)' do
69
+ let :command_options do
70
+ { :text => 'Foo Bar' }
71
+ end
72
+
73
+ it "should return an unrenderable document error" do
74
+ subject.execute
75
+ error = ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered.'
76
+ original_command.response(0.1).should be == error
77
+ end
78
+
79
+ context 'with a single text node without spaces' do
80
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
81
+ let :command_options do
82
+ {
83
+ :ssml => RubySpeech::SSML.draw { string audio_filename }
84
+ }
85
+ end
86
+
87
+ it 'should playback the audio file using the playback application' do
88
+ expect_playback
89
+ subject.execute
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'with a single audio SSML node' do
95
+ let(:audio_filename) { 'http://foo.com/bar.mp3' }
96
+ let :command_options do
97
+ {
98
+ :ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
99
+ }
100
+ end
101
+
102
+ it 'should playback the audio file using the playback application' do
103
+ expect_playback
104
+ subject.execute
105
+ end
106
+
107
+ it 'should send a complete event when the file finishes playback' do
108
+ expect_playback.yields true
109
+ subject.execute
110
+ subject.handle_es_event RubyFS::Event.new(nil, :event_name => "CHANNEL_EXECUTE_COMPLETE", :application_response => 'FILE PLAYED')
111
+ original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
112
+ end
113
+
114
+ context "when playback returns an error" do
115
+ let(:fs_event) { RubyFS::Event.new(nil, :event_name => "CHANNEL_EXECUTE_COMPLETE", :application_response => "PLAYBACK ERROR") }
116
+ let(:complete_reason) { original_command.complete_event(0.1).reason }
117
+
118
+ it "sends a complete event with an error reason" do
119
+ expect_playback.yields true
120
+ subject.execute
121
+ subject.handle_es_event fs_event
122
+ complete_reason.should be_a Punchblock::Event::Complete::Error
123
+ complete_reason.details.should == 'Engine error: PLAYBACK ERROR'
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'with multiple audio SSML nodes' do
129
+ let(:audio_filename1) { 'http://foo.com/bar.mp3' }
130
+ let(:audio_filename2) { 'http://foo.com/baz.mp3' }
131
+ let :command_options do
132
+ {
133
+ :ssml => RubySpeech::SSML.draw do
134
+ audio :src => audio_filename1
135
+ audio :src => audio_filename2
136
+ end
137
+ }
138
+ end
139
+
140
+ it 'should playback all audio files using playback' do
141
+ expect_playback [audio_filename1, audio_filename2].join('!')
142
+ subject.execute
143
+ end
144
+
145
+ it 'should send a complete event when the files finish playback' do
146
+ expect_playback([audio_filename1, audio_filename2].join('!')).yields true
147
+ subject.execute
148
+ subject.handle_es_event RubyFS::Event.new(nil, :event_name => "CHANNEL_EXECUTE_COMPLETE", :application_response => "FILE PLAYED")
149
+ original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
150
+ end
151
+ end
152
+
153
+ context "with an SSML document containing elements other than <audio/>" do
154
+ let :command_options do
155
+ {
156
+ :ssml => RubySpeech::SSML.draw do
157
+ string "Foo Bar"
158
+ end
159
+ }
160
+ end
161
+
162
+ it "should return an unrenderable document error" do
163
+ subject.execute
164
+ error = ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered.'
165
+ original_command.response(0.1).should be == error
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'start-offset' do
171
+ context 'unset' do
172
+ let(:command_opts) { { :start_offset => nil } }
173
+ it 'should not pass any options to Playback' do
174
+ expect_playback
175
+ subject.execute
176
+ end
177
+ end
178
+
179
+ context 'set' do
180
+ let(:command_opts) { { :start_offset => 10 } }
181
+ it "should return an error and not execute any actions" do
182
+ subject.execute
183
+ error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported.'
184
+ original_command.response(0.1).should be == error
185
+ end
186
+ end
187
+ end
188
+
189
+ describe 'start-paused' do
190
+ context 'false' do
191
+ let(:command_opts) { { :start_paused => false } }
192
+ it 'should not pass any options to Playback' do
193
+ expect_playback
194
+ subject.execute
195
+ end
196
+ end
197
+
198
+ context 'true' do
199
+ let(:command_opts) { { :start_paused => true } }
200
+ it "should return an error and not execute any actions" do
201
+ subject.execute
202
+ error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported.'
203
+ original_command.response(0.1).should be == error
204
+ end
205
+ end
206
+ end
207
+
208
+ describe 'repeat-interval' do
209
+ context 'unset' do
210
+ let(:command_opts) { { :repeat_interval => nil } }
211
+ it 'should not pass any options to Playback' do
212
+ expect_playback
213
+ subject.execute
214
+ end
215
+ end
216
+
217
+ context 'set' do
218
+ let(:command_opts) { { :repeat_interval => 10 } }
219
+ it "should return an error and not execute any actions" do
220
+ subject.execute
221
+ error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported.'
222
+ original_command.response(0.1).should be == error
223
+ end
224
+ end
225
+ end
226
+
227
+ describe 'repeat-times' do
228
+ context 'unset' do
229
+ let(:command_opts) { { :repeat_times => nil } }
230
+ it 'should not pass any options to Playback' do
231
+ expect_playback
232
+ subject.execute
233
+ end
234
+ end
235
+
236
+ context 'set' do
237
+ let(:command_opts) { { :repeat_times => 2 } }
238
+ it "should return an error and not execute any actions" do
239
+ subject.execute
240
+ error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported.'
241
+ original_command.response(0.1).should be == error
242
+ end
243
+ end
244
+ end
245
+
246
+ describe 'max-time' do
247
+ context 'unset' do
248
+ let(:command_opts) { { :max_time => nil } }
249
+ it 'should not pass any options to Playback' do
250
+ expect_playback
251
+ subject.execute
252
+ end
253
+ end
254
+
255
+ context 'set' do
256
+ let(:command_opts) { { :max_time => 30 } }
257
+ it "should return an error and not execute any actions" do
258
+ subject.execute
259
+ error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported.'
260
+ original_command.response(0.1).should be == error
261
+ end
262
+ end
263
+ end
264
+
265
+ describe 'voice' do
266
+ context 'unset' do
267
+ let(:command_opts) { { :voice => nil } }
268
+ it 'should not pass the v option to Playback' do
269
+ expect_playback
270
+ subject.execute
271
+ end
272
+ end
273
+
274
+ context 'set' do
275
+ let(:command_opts) { { :voice => 'alison' } }
276
+ it "should return an error and not execute any actions" do
277
+ subject.execute
278
+ error = ProtocolError.new.setup 'option error', 'A voice value is unsupported.'
279
+ original_command.response(0.1).should be == error
280
+ end
281
+ end
282
+ end
283
+
284
+ describe 'interrupt_on' do
285
+ context "set to nil" do
286
+ let(:command_opts) { { :interrupt_on => nil } }
287
+ it "should not pass any digits to Playback" do
288
+ expect_playback
289
+ subject.execute
290
+ end
291
+ end
292
+
293
+ context "set to :any" do
294
+ let(:command_opts) { { :interrupt_on => :any } }
295
+ it "should return an error and not execute any actions" do
296
+ subject.execute
297
+ error = ProtocolError.new.setup 'option error', 'An interrupt-on value of any is unsupported.'
298
+ original_command.response(0.1).should be == error
299
+ end
300
+ end
301
+
302
+ context "set to :dtmf" do
303
+ let(:command_opts) { { :interrupt_on => :dtmf } }
304
+ it "should return an error and not execute any actions" do
305
+ subject.execute
306
+ error = ProtocolError.new.setup 'option error', 'An interrupt-on value of dtmf is unsupported.'
307
+ original_command.response(0.1).should be == error
308
+ end
309
+ end
310
+
311
+ context "set to :speech" do
312
+ let(:command_opts) { { :interrupt_on => :speech } }
313
+ it "should return an error and not execute any actions" do
314
+ subject.execute
315
+ error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
316
+ original_command.response(0.1).should be == error
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ describe "#execute_command" do
323
+ context "with a command it does not understand" do
324
+ let(:command) { Punchblock::Component::Output::Pause.new }
325
+
326
+ before { command.request! }
327
+
328
+ it "returns a ProtocolError response" do
329
+ subject.execute_command command
330
+ command.response(0.1).should be_a ProtocolError
331
+ end
332
+ end
333
+
334
+ context "with a Stop command" do
335
+ let(:command) { Punchblock::Component::Stop.new }
336
+ let(:reason) { original_command.complete_event(5).reason }
337
+
338
+ before do
339
+ command.request!
340
+ original_command.request!
341
+ original_command.execute!
342
+ end
343
+
344
+ it "sets the command response to true" do
345
+ subject.wrapped_object.expects(:application)
346
+ subject.execute_command command
347
+ command.response(0.1).should be == true
348
+ end
349
+
350
+ it "sends the correct complete event" do
351
+ subject.wrapped_object.expects(:application)
352
+ original_command.should_not be_complete
353
+ subject.execute_command command
354
+ reason.should be_a Punchblock::Event::Complete::Stop
355
+ original_command.should be_complete
356
+ end
357
+
358
+ it "breaks the current dialplan application" do
359
+ subject.wrapped_object.expects(:application).once.with 'break'
360
+ subject.execute_command command
361
+ end
362
+ end
363
+ end
364
+ end
365
+
366
+ end
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,373 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Freeswitch
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(:id) { Punchblock.new_uuid }
16
+ let(:translator) { Punchblock::Translator::Freeswitch.new connection }
17
+ let(:mock_stream) { mock('RubyFS::Stream') }
18
+ let(:mock_call) { Punchblock::Translator::Freeswitch::Call.new id, translator, nil, mock_stream }
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
+ before { mock_stream.stub_everything }
29
+
30
+ subject { Record.new original_command, mock_call }
31
+
32
+ let(:filename) { "#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav" }
33
+
34
+ describe '#execute' do
35
+ let(:reason) { original_command.complete_event(5).reason }
36
+ let(:recording) { original_command.complete_event(5).recording }
37
+
38
+ before { original_command.request! }
39
+
40
+ it "sets command response to a reference to the component" do
41
+ subject.execute
42
+ original_command.response(0.1).should be_a Ref
43
+ original_command.component_id.should be == subject.id
44
+ end
45
+
46
+ it "starts a recording via uuid_record, using the component ID as the filename" do
47
+ mock_call.expects(:uuid_foo).once.with(:record, "start #{filename}")
48
+ subject.execute
49
+ end
50
+
51
+ it "sends a success complete event when the recording ends" do
52
+ full_filename = "file://#{filename}"
53
+ subject.execute
54
+ record_stop_event = RubyFS::Event.new nil, {
55
+ :event_name => 'RECORD_STOP',
56
+ :record_file_path => filename
57
+ }
58
+ mock_call.handle_es_event record_stop_event
59
+ reason.should be_a Punchblock::Component::Record::Complete::Success
60
+ recording.uri.should be == full_filename
61
+ original_command.should be_complete
62
+ end
63
+
64
+ describe 'start_paused' do
65
+ context "set to nil" do
66
+ let(:command_options) { { :start_paused => nil } }
67
+ it "should execute normally" do
68
+ mock_call.expects(:uuid_foo).once
69
+ subject.execute
70
+ original_command.response(0.1).should be_a Ref
71
+ end
72
+ end
73
+
74
+ context "set to false" do
75
+ let(:command_options) { { :start_paused => false } }
76
+ it "should execute normally" do
77
+ mock_call.expects(:uuid_foo).once
78
+ subject.execute
79
+ original_command.response(0.1).should be_a Ref
80
+ end
81
+ end
82
+
83
+ context "set to true" do
84
+ let(:command_options) { { :start_paused => true } }
85
+ it "should return an error and not execute any actions" do
86
+ mock_call.expects(:uuid_foo).never
87
+ subject.execute
88
+ error = ProtocolError.new.setup 'option error', 'A start-paused value of true is unsupported.'
89
+ original_command.response(0.1).should be == error
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'initial_timeout' do
95
+ context "set to nil" do
96
+ let(:command_options) { { :initial_timeout => nil } }
97
+ it "should execute normally" do
98
+ mock_call.expects(:uuid_foo).once
99
+ subject.execute
100
+ original_command.response(0.1).should be_a Ref
101
+ end
102
+ end
103
+
104
+ context "set to -1" do
105
+ let(:command_options) { { :initial_timeout => -1 } }
106
+ it "should execute normally" do
107
+ mock_call.expects(:uuid_foo).once
108
+ subject.execute
109
+ original_command.response(0.1).should be_a Ref
110
+ end
111
+ end
112
+
113
+ context "set to a positive number" do
114
+ let(:command_options) { { :initial_timeout => 10 } }
115
+ it "should return an error and not execute any actions" do
116
+ mock_call.expects(:uuid_foo).never
117
+ subject.execute
118
+ error = ProtocolError.new.setup 'option error', 'An initial-timeout value is unsupported.'
119
+ original_command.response(0.1).should be == error
120
+ end
121
+ end
122
+ end
123
+
124
+ describe 'final_timeout' do
125
+ context "set to nil" do
126
+ let(:command_options) { { :final_timeout => nil } }
127
+ it "should execute normally" do
128
+ mock_call.expects(:uuid_foo).once
129
+ subject.execute
130
+ original_command.response(0.1).should be_a Ref
131
+ end
132
+ end
133
+
134
+ context "set to -1" do
135
+ let(:command_options) { { :final_timeout => -1 } }
136
+ it "should execute normally" do
137
+ mock_call.expects(:uuid_foo).once
138
+ subject.execute
139
+ original_command.response(0.1).should be_a Ref
140
+ end
141
+ end
142
+
143
+ context "set to a positive number" do
144
+ let(:command_options) { { :final_timeout => 10 } }
145
+ it "should return an error and not execute any actions" do
146
+ mock_call.expects(:send_agi_action!).never
147
+ subject.execute
148
+ error = ProtocolError.new.setup 'option error', 'A final-timeout value is unsupported.'
149
+ original_command.response(0.1).should be == error
150
+ end
151
+ end
152
+ end
153
+
154
+ describe 'format' do
155
+ context "set to nil" do
156
+ let(:command_options) { { :format => nil } }
157
+ it "should execute as 'wav'" do
158
+ mock_call.expects(:uuid_foo).once.with(:record, regexp_matches(/.wav/))
159
+ subject.execute
160
+ original_command.response(0.1).should be_a Ref
161
+ end
162
+
163
+ it "provides the correct filename in the recording" do
164
+ mock_call.expects(:uuid_foo)
165
+ subject.execute
166
+ record_stop_event = RubyFS::Event.new nil, {
167
+ :event_name => 'RECORD_STOP',
168
+ :record_file_path => "#{Record::RECORDING_BASE_PATH}/#{subject.id}.wav"
169
+ }
170
+ mock_call.handle_es_event record_stop_event
171
+ recording.uri.should match(/.*\.wav$/)
172
+ end
173
+ end
174
+
175
+ context "set to 'mp3'" do
176
+ let(:command_options) { { :format => 'mp3' } }
177
+ it "should execute as 'mp3'" do
178
+ mock_call.expects(:uuid_foo).once.with(:record, regexp_matches(/.mp3/))
179
+ subject.execute
180
+ original_command.response(0.1).should be_a Ref
181
+ end
182
+
183
+ it "provides the correct filename in the recording" do
184
+ mock_call.expects(:uuid_foo)
185
+ subject.execute
186
+ record_stop_event = RubyFS::Event.new nil, {
187
+ :event_name => 'RECORD_STOP',
188
+ :record_file_path => "#{Record::RECORDING_BASE_PATH}/#{subject.id}.mp3"
189
+ }
190
+ mock_call.handle_es_event record_stop_event
191
+ recording.uri.should match(/.*\.mp3$/)
192
+ end
193
+ end
194
+ end
195
+
196
+ describe 'start_beep' do
197
+ context "set to nil" do
198
+ let(:command_options) { { :start_beep => nil } }
199
+ it "should execute normally" do
200
+ mock_call.expects(:uuid_foo).once
201
+ subject.execute
202
+ original_command.response(0.1).should be_a Ref
203
+ end
204
+ end
205
+
206
+ context "set to false" do
207
+ let(:command_options) { { :start_beep => false } }
208
+ it "should execute normally" do
209
+ mock_call.expects(:uuid_foo).once
210
+ subject.execute
211
+ original_command.response(0.1).should be_a Ref
212
+ end
213
+ end
214
+
215
+ context "set to true" do
216
+ let(:command_options) { { :start_beep => true } }
217
+
218
+ it "should return an error and not execute any actions" do
219
+ mock_call.expects(:uuid_foo).never
220
+ subject.execute
221
+ error = ProtocolError.new.setup 'option error', 'A start-beep value of true is unsupported.'
222
+ original_command.response(0.1).should be == error
223
+ end
224
+ end
225
+ end
226
+
227
+ describe 'max_duration' do
228
+ context "set to nil" do
229
+ let(:command_options) { { :max_duration => nil } }
230
+ it "should execute normally" do
231
+ mock_call.expects(:uuid_foo).once.with(:record, regexp_matches(/.wav$/))
232
+ subject.execute
233
+ original_command.response(0.1).should be_a Ref
234
+ end
235
+ end
236
+
237
+ context "set to -1" do
238
+ let(:command_options) { { :max_duration => -1 } }
239
+ it "should execute normally" do
240
+ mock_call.expects(:uuid_foo).once.with(:record, regexp_matches(/.wav$/))
241
+ subject.execute
242
+ original_command.response(0.1).should be_a Ref
243
+ end
244
+ end
245
+
246
+ context 'a negative number other than -1' do
247
+ let(:command_options) { { :max_duration => -1000 } }
248
+
249
+ it "should return an error and not execute any actions" do
250
+ subject.execute
251
+ error = ProtocolError.new.setup 'option error', 'A max-duration value that is negative (and not -1) is invalid.'
252
+ original_command.response(0.1).should be == error
253
+ end
254
+ end
255
+
256
+ context 'a positive number' do
257
+ let(:reason) { original_command.complete_event(5).reason }
258
+ let(:recording) { original_command.complete_event(5).recording }
259
+ let(:command_options) { { :max_duration => 1000 } }
260
+
261
+ it "executes the recording with a time limit" do
262
+ mock_call.expects(:uuid_foo).once.with(:record, regexp_matches(/.wav 1$/))
263
+ subject.execute
264
+ original_command.response(0.1).should be_a Ref
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ describe "#execute_command" do
271
+ let(:reason) { original_command.complete_event(5).reason }
272
+ let(:recording) { original_command.complete_event(5).recording }
273
+
274
+ context "with a command it does not understand" do
275
+ let(:command) { Punchblock::Component::Output::Pause.new }
276
+
277
+ before { command.request! }
278
+ it "returns a ProtocolError response" do
279
+ subject.execute_command command
280
+ command.response(0.1).should be_a ProtocolError
281
+ end
282
+ end
283
+
284
+ context "with a Stop command" do
285
+ let(:command) { Punchblock::Component::Stop.new }
286
+
287
+ before do
288
+ mock_call.expects :uuid_foo
289
+ command.request!
290
+ original_command.request!
291
+ subject.execute
292
+ end
293
+
294
+ let :send_stop_event do
295
+ record_stop_event = RubyFS::Event.new nil, {
296
+ :event_name => 'RECORD_STOP',
297
+ :record_file_path => filename
298
+ }
299
+ mock_call.handle_es_event record_stop_event
300
+ end
301
+
302
+ it "sets the command response to true" do
303
+ mock_call.expects :uuid_foo
304
+ subject.execute_command command
305
+ send_stop_event
306
+ command.response(0.1).should be == true
307
+ end
308
+
309
+ it "executes a uuid_record stop command" do
310
+ mock_call.expects(:uuid_foo).with(:record, "stop #{filename}")
311
+ subject.execute_command command
312
+ end
313
+
314
+ it "sends the correct complete event" do
315
+ mock_call.expects(:uuid_foo).with(:record, "stop #{filename}")
316
+ subject.execute_command command
317
+ send_stop_event
318
+ reason.should be_a Punchblock::Event::Complete::Stop
319
+ recording.uri.should be == "file://#{filename}"
320
+ original_command.should be_complete
321
+ end
322
+ end
323
+
324
+ context "with a Pause command" do
325
+ let(:command) { Punchblock::Component::Record::Pause.new }
326
+
327
+ before do
328
+ pending
329
+ mock_call.expects :uuid_foo
330
+ command.request!
331
+ original_command.request!
332
+ subject.execute
333
+ end
334
+
335
+ it "sets the command response to true" do
336
+ subject.execute_command command
337
+ command.response(0.1).should be == true
338
+ end
339
+
340
+ it "pauses the recording via AMI" do
341
+ mock_call.expects(:uuid_foo).once.with('PauseMonitor', 'Channel' => channel)
342
+ subject.execute_command command
343
+ end
344
+ end
345
+
346
+ context "with a Resume command" do
347
+ let(:command) { Punchblock::Component::Record::Resume.new }
348
+
349
+ before do
350
+ pending
351
+ mock_call.expects :uuid_foo
352
+ command.request!
353
+ original_command.request!
354
+ subject.execute
355
+ end
356
+
357
+ it "sets the command response to true" do
358
+ subject.execute_command command
359
+ command.response(0.1).should be == true
360
+ end
361
+
362
+ it "resumes the recording via AMI" do
363
+ mock_call.expects(:uuid_foo).once.with('ResumeMonitor', 'Channel' => channel)
364
+ subject.execute_command command
365
+ end
366
+ end
367
+ end
368
+
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end