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.
@@ -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