punchblock 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,284 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Punchblock
4
- module Translator
5
- class Asterisk
6
- module Component
7
- module Asterisk
8
- describe Input do
9
- let(:connection) do
10
- mock_connection_with_event_handler do |event|
11
- command.add_event event
12
- end
13
- end
14
- let(:media_engine) { nil }
15
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
16
- let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
17
- let(:command_options) { {} }
18
-
19
- let :command do
20
- Punchblock::Component::Input.new command_options
21
- end
22
-
23
- let :grammar do
24
- RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
25
- rule id: 'digit' do
26
- one_of do
27
- 0.upto(9) { |d| item { d.to_s } }
28
- end
29
- end
30
-
31
- rule id: 'pin', scope: 'public' do
32
- item repeat: '2' do
33
- ruleref uri: '#digit'
34
- end
35
- end
36
- end
37
- end
38
-
39
- subject { Input.new command, call }
40
-
41
- describe '#execute' do
42
- before { command.request! }
43
-
44
- context 'with a media engine of :unimrcp' do
45
- pending
46
- let(:media_engine) { :unimrcp }
47
- end
48
-
49
- context 'with a media engine of :asterisk' do
50
- let(:media_engine) { :asterisk }
51
-
52
- let(:command_opts) { {} }
53
-
54
- let :command_options do
55
- { :mode => :dtmf, :grammar => { :value => grammar } }.merge(command_opts)
56
- end
57
-
58
- def ami_event_for_dtmf(digit, position)
59
- RubyAMI::Event.new('DTMF').tap do |e|
60
- e['Digit'] = digit.to_s
61
- e['Start'] = position == :start ? 'Yes' : 'No'
62
- e['End'] = position == :end ? 'Yes' : 'No'
63
- end
64
- end
65
-
66
- def send_ami_events_for_dtmf(digit)
67
- call.process_ami_event ami_event_for_dtmf(digit, :start)
68
- call.process_ami_event ami_event_for_dtmf(digit, :end)
69
- end
70
-
71
- let(:reason) { command.complete_event(5).reason }
72
-
73
- describe "receiving DTMF events" do
74
- before do
75
- subject.execute
76
- expected_event
77
- end
78
-
79
- context "when a match is found" do
80
- before do
81
- send_ami_events_for_dtmf 1
82
- send_ami_events_for_dtmf 2
83
- end
84
-
85
- let :expected_event do
86
- Punchblock::Component::Input::Complete::Success.new :mode => :dtmf,
87
- :confidence => 1,
88
- :utterance => '12',
89
- :interpretation => 'dtmf-1 dtmf-2',
90
- :component_id => subject.id,
91
- :call_id => call.id
92
- end
93
-
94
- it "should send a success complete event with the relevant data" do
95
- reason.should == expected_event
96
- end
97
- end
98
-
99
- context "when the match is invalid" do
100
- before do
101
- send_ami_events_for_dtmf 1
102
- send_ami_events_for_dtmf '#'
103
- end
104
-
105
- let :expected_event do
106
- Punchblock::Component::Input::Complete::NoMatch.new :component_id => subject.id,
107
- :call_id => call.id
108
- end
109
-
110
- it "should send a nomatch complete event" do
111
- reason.should == expected_event
112
- end
113
- end
114
- end
115
-
116
- describe 'grammar' do
117
- context 'unset' do
118
- let(:command_opts) { { :grammar => nil } }
119
- it "should return an error and not execute any actions" do
120
- subject.execute
121
- error = ProtocolError.new 'option error', 'A grammar document is required.'
122
- command.response(0.1).should == error
123
- end
124
- end
125
- end
126
-
127
- describe 'mode' do
128
- context 'unset' do
129
- let(:command_opts) { { :mode => nil } }
130
- it "should return an error and not execute any actions" do
131
- subject.execute
132
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
133
- command.response(0.1).should == error
134
- end
135
- end
136
-
137
- context 'any' do
138
- let(:command_opts) { { :mode => :any } }
139
- it "should return an error and not execute any actions" do
140
- subject.execute
141
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
142
- command.response(0.1).should == error
143
- end
144
- end
145
-
146
- context 'speech' do
147
- let(:command_opts) { { :mode => :speech } }
148
- it "should return an error and not execute any actions" do
149
- subject.execute
150
- error = ProtocolError.new 'option error', 'A mode value other than DTMF is unsupported on Asterisk.'
151
- command.response(0.1).should == error
152
- end
153
- end
154
- end
155
-
156
- describe 'terminator' do
157
- pending
158
- end
159
-
160
- describe 'recognizer' do
161
- pending
162
- end
163
-
164
- describe 'initial-timeout' do
165
- context 'a positive number' do
166
- let(:command_opts) { { :initial_timeout => 1000 } }
167
-
168
- it "should not cause a NoInput if first input is received in time" do
169
- subject.execute
170
- send_ami_events_for_dtmf 1
171
- sleep 1.5
172
- send_ami_events_for_dtmf 2
173
- reason.should be_a Punchblock::Component::Input::Complete::Success
174
- end
175
-
176
- it "should cause a NoInput complete event to be sent after the timeout" do
177
- subject.execute
178
- sleep 1.5
179
- send_ami_events_for_dtmf 1
180
- send_ami_events_for_dtmf 2
181
- reason.should be_a Punchblock::Component::Input::Complete::NoInput
182
- end
183
- end
184
-
185
- context '-1' do
186
- let(:command_opts) { { :initial_timeout => -1 } }
187
-
188
- it "should not start a timer" do
189
- subject.wrapped_object.expects(:begin_initial_timer).never
190
- subject.execute
191
- end
192
- end
193
-
194
- context 'unset' do
195
- let(:command_opts) { { :initial_timeout => nil } }
196
-
197
- it "should not start a timer" do
198
- subject.wrapped_object.expects(:begin_initial_timer).never
199
- subject.execute
200
- end
201
- end
202
-
203
- context 'a negative number other than -1' do
204
- let(:command_opts) { { :initial_timeout => -1000 } }
205
-
206
- it "should return an error and not execute any actions" do
207
- subject.execute
208
- error = ProtocolError.new 'option error', 'An initial timeout value that is negative (and not -1) is invalid.'
209
- command.response(0.1).should == error
210
- end
211
- end
212
- end
213
-
214
- describe 'inter-digit-timeout' do
215
- context 'a positive number' do
216
- let(:command_opts) { { :inter_digit_timeout => 1000 } }
217
-
218
- it "should not prevent a Match if input is received in time" do
219
- subject.execute
220
- sleep 1.5
221
- send_ami_events_for_dtmf 1
222
- sleep 0.5
223
- send_ami_events_for_dtmf 2
224
- reason.should be_a Punchblock::Component::Input::Complete::Success
225
- end
226
-
227
- it "should cause a NoMatch complete event to be sent after the timeout" do
228
- subject.execute
229
- sleep 1.5
230
- send_ami_events_for_dtmf 1
231
- sleep 1.5
232
- send_ami_events_for_dtmf 2
233
- reason.should be_a Punchblock::Component::Input::Complete::NoMatch
234
- end
235
- end
236
-
237
- context '-1' do
238
- let(:command_opts) { { :inter_digit_timeout => -1 } }
239
-
240
- it "should not start a timer" do
241
- subject.wrapped_object.expects(:begin_inter_digit_timer).never
242
- subject.execute
243
- end
244
- end
245
-
246
- context 'unset' do
247
- let(:command_opts) { { :inter_digit_timeout => nil } }
248
-
249
- it "should not start a timer" do
250
- subject.wrapped_object.expects(:begin_inter_digit_timer).never
251
- subject.execute
252
- end
253
- end
254
-
255
- context 'a negative number other than -1' do
256
- let(:command_opts) { { :inter_digit_timeout => -1000 } }
257
-
258
- it "should return an error and not execute any actions" do
259
- subject.execute
260
- error = ProtocolError.new 'option error', 'An inter-digit timeout value that is negative (and not -1) is invalid.'
261
- command.response(0.1).should == error
262
- end
263
- end
264
- end
265
-
266
- describe 'sensitivity' do
267
- pending
268
- end
269
-
270
- describe 'min-confidence' do
271
- pending
272
- end
273
-
274
- describe 'max-silence' do
275
- pending
276
- end
277
- end
278
- end
279
- end
280
- end
281
- end
282
- end
283
- end
284
- end
@@ -1,493 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Punchblock
4
- module Translator
5
- class Asterisk
6
- module Component
7
- module Asterisk
8
- describe Output do
9
- let(:connection) do
10
- mock_connection_with_event_handler do |event|
11
- command.add_event event
12
- end
13
- end
14
- let(:media_engine) { nil }
15
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
16
- let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
17
-
18
- let :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 command, mock_call }
33
-
34
- describe '#execute' do
35
- before { command.request! }
36
-
37
- context 'with a media engine of :unimrcp' do
38
- let(:media_engine) { :unimrcp }
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
- say_as(:interpret_as => :cardinal) { 'FOO' }
46
- end
47
- end
48
-
49
- let(:command_opts) { {} }
50
-
51
- let :command_options do
52
- { :ssml => ssml_doc }.merge(command_opts)
53
- end
54
-
55
- def expect_mrcpsynth_with_options(options)
56
- mock_call.expects(:send_agi_action!).once.with do |*args|
57
- args[0].should == 'EXEC MRCPSynth'
58
- args[2].should match options
59
- end
60
- end
61
-
62
- it "should execute MRCPSynth" do
63
- mock_call.expects(:send_agi_action!).once.with 'EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, ''
64
- subject.execute
65
- end
66
-
67
- it 'should send a complete event when MRCPSynth completes' do
68
- def mock_call.send_agi_action!(*args, &block)
69
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
70
- end
71
- subject.execute
72
- command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
73
- end
74
-
75
- describe 'ssml' do
76
- context 'unset' do
77
- let(:command_opts) { { :ssml => nil } }
78
- it "should return an error and not execute any actions" do
79
- subject.execute
80
- error = ProtocolError.new 'option error', 'An SSML document is required.'
81
- command.response(0.1).should == error
82
- end
83
- end
84
- end
85
-
86
- describe 'start-offset' do
87
- context 'unset' do
88
- let(:command_opts) { { :start_offset => nil } }
89
- it 'should not pass any options to MRCPSynth' do
90
- expect_mrcpsynth_with_options(//)
91
- subject.execute
92
- end
93
- end
94
-
95
- context 'set' do
96
- let(:command_opts) { { :start_offset => 10 } }
97
- it "should return an error and not execute any actions" do
98
- subject.execute
99
- error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
100
- command.response(0.1).should == error
101
- end
102
- end
103
- end
104
-
105
- describe 'start-paused' do
106
- context 'false' do
107
- let(:command_opts) { { :start_paused => false } }
108
- it 'should not pass any options to MRCPSynth' do
109
- expect_mrcpsynth_with_options(//)
110
- subject.execute
111
- end
112
- end
113
-
114
- context 'true' do
115
- let(:command_opts) { { :start_paused => true } }
116
- it "should return an error and not execute any actions" do
117
- subject.execute
118
- error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
119
- command.response(0.1).should == error
120
- end
121
- end
122
- end
123
-
124
- describe 'repeat-interval' do
125
- context 'unset' do
126
- let(:command_opts) { { :repeat_interval => nil } }
127
- it 'should not pass any options to MRCPSynth' do
128
- expect_mrcpsynth_with_options(//)
129
- subject.execute
130
- end
131
- end
132
-
133
- context 'set' do
134
- let(:command_opts) { { :repeat_interval => 10 } }
135
- it "should return an error and not execute any actions" do
136
- subject.execute
137
- error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
138
- command.response(0.1).should == error
139
- end
140
- end
141
- end
142
-
143
- describe 'repeat-times' do
144
- context 'unset' do
145
- let(:command_opts) { { :repeat_times => nil } }
146
- it 'should not pass any options to MRCPSynth' do
147
- expect_mrcpsynth_with_options(//)
148
- subject.execute
149
- end
150
- end
151
-
152
- context 'set' do
153
- let(:command_opts) { { :repeat_times => 2 } }
154
- it "should return an error and not execute any actions" do
155
- subject.execute
156
- error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
157
- command.response(0.1).should == error
158
- end
159
- end
160
- end
161
-
162
- describe 'max-time' do
163
- context 'unset' do
164
- let(:command_opts) { { :max_time => nil } }
165
- it 'should not pass any options to MRCPSynth' do
166
- expect_mrcpsynth_with_options(//)
167
- subject.execute
168
- end
169
- end
170
-
171
- context 'set' do
172
- let(:command_opts) { { :max_time => 30 } }
173
- it "should return an error and not execute any actions" do
174
- subject.execute
175
- error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
176
- command.response(0.1).should == error
177
- end
178
- end
179
- end
180
-
181
- describe 'voice' do
182
- context 'unset' do
183
- let(:command_opts) { { :voice => nil } }
184
- it 'should not pass the v option to MRCPSynth' do
185
- expect_mrcpsynth_with_options(//)
186
- subject.execute
187
- end
188
- end
189
-
190
- context 'set' do
191
- let(:command_opts) { { :voice => 'alison' } }
192
- it 'should pass the v option to MRCPSynth' do
193
- expect_mrcpsynth_with_options(/v=alison/)
194
- subject.execute
195
- end
196
- end
197
- end
198
-
199
- describe 'interrupt_on' do
200
- context "set to nil" do
201
- let(:command_opts) { { :interrupt_on => nil } }
202
- it "should not pass the i option to MRCPSynth" do
203
- expect_mrcpsynth_with_options(//)
204
- subject.execute
205
- end
206
- end
207
-
208
- context "set to :any" do
209
- let(:command_opts) { { :interrupt_on => :any } }
210
- it "should pass the i option to MRCPSynth" do
211
- expect_mrcpsynth_with_options(/i=any/)
212
- subject.execute
213
- end
214
- end
215
-
216
- context "set to :dtmf" do
217
- let(:command_opts) { { :interrupt_on => :dtmf } }
218
- it "should pass the i option to MRCPSynth" do
219
- expect_mrcpsynth_with_options(/i=any/)
220
- subject.execute
221
- end
222
- end
223
-
224
- context "set to :speech" do
225
- let(:command_opts) { { :interrupt_on => :speech } }
226
- it "should return an error and not execute any actions" do
227
- subject.execute
228
- error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
229
- command.response(0.1).should == error
230
- end
231
- end
232
- end
233
- end
234
-
235
- context 'with a media engine of :asterisk' do
236
- let(:media_engine) { :asterisk }
237
-
238
- def expect_stream_file_with_options(options = nil)
239
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename, options do |*args|
240
- args[2].should == options
241
- subject.continue!
242
- true
243
- end
244
- end
245
-
246
- let(:audio_filename) { 'http://foo.com/bar.mp3' }
247
-
248
- let :ssml_doc do
249
- RubySpeech::SSML.draw do
250
- audio :src => audio_filename
251
- say_as(:interpret_as => :cardinal) { 'FOO' }
252
- end
253
- end
254
-
255
- let(:command_opts) { {} }
256
-
257
- let :command_options do
258
- { :ssml => ssml_doc }.merge(command_opts)
259
- end
260
-
261
- let :command do
262
- Punchblock::Component::Output.new command_options
263
- end
264
-
265
- describe 'ssml' do
266
- context 'unset' do
267
- let(:command_opts) { { :ssml => nil } }
268
- it "should return an error and not execute any actions" do
269
- subject.execute
270
- error = ProtocolError.new 'option error', 'An SSML document is required.'
271
- command.response(0.1).should == error
272
- end
273
- end
274
-
275
- context 'with a single audio SSML node' do
276
- let(:audio_filename) { 'http://foo.com/bar.mp3' }
277
- let :command_options do
278
- {
279
- :ssml => RubySpeech::SSML.draw { audio :src => audio_filename }
280
- }
281
- end
282
-
283
- it 'should playback the audio file using STREAM FILE' do
284
- expect_stream_file_with_options
285
- subject.execute
286
- end
287
-
288
- it 'should send a complete event when the file finishes playback' do
289
- def mock_call.send_agi_action!(*args, &block)
290
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
291
- end
292
- subject.execute
293
- command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
294
- end
295
- end
296
-
297
- context 'with multiple audio SSML nodes' do
298
- let(:audio_filename1) { 'http://foo.com/bar.mp3' }
299
- let(:audio_filename2) { 'http://foo.com/baz.mp3' }
300
- let :command_options do
301
- {
302
- :ssml => RubySpeech::SSML.draw do
303
- audio :src => audio_filename1
304
- audio :src => audio_filename2
305
- end
306
- }
307
- end
308
-
309
- it 'should playback each audio file using STREAM FILE' do
310
- latch = CountDownLatch.new 2
311
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename1, nil do
312
- subject.continue
313
- true
314
- latch.countdown!
315
- end
316
- mock_call.expects(:send_agi_action!).once.with 'STREAM FILE', audio_filename2, nil do
317
- subject.continue
318
- true
319
- latch.countdown!
320
- end
321
- subject.execute
322
- latch.wait 2
323
- sleep 2
324
- end
325
-
326
- it 'should send a complete event after the final file has finished playback' do
327
- def mock_call.send_agi_action!(*args, &block)
328
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
329
- end
330
- command.expects(:add_event).once.with do |e|
331
- e.reason.should be_a Punchblock::Component::Output::Complete::Success
332
- end
333
- subject.execute
334
- end
335
- end
336
- end
337
-
338
- describe 'start-offset' do
339
- context 'unset' do
340
- let(:command_opts) { { :start_offset => nil } }
341
- it 'should not pass any options to STREAM FILE' do
342
- expect_stream_file_with_options
343
- subject.execute
344
- end
345
- end
346
-
347
- context 'set' do
348
- let(:command_opts) { { :start_offset => 10 } }
349
- it "should return an error and not execute any actions" do
350
- subject.execute
351
- error = ProtocolError.new 'option error', 'A start_offset value is unsupported on Asterisk.'
352
- command.response(0.1).should == error
353
- end
354
- end
355
- end
356
-
357
- describe 'start-paused' do
358
- context 'false' do
359
- let(:command_opts) { { :start_paused => false } }
360
- it 'should not pass any options to STREAM FILE' do
361
- expect_stream_file_with_options
362
- subject.execute
363
- end
364
- end
365
-
366
- context 'true' do
367
- let(:command_opts) { { :start_paused => true } }
368
- it "should return an error and not execute any actions" do
369
- subject.execute
370
- error = ProtocolError.new 'option error', 'A start_paused value is unsupported on Asterisk.'
371
- command.response(0.1).should == error
372
- end
373
- end
374
- end
375
-
376
- describe 'repeat-interval' do
377
- context 'unset' do
378
- let(:command_opts) { { :repeat_interval => nil } }
379
- it 'should not pass any options to STREAM FILE' do
380
- expect_stream_file_with_options
381
- subject.execute
382
- end
383
- end
384
-
385
- context 'set' do
386
- let(:command_opts) { { :repeat_interval => 10 } }
387
- it "should return an error and not execute any actions" do
388
- subject.execute
389
- error = ProtocolError.new 'option error', 'A repeat_interval value is unsupported on Asterisk.'
390
- command.response(0.1).should == error
391
- end
392
- end
393
- end
394
-
395
- describe 'repeat-times' do
396
- context 'unset' do
397
- let(:command_opts) { { :repeat_times => nil } }
398
- it 'should not pass any options to STREAM FILE' do
399
- expect_stream_file_with_options
400
- subject.execute
401
- end
402
- end
403
-
404
- context 'set' do
405
- let(:command_opts) { { :repeat_times => 2 } }
406
- it "should return an error and not execute any actions" do
407
- subject.execute
408
- error = ProtocolError.new 'option error', 'A repeat_times value is unsupported on Asterisk.'
409
- command.response(0.1).should == error
410
- end
411
- end
412
- end
413
-
414
- describe 'max-time' do
415
- context 'unset' do
416
- let(:command_opts) { { :max_time => nil } }
417
- it 'should not pass any options to STREAM FILE' do
418
- expect_stream_file_with_options
419
- subject.execute
420
- end
421
- end
422
-
423
- context 'set' do
424
- let(:command_opts) { { :max_time => 30 } }
425
- it "should return an error and not execute any actions" do
426
- subject.execute
427
- error = ProtocolError.new 'option error', 'A max_time value is unsupported on Asterisk.'
428
- command.response(0.1).should == error
429
- end
430
- end
431
- end
432
-
433
- describe 'voice' do
434
- context 'unset' do
435
- let(:command_opts) { { :voice => nil } }
436
- it 'should not pass the v option to STREAM FILE' do
437
- expect_stream_file_with_options
438
- subject.execute
439
- end
440
- end
441
-
442
- context 'set' do
443
- let(:command_opts) { { :voice => 'alison' } }
444
- it "should return an error and not execute any actions" do
445
- subject.execute
446
- error = ProtocolError.new 'option error', 'A voice value is unsupported on Asterisk.'
447
- command.response(0.1).should == error
448
- end
449
- end
450
- end
451
-
452
- describe 'interrupt_on' do
453
- context "set to nil" do
454
- let(:command_opts) { { :interrupt_on => nil } }
455
- it "should not pass any digits to STREAM FILE" do
456
- expect_stream_file_with_options
457
- subject.execute
458
- end
459
- end
460
-
461
- context "set to :any" do
462
- let(:command_opts) { { :interrupt_on => :any } }
463
- it "should pass all digits to STREAM FILE" do
464
- expect_stream_file_with_options '0123456789*#'
465
- subject.execute
466
- end
467
- end
468
-
469
- context "set to :dtmf" do
470
- let(:command_opts) { { :interrupt_on => :dtmf } }
471
- it "should pass all digits to STREAM FILE" do
472
- expect_stream_file_with_options '0123456789*#'
473
- subject.execute
474
- end
475
- end
476
-
477
- context "set to :speech" do
478
- let(:command_opts) { { :interrupt_on => :speech } }
479
- it "should return an error and not execute any actions" do
480
- subject.execute
481
- error = ProtocolError.new 'option error', 'An interrupt-on value of speech is unsupported.'
482
- command.response(0.1).should == error
483
- end
484
- end
485
- end
486
- end
487
- end
488
- end
489
- end
490
- end
491
- end
492
- end
493
- end