punchblock 1.9.4 → 2.0.0.beta1

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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +4 -0
  7. data/README.markdown +6 -0
  8. data/Rakefile +16 -0
  9. data/benchmarks/ami_event_name_comparison.rb +14 -0
  10. data/benchmarks/channel.rb +27 -0
  11. data/lib/punchblock/client.rb +2 -6
  12. data/lib/punchblock/command/accept.rb +3 -24
  13. data/lib/punchblock/command/answer.rb +3 -24
  14. data/lib/punchblock/command/dial.rb +24 -76
  15. data/lib/punchblock/command/hangup.rb +3 -19
  16. data/lib/punchblock/command/join.rb +21 -70
  17. data/lib/punchblock/command/mute.rb +3 -3
  18. data/lib/punchblock/command/redirect.rb +6 -39
  19. data/lib/punchblock/command/reject.rb +14 -54
  20. data/lib/punchblock/command/unjoin.rb +8 -40
  21. data/lib/punchblock/command/unmute.rb +3 -3
  22. data/lib/punchblock/command_node.rb +0 -17
  23. data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
  24. data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
  25. data/lib/punchblock/component/component_node.rb +1 -1
  26. data/lib/punchblock/component/input.rb +89 -268
  27. data/lib/punchblock/component/output.rb +106 -154
  28. data/lib/punchblock/component/prompt.rb +51 -0
  29. data/lib/punchblock/component/record.rb +41 -130
  30. data/lib/punchblock/component.rb +1 -0
  31. data/lib/punchblock/connection/asterisk.rb +31 -4
  32. data/lib/punchblock/connection/xmpp.rb +6 -14
  33. data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
  34. data/lib/punchblock/event/active_speaker.rb +2 -10
  35. data/lib/punchblock/event/answered.rb +3 -3
  36. data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
  37. data/lib/punchblock/event/complete.rb +26 -48
  38. data/lib/punchblock/event/dtmf.rb +3 -13
  39. data/lib/punchblock/event/end.rb +10 -11
  40. data/lib/punchblock/event/joined.rb +5 -25
  41. data/lib/punchblock/event/offer.rb +4 -25
  42. data/lib/punchblock/event/ringing.rb +3 -3
  43. data/lib/punchblock/event/unjoined.rb +5 -25
  44. data/lib/punchblock/event.rb +0 -10
  45. data/lib/punchblock/has_headers.rb +20 -26
  46. data/lib/punchblock/rayo_node.rb +46 -23
  47. data/lib/punchblock/ref.rb +39 -18
  48. data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
  49. data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
  50. data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
  51. data/lib/punchblock/translator/asterisk/call.rb +60 -39
  52. data/lib/punchblock/translator/asterisk/channel.rb +41 -0
  53. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
  54. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
  55. data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
  56. data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
  57. data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
  58. data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
  59. data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
  60. data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
  61. data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
  62. data/lib/punchblock/translator/asterisk/component.rb +6 -5
  63. data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
  64. data/lib/punchblock/translator/asterisk.rb +24 -28
  65. data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
  66. data/lib/punchblock/translator/freeswitch/call.rb +15 -14
  67. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
  68. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
  69. data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
  70. data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
  71. data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
  72. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
  73. data/lib/punchblock/translator/freeswitch/component.rb +2 -5
  74. data/lib/punchblock/translator/freeswitch.rb +2 -2
  75. data/lib/punchblock/translator/input_component.rb +33 -13
  76. data/lib/punchblock/uri_list.rb +21 -0
  77. data/lib/punchblock/version.rb +1 -1
  78. data/lib/punchblock.rb +4 -3
  79. data/punchblock.gemspec +7 -3
  80. data/spec/punchblock/client/component_registry_spec.rb +1 -1
  81. data/spec/punchblock/client_spec.rb +10 -26
  82. data/spec/punchblock/command/accept_spec.rb +41 -7
  83. data/spec/punchblock/command/answer_spec.rb +51 -7
  84. data/spec/punchblock/command/dial_spec.rb +56 -14
  85. data/spec/punchblock/command/hangup_spec.rb +41 -7
  86. data/spec/punchblock/command/join_spec.rb +53 -11
  87. data/spec/punchblock/command/mute_spec.rb +19 -4
  88. data/spec/punchblock/command/redirect_spec.rb +40 -10
  89. data/spec/punchblock/command/reject_spec.rb +43 -11
  90. data/spec/punchblock/command/unjoin_spec.rb +40 -9
  91. data/spec/punchblock/command/unmute_spec.rb +19 -4
  92. data/spec/punchblock/command_node_spec.rb +0 -4
  93. data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
  94. data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
  95. data/spec/punchblock/component/component_node_spec.rb +3 -5
  96. data/spec/punchblock/component/input_spec.rb +194 -61
  97. data/spec/punchblock/component/output_spec.rb +194 -62
  98. data/spec/punchblock/component/prompt_spec.rb +132 -0
  99. data/spec/punchblock/component/record_spec.rb +70 -32
  100. data/spec/punchblock/connection/asterisk_spec.rb +17 -3
  101. data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
  102. data/spec/punchblock/connection/xmpp_spec.rb +20 -38
  103. data/spec/punchblock/event/answered_spec.rb +12 -10
  104. data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
  105. data/spec/punchblock/event/complete_spec.rb +15 -19
  106. data/spec/punchblock/event/dtmf_spec.rb +5 -6
  107. data/spec/punchblock/event/end_spec.rb +20 -10
  108. data/spec/punchblock/event/joined_spec.rb +8 -7
  109. data/spec/punchblock/event/offer_spec.rb +41 -12
  110. data/spec/punchblock/event/ringing_spec.rb +12 -10
  111. data/spec/punchblock/event/started_speaking_spec.rb +5 -6
  112. data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
  113. data/spec/punchblock/event/unjoined_spec.rb +7 -7
  114. data/spec/punchblock/ref_spec.rb +86 -9
  115. data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
  116. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
  117. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
  118. data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
  119. data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
  120. data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
  121. data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
  122. data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
  123. data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
  124. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  125. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
  126. data/spec/punchblock/translator/asterisk_spec.rb +20 -24
  127. data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
  128. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
  129. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
  130. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
  131. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
  132. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
  133. data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
  134. data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
  135. data/spec/punchblock/uri_list_spec.rb +49 -0
  136. data/spec/punchblock_spec.rb +11 -1
  137. data/spec/spec_helper.rb +7 -11
  138. data/spec/support/mock_connection_with_event_handler.rb +1 -1
  139. metadata +104 -24
  140. data/lib/punchblock/header.rb +0 -9
  141. data/lib/punchblock/key_value_pair_node.rb +0 -51
  142. data/spec/punchblock/header_spec.rb +0 -11
