adhearsion-asr 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +6 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +172 -0
- data/Rakefile +6 -0
- data/adhearsion_asr.gemspec +28 -0
- data/lib/adhearsion-asr/ask_grammar_builder.rb +38 -0
- data/lib/adhearsion-asr/controller_methods.rb +112 -0
- data/lib/adhearsion-asr/menu_builder.rb +122 -0
- data/lib/adhearsion-asr/plugin.rb +11 -0
- data/lib/adhearsion-asr/prompt_builder.rb +54 -0
- data/lib/adhearsion-asr/result.rb +11 -0
- data/lib/adhearsion-asr/version.rb +3 -0
- data/lib/adhearsion-asr.rb +7 -0
- data/spec/adhearsion-asr/controller_methods_spec.rb +989 -0
- data/spec/spec_helper.rb +13 -0
- metadata +163 -0
@@ -0,0 +1,989 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
module AdhearsionASR
|
6
|
+
describe ControllerMethods do
|
7
|
+
describe "mixed in to a CallController" do
|
8
|
+
|
9
|
+
let(:call_id) { SecureRandom.uuid }
|
10
|
+
let(:call) { Adhearsion::Call.new }
|
11
|
+
let(:block) { nil }
|
12
|
+
let(:controller) { Class.new(Adhearsion::CallController).new call }
|
13
|
+
|
14
|
+
subject { controller }
|
15
|
+
|
16
|
+
before do
|
17
|
+
mock call, write_command: true, id: call_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def expect_message_waiting_for_response(message, fail = false)
|
21
|
+
expectation = controller.should_receive(:write_and_await_response).with message
|
22
|
+
if fail
|
23
|
+
expectation.and_raise fail
|
24
|
+
else
|
25
|
+
expectation.and_return message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def expect_message_of_type_waiting_for_response(message)
|
30
|
+
controller.should_receive(:write_and_await_response).with(message.class).and_return message
|
31
|
+
end
|
32
|
+
|
33
|
+
def expect_component_execution(component, fail = false)
|
34
|
+
expectation = controller.should_receive(:execute_component_and_await_completion).ordered.with(component)
|
35
|
+
if fail
|
36
|
+
expectation.and_raise fail
|
37
|
+
else
|
38
|
+
expectation.and_return component
|
39
|
+
end
|
40
|
+
expectation
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.temp_config_value(key, value)
|
44
|
+
before do
|
45
|
+
@original_value = Plugin.config[key]
|
46
|
+
Plugin.config[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
after { Plugin.config[key] = @original_value }
|
50
|
+
end
|
51
|
+
|
52
|
+
before do
|
53
|
+
controller.extend AdhearsionASR::ControllerMethods
|
54
|
+
end
|
55
|
+
|
56
|
+
let(:prompts) { ['http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
|
57
|
+
|
58
|
+
let :expected_ssml do
|
59
|
+
RubySpeech::SSML.draw do
|
60
|
+
audio src: 'http://example.com/nice-to-meet-you.mp3'
|
61
|
+
audio src: 'http://example.com/press-some-buttons.mp3'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
let :expected_output_options do
|
66
|
+
{
|
67
|
+
render_document: {value: expected_ssml},
|
68
|
+
renderer: nil
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
let :expected_input_options do
|
73
|
+
{
|
74
|
+
mode: :dtmf,
|
75
|
+
initial_timeout: 5000,
|
76
|
+
inter_digit_timeout: 5000,
|
77
|
+
max_silence: 5000,
|
78
|
+
min_confidence: 0.5,
|
79
|
+
recognizer: nil,
|
80
|
+
language: 'en-US',
|
81
|
+
grammar: { value: expected_grxml }
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:expected_barge_in) { true }
|
86
|
+
|
87
|
+
let :expected_prompt do
|
88
|
+
Punchblock::Component::Prompt.new expected_output_options, expected_input_options, barge_in: expected_barge_in
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:reason) { Punchblock::Component::Input::Complete::NoMatch.new }
|
92
|
+
|
93
|
+
before { Punchblock::Component::Prompt.any_instance.stub complete_event: mock(reason: reason) }
|
94
|
+
|
95
|
+
describe "#ask" do
|
96
|
+
let :digit_limit_grammar do
|
97
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
98
|
+
rule id: 'digits', scope: 'public' do
|
99
|
+
item repeat: '0-5' do
|
100
|
+
one_of do
|
101
|
+
0.upto(9) { |d| item { d.to_s } }
|
102
|
+
item { "#" }
|
103
|
+
item { "*" }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "without a digit limit, terminator digit or grammar" do
|
111
|
+
it "raises ArgumentError" do
|
112
|
+
expect { subject.ask prompts }.to raise_error(ArgumentError, "You must specify at least one of limit, terminator or grammar")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with a digit limit" do
|
117
|
+
let(:expected_grxml) { digit_limit_grammar }
|
118
|
+
|
119
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
120
|
+
expect_component_execution expected_prompt
|
121
|
+
|
122
|
+
subject.ask prompts, limit: 5
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with a terminator" do
|
127
|
+
let :expected_grxml do
|
128
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
129
|
+
rule id: 'digits', scope: 'public' do
|
130
|
+
item repeat: '0-' do
|
131
|
+
one_of do
|
132
|
+
0.upto(9) { |d| item { d.to_s } }
|
133
|
+
item { "#" }
|
134
|
+
item { "*" }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
before do
|
142
|
+
expected_input_options.merge! terminator: '#'
|
143
|
+
end
|
144
|
+
|
145
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
146
|
+
expect_component_execution expected_prompt
|
147
|
+
|
148
|
+
subject.ask prompts, terminator: '#'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "with a digit limit and a terminator" do
|
153
|
+
let :expected_grxml do
|
154
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
155
|
+
rule id: 'digits', scope: 'public' do
|
156
|
+
item repeat: '0-5' do
|
157
|
+
one_of do
|
158
|
+
0.upto(9) { |d| item { d.to_s } }
|
159
|
+
item { "#" }
|
160
|
+
item { "*" }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
before do
|
168
|
+
expected_input_options.merge! grammar: { value: expected_grxml },
|
169
|
+
terminator: '#'
|
170
|
+
end
|
171
|
+
|
172
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
173
|
+
expect_component_execution expected_prompt
|
174
|
+
|
175
|
+
subject.ask prompts, limit: 5, terminator: '#'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "with an inline GRXML grammar specified" do
|
180
|
+
let :expected_grxml do
|
181
|
+
RubySpeech::GRXML.draw root: 'main', language: 'en-us', mode: :voice do
|
182
|
+
rule id: 'main', scope: 'public' do
|
183
|
+
one_of do
|
184
|
+
item { 'yes' }
|
185
|
+
item { 'no' }
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
before do
|
192
|
+
expected_input_options.merge! grammar: { value: expected_grxml }
|
193
|
+
end
|
194
|
+
|
195
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
196
|
+
expect_component_execution expected_prompt
|
197
|
+
|
198
|
+
subject.ask prompts, grammar: expected_grxml
|
199
|
+
end
|
200
|
+
|
201
|
+
context "with multiple grammars specified" do
|
202
|
+
let :other_expected_grxml do
|
203
|
+
RubySpeech::GRXML.draw root: 'main', mode: :dtmf do
|
204
|
+
rule id: 'main', scope: 'public' do
|
205
|
+
one_of do
|
206
|
+
item { 1 }
|
207
|
+
item { 2 }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
before do
|
214
|
+
expected_input_options.merge! grammars: [{ value: expected_grxml }, { value: other_expected_grxml }]
|
215
|
+
end
|
216
|
+
|
217
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
218
|
+
expect_component_execution expected_prompt
|
219
|
+
|
220
|
+
subject.ask prompts, grammar: [expected_grxml, other_expected_grxml]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "with a grammar URL specified" do
|
226
|
+
let(:expected_grxml) { digit_limit_grammar }
|
227
|
+
let(:grammar_url) { 'http://example.com/cities.grxml' }
|
228
|
+
|
229
|
+
before do
|
230
|
+
expected_input_options.merge! grammar: { url: grammar_url }
|
231
|
+
end
|
232
|
+
|
233
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
234
|
+
expect_component_execution expected_prompt
|
235
|
+
|
236
|
+
subject.ask prompts, grammar_url: grammar_url
|
237
|
+
end
|
238
|
+
|
239
|
+
context "with multiple grammar URLs specified" do
|
240
|
+
let(:other_grammar_url) { 'http://example.com/states.grxml' }
|
241
|
+
|
242
|
+
before do
|
243
|
+
expected_input_options.merge! grammars: [{ url: grammar_url }, { url: other_grammar_url }]
|
244
|
+
end
|
245
|
+
|
246
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
247
|
+
expect_component_execution expected_prompt
|
248
|
+
|
249
|
+
subject.ask prompts, grammar_url: [grammar_url, other_grammar_url]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context "with grammars specified inline and by URL" do
|
254
|
+
before do
|
255
|
+
expected_input_options.merge! grammars: [{ value: expected_grxml }, { url: grammar_url }]
|
256
|
+
end
|
257
|
+
|
258
|
+
it "executes a Prompt component with the correct prompts and grammar" do
|
259
|
+
expect_component_execution expected_prompt
|
260
|
+
|
261
|
+
subject.ask prompts, grammar: expected_grxml, grammar_url: [grammar_url]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "with interruptible: false" do
|
267
|
+
let(:expected_grxml) { digit_limit_grammar }
|
268
|
+
|
269
|
+
let(:expected_barge_in) { false }
|
270
|
+
|
271
|
+
it "executes a Prompt with barge-in disabled" do
|
272
|
+
expect_component_execution expected_prompt
|
273
|
+
|
274
|
+
subject.ask prompts, limit: 5, interruptible: false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context "with a timeout specified" do
|
279
|
+
let(:expected_grxml) { digit_limit_grammar }
|
280
|
+
|
281
|
+
before do
|
282
|
+
expected_input_options.merge! initial_timeout: 10000,
|
283
|
+
inter_digit_timeout: 10000,
|
284
|
+
max_silence: 10000
|
285
|
+
end
|
286
|
+
|
287
|
+
it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
|
288
|
+
expect_component_execution expected_prompt
|
289
|
+
|
290
|
+
subject.ask prompts, limit: 5, timeout: 10
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context "with a different default timeout" do
|
295
|
+
let(:expected_grxml) { digit_limit_grammar }
|
296
|
+
|
297
|
+
before do
|
298
|
+
expected_input_options.merge! initial_timeout: 10000,
|
299
|
+
inter_digit_timeout: 10000,
|
300
|
+
max_silence: 10000
|
301
|
+
end
|
302
|
+
|
303
|
+
temp_config_value :timeout, 10
|
304
|
+
|
305
|
+
it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
|
306
|
+
expect_component_execution expected_prompt
|
307
|
+
|
308
|
+
subject.ask prompts, limit: 5
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context "with a different default minimum confidence" do
|
313
|
+
let(:expected_grxml) { digit_limit_grammar }
|
314
|
+
|
315
|
+
before do
|
316
|
+
expected_input_options.merge! min_confidence: 0.8
|
317
|
+
end
|
318
|
+
|
319
|
+
temp_config_value :min_confidence, 0.8
|
320
|
+
|
321
|
+
it "executes a Prompt with correct minimum confidence" do
|
322
|
+
expect_component_execution expected_prompt
|
323
|
+
|
324
|
+
subject.ask prompts, limit: 5
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context "with a different default recognizer" do
|
329
|
+
let(:expected_grxml) { digit_limit_grammar }
|
330
|
+
|
331
|
+
before do
|
332
|
+
expected_input_options.merge! recognizer: 'something_else'
|
333
|
+
end
|
334
|
+
|
335
|
+
temp_config_value :recognizer, 'something_else'
|
336
|
+
|
337
|
+
it "executes a Prompt with correct recognizer" do
|
338
|
+
expect_component_execution expected_prompt
|
339
|
+
|
340
|
+
subject.ask prompts, limit: 5
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context "with a different default input language" do
|
345
|
+
let(:expected_grxml) { digit_limit_grammar }
|
346
|
+
|
347
|
+
before do
|
348
|
+
expected_input_options.merge! language: 'pt-BR'
|
349
|
+
end
|
350
|
+
|
351
|
+
temp_config_value :input_language, 'pt-BR'
|
352
|
+
|
353
|
+
it "executes a Prompt with correct input language" do
|
354
|
+
expect_component_execution expected_prompt
|
355
|
+
|
356
|
+
subject.ask prompts, limit: 5
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
context "with a different default output renderer" do
|
361
|
+
let(:expected_grxml) { digit_limit_grammar }
|
362
|
+
|
363
|
+
before do
|
364
|
+
expected_output_options.merge! renderer: 'something_else'
|
365
|
+
end
|
366
|
+
|
367
|
+
temp_config_value :renderer, 'something_else'
|
368
|
+
|
369
|
+
it "executes a Prompt with correct renderer" do
|
370
|
+
expect_component_execution expected_prompt
|
371
|
+
|
372
|
+
subject.ask prompts, limit: 5
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
context "with overridden input options" do
|
377
|
+
let(:expected_grxml) { digit_limit_grammar }
|
378
|
+
|
379
|
+
before do
|
380
|
+
expected_input_options.merge! inter_digit_timeout: 35000
|
381
|
+
end
|
382
|
+
|
383
|
+
it "executes a Prompt with correct input options" do
|
384
|
+
expect_component_execution expected_prompt
|
385
|
+
|
386
|
+
subject.ask prompts, limit: 5, input_options: {inter_digit_timeout: 35000}
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
context "with overridden output options" do
|
391
|
+
let(:expected_grxml) { digit_limit_grammar }
|
392
|
+
|
393
|
+
before do
|
394
|
+
expected_output_options.merge! max_time: 35000
|
395
|
+
end
|
396
|
+
|
397
|
+
it "executes a Prompt with correct output options" do
|
398
|
+
expect_component_execution expected_prompt
|
399
|
+
|
400
|
+
subject.ask prompts, limit: 5, output_options: {max_time: 35000}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
context "when a response is received" do
|
405
|
+
let(:expected_grxml) { digit_limit_grammar }
|
406
|
+
|
407
|
+
context "that is a match" do
|
408
|
+
let :nlsml do
|
409
|
+
RubySpeech::NLSML.draw do
|
410
|
+
interpretation confidence: 1 do
|
411
|
+
input '123', mode: :dtmf
|
412
|
+
instance 'Foo'
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
let(:reason) { Punchblock::Component::Input::Complete::Match.new nlsml: nlsml }
|
418
|
+
|
419
|
+
it "returns :match status and the response" do
|
420
|
+
expect_component_execution expected_prompt
|
421
|
+
|
422
|
+
result = subject.ask prompts, limit: 5
|
423
|
+
result.status.should be :match
|
424
|
+
result.confidence.should == 1
|
425
|
+
result.response.should == '123'
|
426
|
+
result.interpretation.should == 'Foo'
|
427
|
+
result.nlsml.should == nlsml
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
context "that is a nomatch" do
|
432
|
+
let(:reason) { Punchblock::Component::Input::Complete::NoMatch.new }
|
433
|
+
|
434
|
+
it "returns :nomatch status and a nil response" do
|
435
|
+
expect_component_execution expected_prompt
|
436
|
+
|
437
|
+
result = subject.ask prompts, limit: 5
|
438
|
+
result.status.should be :nomatch
|
439
|
+
result.response.should be_nil
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context "that is a noinput" do
|
444
|
+
let(:reason) { Punchblock::Component::Input::Complete::NoInput.new }
|
445
|
+
|
446
|
+
it "returns :noinput status and a nil response" do
|
447
|
+
expect_component_execution expected_prompt
|
448
|
+
|
449
|
+
result = subject.ask prompts, limit: 5
|
450
|
+
result.status.should be :noinput
|
451
|
+
result.response.should be_nil
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context "that is an error" do
|
456
|
+
let(:reason) { Punchblock::Event::Complete::Error.new details: 'foobar' }
|
457
|
+
|
458
|
+
it "should raise an error with a message of 'foobar" do
|
459
|
+
expect_component_execution expected_prompt
|
460
|
+
|
461
|
+
expect { subject.ask prompts, limit: 5 }.to raise_error(AdhearsionASR::Error, /foobar/)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
describe "#menu" do
|
468
|
+
context "with no block" do
|
469
|
+
it "should raise ArgumentError" do
|
470
|
+
expect { subject.menu }.to raise_error(ArgumentError, /specify a block to build the menu/)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
context "with no matches" do
|
475
|
+
it "should raise ArgumentError" do
|
476
|
+
expect do
|
477
|
+
subject.menu do
|
478
|
+
end
|
479
|
+
end.to raise_error(ArgumentError, /specify one or more matches/)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context "with several matches specified" do
|
484
|
+
let :expected_grxml do
|
485
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options' do
|
486
|
+
rule id: 'options', scope: 'public' do
|
487
|
+
item do
|
488
|
+
one_of do
|
489
|
+
item do
|
490
|
+
tag { '0' }
|
491
|
+
'1'
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context "with interruptible: false" do
|
500
|
+
let(:expected_barge_in) { false }
|
501
|
+
|
502
|
+
it "executes a Prompt with barge-in disabled" do
|
503
|
+
expect_component_execution expected_prompt
|
504
|
+
|
505
|
+
subject.menu prompts, interruptible: false do
|
506
|
+
match(1) {}
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
context "with a timeout specified" do
|
512
|
+
before do
|
513
|
+
expected_input_options.merge! initial_timeout: 10000,
|
514
|
+
inter_digit_timeout: 10000,
|
515
|
+
max_silence: 10000
|
516
|
+
end
|
517
|
+
|
518
|
+
it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
|
519
|
+
expect_component_execution expected_prompt
|
520
|
+
|
521
|
+
subject.menu prompts, timeout: 10 do
|
522
|
+
match(1) {}
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
context "with a different default timeout" do
|
528
|
+
before do
|
529
|
+
expected_input_options.merge! initial_timeout: 10000,
|
530
|
+
inter_digit_timeout: 10000,
|
531
|
+
max_silence: 10000
|
532
|
+
end
|
533
|
+
|
534
|
+
temp_config_value :timeout, 10
|
535
|
+
|
536
|
+
it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
|
537
|
+
expect_component_execution expected_prompt
|
538
|
+
|
539
|
+
subject.menu prompts do
|
540
|
+
match(1) {}
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
context "with a different default minimum confidence" do
|
546
|
+
before do
|
547
|
+
expected_input_options.merge! min_confidence: 0.8
|
548
|
+
end
|
549
|
+
|
550
|
+
temp_config_value :min_confidence, 0.8
|
551
|
+
|
552
|
+
it "executes a Prompt with correct minimum confidence" do
|
553
|
+
expect_component_execution expected_prompt
|
554
|
+
|
555
|
+
subject.menu prompts do
|
556
|
+
match(1) {}
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
context "with a different default recognizer" do
|
562
|
+
before do
|
563
|
+
expected_input_options.merge! recognizer: 'something_else'
|
564
|
+
end
|
565
|
+
|
566
|
+
temp_config_value :recognizer, 'something_else'
|
567
|
+
|
568
|
+
it "executes a Prompt with correct recognizer" do
|
569
|
+
expect_component_execution expected_prompt
|
570
|
+
|
571
|
+
subject.menu prompts do
|
572
|
+
match(1) {}
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
context "with a different default input language" do
|
578
|
+
before do
|
579
|
+
expected_input_options.merge! language: 'pt-BR'
|
580
|
+
end
|
581
|
+
|
582
|
+
temp_config_value :input_language, 'pt-BR'
|
583
|
+
|
584
|
+
it "executes a Prompt with correct input language" do
|
585
|
+
expect_component_execution expected_prompt
|
586
|
+
|
587
|
+
subject.menu prompts do
|
588
|
+
match(1) {}
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
context "with a different default output renderer" do
|
594
|
+
before do
|
595
|
+
expected_output_options.merge! renderer: 'something_else'
|
596
|
+
end
|
597
|
+
|
598
|
+
temp_config_value :renderer, 'something_else'
|
599
|
+
|
600
|
+
it "executes a Prompt with correct renderer" do
|
601
|
+
expect_component_execution expected_prompt
|
602
|
+
|
603
|
+
subject.menu prompts do
|
604
|
+
match(1) {}
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
context "with overridden input options" do
|
610
|
+
before do
|
611
|
+
expected_input_options.merge! inter_digit_timeout: 35000
|
612
|
+
end
|
613
|
+
|
614
|
+
it "executes a Prompt with correct input options" do
|
615
|
+
expect_component_execution expected_prompt
|
616
|
+
|
617
|
+
subject.menu prompts, input_options: {inter_digit_timeout: 35000} do
|
618
|
+
match(1) {}
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
context "with overridden output options" do
|
624
|
+
before do
|
625
|
+
expected_output_options.merge! max_time: 35000
|
626
|
+
end
|
627
|
+
|
628
|
+
it "executes a Prompt with correct output options" do
|
629
|
+
expect_component_execution expected_prompt
|
630
|
+
|
631
|
+
subject.menu prompts, output_options: {max_time: 35000} do
|
632
|
+
match(1) {}
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
context "when input completes with an error" do
|
638
|
+
let(:reason) { Punchblock::Event::Complete::Error.new details: 'foobar' }
|
639
|
+
|
640
|
+
it "should raise an error with a message of 'foobar'" do
|
641
|
+
expect_component_execution expected_prompt
|
642
|
+
|
643
|
+
expect do
|
644
|
+
subject.menu prompts do
|
645
|
+
match(1) {}
|
646
|
+
end
|
647
|
+
end.to raise_error(AdhearsionASR::Error, /foobar/)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
context "when input doesn't match any of the specified matches" do
|
652
|
+
it "runs the invalid and failure handlers" do
|
653
|
+
expect_component_execution expected_prompt
|
654
|
+
should_receive(:do_something_on_invalid).once.ordered
|
655
|
+
should_receive(:do_something_on_failure).once.ordered
|
656
|
+
|
657
|
+
subject.menu prompts do
|
658
|
+
match(1) {}
|
659
|
+
|
660
|
+
invalid { do_something_on_invalid }
|
661
|
+
failure { do_something_on_failure }
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
context "when allowed multiple tries" do
|
666
|
+
let :nlsml do
|
667
|
+
RubySpeech::NLSML.draw do
|
668
|
+
interpretation confidence: 1 do
|
669
|
+
input '1', mode: :dtmf
|
670
|
+
instance '0'
|
671
|
+
end
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
let(:reason2) { Punchblock::Component::Input::Complete::Match.new nlsml: nlsml }
|
676
|
+
|
677
|
+
it "executes the prompt repeatedly until it gets a match" do
|
678
|
+
some_controller_class = Class.new Adhearsion::CallController
|
679
|
+
|
680
|
+
expect_component_execution(expected_prompt).twice
|
681
|
+
should_receive(:do_something_on_invalid).once.ordered
|
682
|
+
should_receive(:invoke).once.with(some_controller_class, extension: '1').ordered
|
683
|
+
should_receive(:do_something_on_failure).never
|
684
|
+
|
685
|
+
invocation_count = 0
|
686
|
+
Punchblock::Component::Prompt.any_instance.stub(:complete_event) do
|
687
|
+
invocation_count += 1
|
688
|
+
case invocation_count
|
689
|
+
when 1 then mock(reason: reason)
|
690
|
+
when 2 then mock(reason: reason2)
|
691
|
+
else raise('Too many attempts')
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
subject.menu prompts, tries: 3 do
|
696
|
+
match 1, some_controller_class
|
697
|
+
|
698
|
+
invalid { do_something_on_invalid }
|
699
|
+
failure { do_something_on_failure }
|
700
|
+
end
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|
704
|
+
|
705
|
+
context "when we don't get any input" do
|
706
|
+
let(:reason) { Punchblock::Component::Input::Complete::NoInput.new }
|
707
|
+
|
708
|
+
it "runs the timeout and failure handlers" do
|
709
|
+
expect_component_execution expected_prompt
|
710
|
+
should_receive(:do_something_on_timeout).once.ordered
|
711
|
+
should_receive(:do_something_on_failure).once.ordered
|
712
|
+
|
713
|
+
subject.menu prompts do
|
714
|
+
match(1) {}
|
715
|
+
|
716
|
+
timeout { do_something_on_timeout }
|
717
|
+
failure { do_something_on_failure }
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
context "when allowed multiple tries" do
|
722
|
+
let :nlsml do
|
723
|
+
RubySpeech::NLSML.draw do
|
724
|
+
interpretation confidence: 1 do
|
725
|
+
input '1', mode: :dtmf
|
726
|
+
instance '0'
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
let(:reason2) { Punchblock::Component::Input::Complete::Match.new nlsml: nlsml }
|
732
|
+
|
733
|
+
it "executes the prompt repeatedly until it gets a match" do
|
734
|
+
some_controller_class = Class.new Adhearsion::CallController
|
735
|
+
|
736
|
+
expect_component_execution(expected_prompt).twice
|
737
|
+
should_receive(:do_something_on_timeout).once.ordered
|
738
|
+
should_receive(:invoke).once.with(some_controller_class, extension: '1').ordered
|
739
|
+
should_receive(:do_something_on_failure).never
|
740
|
+
|
741
|
+
invocation_count = 0
|
742
|
+
Punchblock::Component::Prompt.any_instance.stub(:complete_event) do
|
743
|
+
invocation_count += 1
|
744
|
+
case invocation_count
|
745
|
+
when 1 then mock(reason: reason)
|
746
|
+
when 2 then mock(reason: reason2)
|
747
|
+
else raise('Too many attempts')
|
748
|
+
end
|
749
|
+
end
|
750
|
+
|
751
|
+
subject.menu prompts, tries: 3 do
|
752
|
+
match 1, some_controller_class
|
753
|
+
|
754
|
+
timeout { do_something_on_timeout }
|
755
|
+
failure { do_something_on_failure }
|
756
|
+
end
|
757
|
+
end
|
758
|
+
end
|
759
|
+
end
|
760
|
+
|
761
|
+
context "when the input unambiguously matches a specified match" do
|
762
|
+
let :expected_grxml do
|
763
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
|
764
|
+
rule id: 'options', scope: 'public' do
|
765
|
+
item do
|
766
|
+
one_of do
|
767
|
+
item do
|
768
|
+
tag { '0' }
|
769
|
+
'2'
|
770
|
+
end
|
771
|
+
item do
|
772
|
+
tag { '1' }
|
773
|
+
'1'
|
774
|
+
end
|
775
|
+
item do
|
776
|
+
tag { '2' }
|
777
|
+
'3'
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
let :nlsml do
|
786
|
+
RubySpeech::NLSML.draw do
|
787
|
+
interpretation confidence: 1 do
|
788
|
+
input '3', mode: :dtmf
|
789
|
+
instance '2'
|
790
|
+
end
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
let(:reason) { Punchblock::Component::Input::Complete::Match.new nlsml: nlsml }
|
795
|
+
|
796
|
+
context "which specifies a controller class" do
|
797
|
+
it "invokes the specfied controller, with the matched input as the :extension key in its metadata" do
|
798
|
+
some_controller_class = Class.new Adhearsion::CallController
|
799
|
+
|
800
|
+
expect_component_execution expected_prompt
|
801
|
+
should_receive(:invoke).once.with(some_controller_class, extension: '3')
|
802
|
+
|
803
|
+
subject.menu prompts do
|
804
|
+
match(2) {}
|
805
|
+
match(1) {}
|
806
|
+
match 3, some_controller_class
|
807
|
+
end
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
811
|
+
context "which specifies a block to be run" do
|
812
|
+
it "invokes the block, passing in the input that matched" do
|
813
|
+
expect_component_execution expected_prompt
|
814
|
+
should_receive(:do_something_on_match).once.with('3')
|
815
|
+
|
816
|
+
subject.menu prompts do
|
817
|
+
match(2) {}
|
818
|
+
match(1) {}
|
819
|
+
match(3) { |v| do_something_on_match v }
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
context "when the match was a set of options" do
|
825
|
+
let :expected_grxml do
|
826
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
|
827
|
+
rule id: 'options', scope: 'public' do
|
828
|
+
item do
|
829
|
+
one_of do
|
830
|
+
item do
|
831
|
+
tag { '0' }
|
832
|
+
'0'
|
833
|
+
end
|
834
|
+
item do
|
835
|
+
tag { '1' }
|
836
|
+
'1'
|
837
|
+
end
|
838
|
+
item do
|
839
|
+
tag { '2' }
|
840
|
+
one_of do
|
841
|
+
item { '2' }
|
842
|
+
item { '3' }
|
843
|
+
end
|
844
|
+
end
|
845
|
+
end
|
846
|
+
end
|
847
|
+
end
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
it "invokes the match payload" do
|
852
|
+
expect_component_execution expected_prompt
|
853
|
+
should_receive(:do_something_on_match).once.with('3')
|
854
|
+
|
855
|
+
subject.menu prompts do
|
856
|
+
match(0) {}
|
857
|
+
match(1) {}
|
858
|
+
match(2,3) { |v| do_something_on_match v }
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
context "when the match was a range" do
|
864
|
+
let :expected_grxml do
|
865
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
|
866
|
+
rule id: 'options', scope: 'public' do
|
867
|
+
item do
|
868
|
+
one_of do
|
869
|
+
item do
|
870
|
+
tag { '0' }
|
871
|
+
'0'
|
872
|
+
end
|
873
|
+
item do
|
874
|
+
tag { '1' }
|
875
|
+
'1'
|
876
|
+
end
|
877
|
+
item do
|
878
|
+
tag { '2' }
|
879
|
+
one_of do
|
880
|
+
item { '2' }
|
881
|
+
item { '3' }
|
882
|
+
end
|
883
|
+
end
|
884
|
+
end
|
885
|
+
end
|
886
|
+
end
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
it "invokes the match payload" do
|
891
|
+
expect_component_execution expected_prompt
|
892
|
+
should_receive(:do_something_on_match).once.with('3')
|
893
|
+
|
894
|
+
subject.menu prompts do
|
895
|
+
match(0) {}
|
896
|
+
match(1) {}
|
897
|
+
match(2..3) { |v| do_something_on_match v }
|
898
|
+
end
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
context "when the match was an array of options" do
|
903
|
+
let :expected_grxml do
|
904
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
|
905
|
+
rule id: 'options', scope: 'public' do
|
906
|
+
item do
|
907
|
+
one_of do
|
908
|
+
item do
|
909
|
+
tag { '0' }
|
910
|
+
'0'
|
911
|
+
end
|
912
|
+
item do
|
913
|
+
tag { '1' }
|
914
|
+
'1'
|
915
|
+
end
|
916
|
+
item do
|
917
|
+
tag { '2' }
|
918
|
+
one_of do
|
919
|
+
item { '2' }
|
920
|
+
item { '3' }
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
end
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
it "invokes the match payload" do
|
930
|
+
expect_component_execution expected_prompt
|
931
|
+
should_receive(:do_something_on_match).once.with('3')
|
932
|
+
|
933
|
+
subject.menu prompts do
|
934
|
+
match(0) {}
|
935
|
+
match(1) {}
|
936
|
+
match([2,3]) { |v| do_something_on_match v }
|
937
|
+
end
|
938
|
+
end
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
context "when the input abmiguously matches multiple specified matches" do
|
943
|
+
let :expected_grxml do
|
944
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
|
945
|
+
rule id: 'options', scope: 'public' do
|
946
|
+
item do
|
947
|
+
one_of do
|
948
|
+
item do
|
949
|
+
tag { '0' }
|
950
|
+
'1'
|
951
|
+
end
|
952
|
+
item do
|
953
|
+
tag { '1' }
|
954
|
+
'1'
|
955
|
+
end
|
956
|
+
end
|
957
|
+
end
|
958
|
+
end
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
let :nlsml do
|
963
|
+
RubySpeech::NLSML.draw do
|
964
|
+
interpretation confidence: 1 do
|
965
|
+
input '1', mode: :dtmf
|
966
|
+
instance '0'
|
967
|
+
instance '1'
|
968
|
+
end
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
let(:reason) { Punchblock::Component::Input::Complete::Match.new nlsml: nlsml }
|
973
|
+
|
974
|
+
it "executes the first successful match" do
|
975
|
+
expect_component_execution expected_prompt
|
976
|
+
should_receive(:do_something_on_match).once.with('1')
|
977
|
+
should_receive(:do_otherthing_on_match).never
|
978
|
+
|
979
|
+
subject.menu prompts do
|
980
|
+
match(1) { |v| do_something_on_match v }
|
981
|
+
match(1) { |v| do_otherthing_on_match v }
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
end
|
986
|
+
end
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|