adhearsion-asr 0.1.0

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