@@ -0,0 +1,652 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ describe MRCPNativePrompt do
10
+ include HasMockCallbackConnection
11
+
12
+ let(:media_engine) { :unimrcp }
13
+ let(:ami_client) { double('AMI') }
14
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
15
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
16
+
17
+ let :ssml_doc do
18
+ RubySpeech::SSML.draw do
19
+ say_as(:interpret_as => :cardinal) { 'FOO' }
20
+ end
21
+ end
22
+
23
+ let :voice_grammar do
24
+ RubySpeech::GRXML.draw :mode => 'voice', :root => 'color' do
25
+ rule id: 'color' do
26
+ one_of do
27
+ item { 'red' }
28
+ item { 'blue' }
29
+ item { 'green' }
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ let :dtmf_grammar do
36
+ RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'pin' do
37
+ rule id: 'digit' do
38
+ one_of do
39
+ 0.upto(9) { |d| item { d.to_s } }
40
+ end
41
+ end
42
+
43
+ rule id: 'pin', scope: 'public' do
44
+ item repeat: '2' do
45
+ ruleref uri: '#digit'
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ let(:grammar) { voice_grammar }
52
+
53
+ let(:output_command_opts) { {} }
54
+
55
+ let(:audio_filename) { 'http://example.com/hello.mp3' }
56
+
57
+ let :output_command_options do
58
+ { render_document: {value: [audio_filename], content_type: 'text/uri-list'} }.merge(output_command_opts)
59
+ end
60
+
61
+ let(:input_command_opts) { {} }
62
+
63
+ let :input_command_options do
64
+ { grammar: {value: grammar} }.merge(input_command_opts)
65
+ end
66
+
67
+ let(:command_options) { {} }
68
+
69
+ let :output_command do
70
+ Punchblock::Component::Output.new output_command_options
71
+ end
72
+
73
+ let :input_command do
74
+ Punchblock::Component::Input.new input_command_options
75
+ end
76
+
77
+ let :original_command do
78
+ Punchblock::Component::Prompt.new output_command, input_command, command_options
79
+ end
80
+
81
+ let(:recog_status) { 'OK' }
82
+ let(:recog_completion_cause) { '000' }
83
+ let(:recog_result) { "%3C?xml%20version=%221.0%22?%3E%3Cresult%3E%0D%0A%3Cinterpretation%20grammar=%22session:grammar-0%22%20confidence=%220.43%22%3E%3Cinput%20mode=%22speech%22%3EHello%3C/input%3E%3Cinstance%3EHello%3C/instance%3E%3C/interpretation%3E%3C/result%3E" }
84
+
85
+ subject { described_class.new original_command, mock_call }
86
+
87
+ before do
88
+ original_command.request!
89
+ {
90
+ 'RECOG_STATUS' => recog_status,
91
+ 'RECOG_COMPLETION_CAUSE' => recog_completion_cause,
92
+ 'RECOG_RESULT' => recog_result
93
+ }.each do |var, val|
94
+ mock_call.stub(:channel_var).with(var).and_return val
95
+ end
96
+ end
97
+
98
+ context 'with an invalid recognizer' do
99
+ let(:input_command_opts) { { recognizer: 'foobar' } }
100
+
101
+ it "should return an error and not execute any actions" do
102
+ subject.execute
103
+ error = ProtocolError.new.setup 'option error', 'The recognizer foobar is unsupported.'
104
+ original_command.response(0.1).should be == error
105
+ end
106
+ end
107
+
108
+ [:asterisk].each do |recognizer|
109
+ context "with a recognizer #{recognizer.inspect}" do
110
+ let(:input_command_opts) { { recognizer: recognizer } }
111
+
112
+ it "should return an error and not execute any actions" do
113
+ subject.execute
114
+ error = ProtocolError.new.setup 'option error', "The recognizer #{recognizer} is unsupported."
115
+ original_command.response(0.1).should be == error
116
+ end
117
+ end
118
+ end
119
+
120
+ def expect_mrcprecog_with_options(options)
121
+ expect_app_with_options 'MRCPRecog', options
122
+ end
123
+
124
+ def expect_app_with_options(app, options)
125
+ mock_call.should_receive(:execute_agi_command).once.with do |*args|
126
+ args[0].should be == "EXEC #{app}"
127
+ args[1].should match options
128
+ end.and_return code: 200, result: 1
129
+ end
130
+
131
+ describe 'Output#document' do
132
+ context 'with multiple inline documents' do
133
+ let(:output_command_options) { { render_documents: [{value: ssml_doc}, {value: ssml_doc}] } }
134
+
135
+ it "should return an error and not execute any actions" do
136
+ subject.execute
137
+ error = ProtocolError.new.setup 'option error', 'Only one document is allowed.'
138
+ original_command.response(0.1).should be == error
139
+ end
140
+ end
141
+
142
+ context 'with a document by URI' do
143
+ let(:output_command_options) { { render_documents: [{url: 'http://example.com/doc1.ssml'}] } }
144
+
145
+ it "should return an error and not execute any actions" do
146
+ subject.execute
147
+ error = ProtocolError.new.setup 'option error', 'Only inline documents are allowed.'
148
+ original_command.response(0.1).should be == error
149
+ end
150
+ end
151
+
152
+ context 'with a urilist > size 1' do
153
+ let(:output_command_options) { { render_documents: [{content_type: 'text/uri-list', value: ['http://example.com/hello.mp3', 'http://example.com/goodbye.mp3']}] } }
154
+
155
+ it "should return an error and not execute any actions" do
156
+ subject.execute
157
+ error = ProtocolError.new.setup 'option error', 'Only one audio file is allowed.'
158
+ original_command.response(0.1).should be == error
159
+ end
160
+ end
161
+
162
+ context 'unset' do
163
+ let(:output_command_options) { {} }
164
+
165
+ it "should return an error and not execute any actions" do
166
+ subject.execute
167
+ error = ProtocolError.new.setup 'option error', 'A document is required.'
168
+ original_command.response(0.1).should be == error
169
+ end
170
+ end
171
+ end
172
+
173
+ describe 'Output#renderer' do
174
+ [nil, :asterisk].each do |renderer|
175
+ context renderer.to_s do
176
+ let(:output_command_opts) { { renderer: renderer } }
177
+
178
+ it "should return a ref and execute MRCPRecog" do
179
+ param = ["\"#{grammar.to_doc.to_s.squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
180
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
181
+ subject.execute
182
+ original_command.response(0.1).should be_a Ref
183
+ end
184
+
185
+ context "when MRCPRecog completes" do
186
+ context "with a match" do
187
+ let :expected_nlsml do
188
+ RubySpeech::NLSML.draw do
189
+ interpretation grammar: 'session:grammar-0', confidence: 0.43 do
190
+ input 'Hello', mode: :speech
191
+ instance 'Hello'
192
+ end
193
+ end.root
194
+ end
195
+
196
+ it 'should send a match complete event' do
197
+ expected_complete_reason = Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
198
+
199
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
200
+ subject.execute
201
+ original_command.complete_event(0.1).reason.should == expected_complete_reason
202
+ end
203
+ end
204
+
205
+ context "with a nomatch cause" do
206
+ let(:recog_completion_cause) { '001' }
207
+
208
+ it 'should send a nomatch complete event' do
209
+ expected_complete_reason = Punchblock::Component::Input::Complete::NoMatch.new
210
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
211
+ subject.execute
212
+ original_command.complete_event(0.1).reason.should == expected_complete_reason
213
+ end
214
+ end
215
+
216
+ context "with a noinput cause" do
217
+ let(:recog_completion_cause) { '002' }
218
+
219
+ it 'should send a nomatch complete event' do
220
+ expected_complete_reason = Punchblock::Component::Input::Complete::NoInput.new
221
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
222
+ subject.execute
223
+ original_command.complete_event(0.1).reason.should == expected_complete_reason
224
+ end
225
+ end
226
+
227
+ context "when the RECOG_STATUS variable is set to 'ERROR'" do
228
+ let(:recog_status) { 'ERROR' }
229
+
230
+ it "should send an error complete event" do
231
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
232
+ subject.execute
233
+ complete_reason = original_command.complete_event(0.1).reason
234
+ complete_reason.should be_a Punchblock::Event::Complete::Error
235
+ complete_reason.details.should == "Terminated due to UniMRCP error"
236
+ end
237
+ end
238
+ end
239
+
240
+ context "when we get a RubyAMI Error" do
241
+ it "should send an error complete event" do
242
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
243
+ mock_call.should_receive(:execute_agi_command).and_raise error
244
+ subject.execute
245
+ complete_reason = original_command.complete_event(0.1).reason
246
+ complete_reason.should be_a Punchblock::Event::Complete::Error
247
+ complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
248
+ end
249
+ end
250
+
251
+ context "when the channel is gone" do
252
+ it "should send an error complete event" do
253
+ error = ChannelGoneError.new 'FooBar'
254
+ mock_call.should_receive(:execute_agi_command).and_raise error
255
+ subject.execute
256
+ complete_reason = original_command.complete_event(0.1).reason
257
+ complete_reason.should be_a Punchblock::Event::Complete::Hangup
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ [:foobar, :swift, :unimrcp].each do |renderer|
264
+ context renderer do
265
+ let(:output_command_opts) { { renderer: renderer } }
266
+
267
+ it "should return an error and not execute any actions" do
268
+ subject.execute
269
+ error = ProtocolError.new.setup 'option error', "The renderer #{renderer} is unsupported."
270
+ original_command.response(0.1).should be == error
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ describe 'barge_in' do
277
+ context 'unset' do
278
+ let(:command_options) { { barge_in: nil } }
279
+
280
+ it 'should pass the b=1 option to MRCPRecog' do
281
+ expect_mrcprecog_with_options(/b=1/)
282
+ subject.execute
283
+ end
284
+ end
285
+
286
+ context 'true' do
287
+ let(:command_options) { { barge_in: true } }
288
+
289
+ it 'should pass the b=1 option to MRCPRecog' do
290
+ expect_mrcprecog_with_options(/b=1/)
291
+ subject.execute
292
+ end
293
+ end
294
+
295
+ context 'false' do
296
+ let(:command_options) { { barge_in: false } }
297
+
298
+ it 'should pass the b=0 option to MRCPRecog' do
299
+ expect_mrcprecog_with_options(/b=0/)
300
+ subject.execute
301
+ end
302
+ end
303
+ end
304
+
305
+ describe 'Output#voice' do
306
+ context 'unset' do
307
+ let(:output_command_opts) { { voice: nil } }
308
+
309
+ it 'should not pass any options to MRCPRecog' do
310
+ expect_mrcprecog_with_options(//)
311
+ subject.execute
312
+ end
313
+ end
314
+
315
+ context 'set' do
316
+ let(:output_command_opts) { { voice: 'alison' } }
317
+
318
+ it 'should not pass any options to MRCPRecog' do
319
+ expect_mrcprecog_with_options(//)
320
+ subject.execute
321
+ end
322
+ end
323
+ end
324
+
325
+ describe 'Output#start-offset' do
326
+ context 'unset' do
327
+ let(:output_command_opts) { { start_offset: nil } }
328
+ it 'should not pass any options to MRCPRecog' do
329
+ expect_mrcprecog_with_options(//)
330
+ subject.execute
331
+ end
332
+ end
333
+
334
+ context 'set' do
335
+ let(:output_command_opts) { { start_offset: 10 } }
336
+ it "should return an error and not execute any actions" do
337
+ subject.execute
338
+ error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported on Asterisk.'
339
+ original_command.response(0.1).should be == error
340
+ end
341
+ end
342
+ end
343
+
344
+ describe 'Output#start-paused' do
345
+ context 'false' do
346
+ let(:output_command_opts) { { start_paused: false } }
347
+ it 'should not pass any options to MRCPRecog' do
348
+ expect_mrcprecog_with_options(//)
349
+ subject.execute
350
+ end
351
+ end
352
+
353
+ context 'true' do
354
+ let(:output_command_opts) { { start_paused: true } }
355
+ it "should return an error and not execute any actions" do
356
+ subject.execute
357
+ error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported on Asterisk.'
358
+ original_command.response(0.1).should be == error
359
+ end
360
+ end
361
+ end
362
+
363
+ describe 'Output#repeat-interval' do
364
+ context 'unset' do
365
+ let(:output_command_opts) { { repeat_interval: nil } }
366
+ it 'should not pass any options to MRCPRecog' do
367
+ expect_mrcprecog_with_options(//)
368
+ subject.execute
369
+ end
370
+ end
371
+
372
+ context 'set' do
373
+ let(:output_command_opts) { { repeat_interval: 10 } }
374
+ it "should return an error and not execute any actions" do
375
+ subject.execute
376
+ error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported on Asterisk.'
377
+ original_command.response(0.1).should be == error
378
+ end
379
+ end
380
+ end
381
+
382
+ describe 'Output#repeat-times' do
383
+ context 'unset' do
384
+ let(:output_command_opts) { { repeat_times: nil } }
385
+ it 'should not pass any options to MRCPRecog' do
386
+ expect_mrcprecog_with_options(//)
387
+ subject.execute
388
+ end
389
+ end
390
+
391
+ context 'set' do
392
+ let(:output_command_opts) { { repeat_times: 2 } }
393
+ it "should return an error and not execute any actions" do
394
+ subject.execute
395
+ error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported on Asterisk.'
396
+ original_command.response(0.1).should be == error
397
+ end
398
+ end
399
+ end
400
+
401
+ describe 'Output#max-time' do
402
+ context 'unset' do
403
+ let(:output_command_opts) { { max_time: nil } }
404
+ it 'should not pass any options to MRCPRecog' do
405
+ expect_mrcprecog_with_options(//)
406
+ subject.execute
407
+ end
408
+ end
409
+
410
+ context 'set' do
411
+ let(:output_command_opts) { { max_time: 30 } }
412
+ it "should return an error and not execute any actions" do
413
+ subject.execute
414
+ error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported on Asterisk.'
415
+ original_command.response(0.1).should be == error
416
+ end
417
+ end
418
+ end
419
+
420
+ describe 'Output#interrupt_on' do
421
+ context 'unset' do
422
+ let(:output_command_opts) { { interrupt_on: nil } }
423
+ it 'should not pass any options to MRCPRecog' do
424
+ expect_mrcprecog_with_options(//)
425
+ subject.execute
426
+ end
427
+ end
428
+
429
+ context 'set' do
430
+ let(:output_command_opts) { { interrupt_on: :dtmf } }
431
+ it "should return an error and not execute any actions" do
432
+ subject.execute
433
+ error = ProtocolError.new.setup 'option error', 'A interrupt_on value is unsupported on Asterisk.'
434
+ original_command.response(0.1).should be == error
435
+ end
436
+ end
437
+ end
438
+
439
+ describe 'Input#grammar' do
440
+ context 'with multiple inline grammars' do
441
+ let(:input_command_options) { { grammars: [{value: voice_grammar}, {value: dtmf_grammar}] } }
442
+
443
+ it "should return a ref and execute MRCPRecog" do
444
+ param = ["\"#{[voice_grammar.to_doc.to_s, dtmf_grammar.to_doc.to_s].join(',').squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
445
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
446
+ subject.execute
447
+ original_command.response(0.1).should be_a Ref
448
+ end
449
+ end
450
+
451
+ context 'with multiple grammars by URI' do
452
+ let(:input_command_options) { { grammars: [{url: 'http://example.com/grammar1.grxml'}, {url: 'http://example.com/grammar2.grxml'}] } }
453
+
454
+ it "should return a ref and execute MRCPRecog" do
455
+ param = ["\"#{"http://example.com/grammar1.grxml,http://example.com/grammar2.grxml".squish.gsub('"', '\"')}\"", "uer=1&b=1&f=#{audio_filename}"].join(',')
456
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPRecog', param).and_return code: 200, result: 1
457
+ subject.execute
458
+ original_command.response(0.1).should be_a Ref
459
+ end
460
+ end
461
+
462
+ context 'unset' do
463
+ let(:input_command_options) { {} }
464
+
465
+ it "should return an error and not execute any actions" do
466
+ subject.execute
467
+ error = ProtocolError.new.setup 'option error', 'A grammar is required.'
468
+ original_command.response(0.1).should be == error
469
+ end
470
+ end
471
+ end
472
+
473
+ describe 'Input#initial-timeout' do
474
+ context 'a positive number' do
475
+ let(:input_command_opts) { { initial_timeout: 1000 } }
476
+
477
+ it 'should pass the nit option to MRCPRecog' do
478
+ expect_mrcprecog_with_options(/nit=1000/)
479
+ subject.execute
480
+ end
481
+ end
482
+
483
+ context '-1' do
484
+ let(:input_command_opts) { { initial_timeout: -1 } }
485
+
486
+ it 'should not pass any options to MRCPRecog' do
487
+ expect_mrcprecog_with_options(//)
488
+ subject.execute
489
+ end
490
+ end
491
+
492
+ context 'unset' do
493
+ let(:input_command_opts) { { initial_timeout: nil } }
494
+
495
+ it 'should not pass any options to MRCPRecog' do
496
+ expect_mrcprecog_with_options(//)
497
+ subject.execute
498
+ end
499
+ end
500
+
501
+ context 'a negative number other than -1' do
502
+ let(:input_command_opts) { { initial_timeout: -1000 } }
503
+
504
+ it "should return an error and not execute any actions" do
505
+ subject.execute
506
+ error = ProtocolError.new.setup 'option error', 'An initial-timeout value must be -1 or a positive integer.'
507
+ original_command.response(0.1).should be == error
508
+ end
509
+ end
510
+ end
511
+
512
+ describe 'Input#inter-digit-timeout' do
513
+ context 'a positive number' do
514
+ let(:input_command_opts) { { inter_digit_timeout: 1000 } }
515
+
516
+ it 'should pass the dit option to MRCPRecog' do
517
+ expect_mrcprecog_with_options(/dit=1000/)
518
+ subject.execute
519
+ end
520
+ end
521
+
522
+ context '-1' do
523
+ let(:input_command_opts) { { inter_digit_timeout: -1 } }
524
+
525
+ it 'should not pass any options to MRCPRecog' do
526
+ expect_mrcprecog_with_options(//)
527
+ subject.execute
528
+ end
529
+ end
530
+
531
+ context 'unset' do
532
+ let(:input_command_opts) { { inter_digit_timeout: nil } }
533
+
534
+ it 'should not pass any options to MRCPRecog' do
535
+ expect_mrcprecog_with_options(//)
536
+ subject.execute
537
+ end
538
+ end
539
+
540
+ context 'a negative number other than -1' do
541
+ let(:input_command_opts) { { inter_digit_timeout: -1000 } }
542
+
543
+ it "should return an error and not execute any actions" do
544
+ subject.execute
545
+ error = ProtocolError.new.setup 'option error', 'An inter-digit-timeout value must be -1 or a positive integer.'
546
+ original_command.response(0.1).should be == error
547
+ end
548
+ end
549
+ end
550
+
551
+ describe 'Input#mode' do
552
+ pending
553
+ end
554
+
555
+ describe 'Input#terminator' do
556
+ context 'a string' do
557
+ let(:input_command_opts) { { terminator: '#' } }
558
+
559
+ it 'should pass the dttc option to SynthAndRecog' do
560
+ expect_mrcprecog_with_options(/dttc=#/)
561
+ subject.execute
562
+ end
563
+ end
564
+
565
+ context 'unset' do
566
+ let(:input_command_opts) { { terminator: nil } }
567
+
568
+ it 'should not pass any options to SynthAndRecog' do
569
+ expect_mrcprecog_with_options(//)
570
+ subject.execute
571
+ end
572
+ end
573
+ end
574
+
575
+ describe 'Input#recognizer' do
576
+ pending
577
+ end
578
+
579
+ describe 'Input#sensitivity' do
580
+ pending
581
+ end
582
+
583
+ describe 'Input#min-confidence' do
584
+ pending
585
+ end
586
+
587
+ describe 'Input#max-silence' do
588
+ pending
589
+ end
590
+
591
+ describe 'Input#match-content-type' do
592
+ pending
593
+ end
594
+
595
+ describe 'Input#language' do
596
+ pending
597
+ end
598
+
599
+ describe "#execute_command" do
600
+ context "with a command it does not understand" do
601
+ let(:command) { Punchblock::Component::Output::Pause.new }
602
+
603
+ before { command.request! }
604
+ it "returns a ProtocolError response" do
605
+ subject.execute_command command
606
+ command.response(0.1).should be_a ProtocolError
607
+ end
608
+ end
609
+
610
+ context "with a Stop command" do
611
+ let(:command) { Punchblock::Component::Stop.new }
612
+ let(:reason) { original_command.complete_event(5).reason }
613
+ let(:channel) { "SIP/1234-00000000" }
614
+ let :ami_event do
615
+ RubyAMI::Event.new 'AsyncAGI',
616
+ 'SubEvent' => "Start",
617
+ 'Channel' => channel,
618
+ 'Env' => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
619
+ end
620
+
621
+ before do
622
+ command.request!
623
+ original_command.execute!
624
+ end
625
+
626
+ it "sets the command response to true" do
627
+ mock_call.async.should_receive(:redirect_back)
628
+ subject.execute_command command
629
+ command.response(0.1).should be == true
630
+ end
631
+
632
+ it "sends the correct complete event" do
633
+ mock_call.async.should_receive(:redirect_back)
634
+ subject.execute_command command
635
+ original_command.should_not be_complete
636
+ mock_call.process_ami_event ami_event
637
+ reason.should be_a Punchblock::Event::Complete::Stop
638
+ original_command.should be_complete
639
+ end
640
+
641
+ it "redirects the call by unjoining it" do
642
+ mock_call.async.should_receive(:redirect_back)
643
+ subject.execute_command command
644
+ end
645
+ end
646
+ end
647
+
648
+ end
649
+ end
650
+ end
651
+ end
652
+ end