punchblock 1.3.0 → 1.4.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 +5 -0
- data/lib/punchblock.rb +1 -1
- data/lib/punchblock/connection.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +0 -1
- data/lib/punchblock/connection/freeswitch.rb +49 -0
- data/lib/punchblock/event/offer.rb +1 -1
- data/lib/punchblock/translator.rb +5 -0
- data/lib/punchblock/translator/asterisk.rb +16 -28
- data/lib/punchblock/translator/asterisk/call.rb +4 -21
- data/lib/punchblock/translator/asterisk/component.rb +0 -5
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +0 -3
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +0 -1
- data/lib/punchblock/translator/asterisk/component/input.rb +7 -97
- data/lib/punchblock/translator/asterisk/component/output.rb +0 -4
- data/lib/punchblock/translator/asterisk/component/record.rb +0 -2
- data/lib/punchblock/translator/freeswitch.rb +153 -0
- data/lib/punchblock/translator/freeswitch/call.rb +265 -0
- data/lib/punchblock/translator/freeswitch/component.rb +92 -0
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +57 -0
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +17 -0
- data/lib/punchblock/translator/freeswitch/component/input.rb +29 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +56 -0
- data/lib/punchblock/translator/freeswitch/component/record.rb +79 -0
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +26 -0
- data/lib/punchblock/translator/input_component.rb +108 -0
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +3 -2
- data/spec/punchblock/connection/freeswitch_spec.rb +90 -0
- data/spec/punchblock/translator/asterisk/call_spec.rb +23 -2
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +3 -3
- data/spec/punchblock/translator/asterisk_spec.rb +1 -1
- data/spec/punchblock/translator/freeswitch/call_spec.rb +922 -0
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +279 -0
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +312 -0
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +369 -0
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +373 -0
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +285 -0
- data/spec/punchblock/translator/freeswitch/component_spec.rb +118 -0
- data/spec/punchblock/translator/freeswitch_spec.rb +597 -0
- data/spec/punchblock_spec.rb +11 -0
- data/spec/spec_helper.rb +1 -0
- 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
|