punchblock 1.9.4 → 2.0.0.beta1

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