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
@@ -6,7 +6,7 @@ module Punchblock
6
6
  module Component
7
7
  describe Output do
8
8
  it 'registers itself' do
9
- RayoNode.class_from_registration(:output, 'urn:xmpp:rayo:output:1').should be == Output
9
+ RayoNode.class_from_registration(:output, 'urn:xmpp:rayo:output:1').should be == described_class
10
10
  end
11
11
 
12
12
  describe 'default values' do
@@ -18,21 +18,29 @@ module Punchblock
18
18
  its(:max_time) { should be nil }
19
19
  its(:voice) { should be nil }
20
20
  its(:renderer) { should be nil }
21
+ its(:render_documents) { should be == [] }
22
+ end
23
+
24
+ def ssml_doc(mode = :ordinal)
25
+ RubySpeech::SSML.draw do
26
+ say_as(:interpret_as => mode) { string '100' }
27
+ end
21
28
  end
22
29
 
23
30
  describe "when setting options in initializer" do
24
31
  subject do
25
- Output.new :interrupt_on => :speech,
32
+ Output.new :interrupt_on => :voice,
26
33
  :start_offset => 2000,
27
34
  :start_paused => false,
28
35
  :repeat_interval => 2000,
29
36
  :repeat_times => 10,
30
37
  :max_time => 30000,
31
38
  :voice => 'allison',
32
- :renderer => 'swift'
39
+ :renderer => 'swift',
40
+ :render_document => {:value => ssml_doc}
33
41
  end
34
42
 
35
- its(:interrupt_on) { should be == :speech }
43
+ its(:interrupt_on) { should be == :voice }
36
44
  its(:start_offset) { should be == 2000 }
37
45
  its(:start_paused) { should be == false }
38
46
  its(:repeat_interval) { should be == 2000 }
@@ -40,28 +48,131 @@ module Punchblock
40
48
  its(:max_time) { should be == 30000 }
41
49
  its(:voice) { should be == 'allison' }
42
50
  its(:renderer) { should be == 'swift' }
51
+ its(:render_documents) { should be == [Output::Document.new(:value => ssml_doc)] }
52
+
53
+ context "using #ssml=" do
54
+ subject do
55
+ Output.new :ssml => ssml_doc
56
+ end
57
+
58
+ its(:render_documents) { should be == [Output::Document.new(:value => ssml_doc)] }
59
+ end
60
+
61
+ context "with multiple documents" do
62
+ subject do
63
+ Output.new :render_documents => [
64
+ {:value => ssml_doc},
65
+ {:value => ssml_doc(:cardinal)}
66
+ ]
67
+ end
68
+
69
+ its(:render_documents) { should be == [
70
+ Output::Document.new(:value => ssml_doc),
71
+ Output::Document.new(:value => ssml_doc(:cardinal))
72
+ ]}
73
+ end
74
+
75
+ context "with a urilist" do
76
+ subject do
77
+ Output.new render_document: {
78
+ content_type: 'text/uri-list',
79
+ value: Punchblock::URIList.new('http://example.com/hello.mp3')
80
+ }
81
+ end
82
+
83
+ its(:render_documents) { should be == [Output::Document.new(content_type: 'text/uri-list', value: ['http://example.com/hello.mp3'])] }
84
+
85
+ describe "exporting to Rayo" do
86
+ it "should export to XML that can be understood by its parser" do
87
+ puts subject.to_rayo.to_xml
88
+ new_instance = RayoNode.from_xml Nokogiri::XML(subject.to_rayo.to_xml, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root
89
+ new_instance.render_documents.should be == [Output::Document.new(content_type: 'text/uri-list', value: ['http://example.com/hello.mp3'])]
90
+ end
91
+ end
92
+ end
93
+
94
+ context "with a nil document" do
95
+ it "removes all documents" do
96
+ subject.render_document = nil
97
+ subject.render_documents.should == []
98
+ end
99
+ end
100
+
101
+ context "without any documents" do
102
+ subject { described_class.new }
103
+
104
+ its(:render_documents) { should == [] }
105
+ end
106
+
107
+ describe "exporting to Rayo" do
108
+ it "should export to XML that can be understood by its parser" do
109
+ new_instance = RayoNode.from_xml Nokogiri::XML(subject.to_rayo.to_xml, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root
110
+ new_instance.should be_instance_of described_class
111
+ new_instance.interrupt_on.should be == :voice
112
+ new_instance.start_offset.should be == 2000
113
+ new_instance.start_paused.should be == false
114
+ new_instance.repeat_interval.should be == 2000
115
+ new_instance.repeat_times.should be == 10
116
+ new_instance.max_time.should be == 30000
117
+ new_instance.voice.should be == 'allison'
118
+ new_instance.renderer.should be == 'swift'
119
+ new_instance.render_documents.should be == [Output::Document.new(:value => ssml_doc)]
120
+ end
121
+
122
+ it "should wrap the document value in CDATA" do
123
+ grammar_node = subject.to_rayo.at_xpath('ns:document', ns: described_class.registered_ns)
124
+ grammar_node.children.first.should be_a Nokogiri::XML::CDATA
125
+ end
126
+
127
+ it "should render to a parent node if supplied" do
128
+ doc = Nokogiri::XML::Document.new
129
+ parent = Nokogiri::XML::Node.new 'foo', doc
130
+ doc.root = parent
131
+ rayo_doc = subject.to_rayo(parent)
132
+ rayo_doc.should == parent
133
+ end
134
+ end
43
135
  end
44
136
 
45
137
  describe "from a stanza" do
46
138
  let :stanza do
47
139
  <<-MESSAGE
48
140
  <output xmlns='urn:xmpp:rayo:output:1'
49
- interrupt-on='speech'
141
+ interrupt-on='voice'
50
142
  start-offset='2000'
51
143
  start-paused='false'
52
144
  repeat-interval='2000'
53
145
  repeat-times='10'
54
146
  max-time='30000'
55
147
  voice='allison'
56
- renderer='swift'>Hello world</output>
148
+ renderer='swift'>
149
+ <document content-type="application/ssml+xml">
150
+ <![CDATA[
151
+ <speak version="1.0"
152
+ xmlns="http://www.w3.org/2001/10/synthesis"
153
+ xml:lang="en-US">
154
+ <say-as interpret-as="ordinal">100</say-as>
155
+ </speak>
156
+ ]]>
157
+ </document>
158
+ <document content-type="application/ssml+xml">
159
+ <![CDATA[
160
+ <speak version="1.0"
161
+ xmlns="http://www.w3.org/2001/10/synthesis"
162
+ xml:lang="en-US">
163
+ <say-as interpret-as="ordinal">100</say-as>
164
+ </speak>
165
+ ]]>
166
+ </document>
167
+ </output>
57
168
  MESSAGE
58
169
  end
59
170
 
60
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
171
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
61
172
 
62
173
  it { should be_instance_of Output }
63
174
 
64
- its(:interrupt_on) { should be == :speech }
175
+ its(:interrupt_on) { should be == :voice }
65
176
  its(:start_offset) { should be == 2000 }
66
177
  its(:start_paused) { should be == false }
67
178
  its(:repeat_interval) { should be == 2000 }
@@ -69,73 +180,89 @@ module Punchblock
69
180
  its(:max_time) { should be == 30000 }
70
181
  its(:voice) { should be == 'allison' }
71
182
  its(:renderer) { should be == 'swift' }
72
- its(:text) { should be == 'Hello world' }
183
+ its(:render_documents) { should be == [Output::Document.new(:value => ssml_doc), Output::Document.new(:value => ssml_doc)] }
73
184
 
74
- context "with SSML" do
185
+ context "with a urilist" do
75
186
  let :stanza do
76
187
  <<-MESSAGE
77
- <output xmlns='urn:xmpp:rayo:output:1'
78
- interrupt-on='speech'
79
- start-offset='2000'
80
- start-paused='false'
81
- repeat-interval='2000'
82
- repeat-times='10'
83
- max-time='30000'
84
- voice='allison'
85
- renderer='swift'>
86
- <speak version="1.0"
87
- xmlns="http://www.w3.org/2001/10/synthesis"
88
- xml:lang="en-US">
89
- <say-as interpret-as="ordinal">100</say-as>
90
- </speak>
188
+ <output xmlns='urn:xmpp:rayo:output:1'>
189
+ <document content-type="text/uri-list">
190
+ <![CDATA[
191
+ http://example.com/hello.mp3
192
+ http://example.com/goodbye.mp3
193
+ ]]>
194
+ </document>
91
195
  </output>
92
196
  MESSAGE
93
197
  end
94
198
 
95
- def ssml_doc(mode = :ordinal)
96
- RubySpeech::SSML.draw do
97
- say_as(:interpret_as => mode) { string '100' }
98
- end
99
- end
100
-
101
- its(:ssml) { should be == ssml_doc }
199
+ its(:render_documents) { should be == [Output::Document.new(content_type: 'text/uri-list', value: ['http://example.com/hello.mp3', 'http://example.com/goodbye.mp3'])] }
102
200
  end
103
201
  end
104
202
 
105
- describe "for text" do
106
- subject { Output.new :text => 'Once upon a time there was a message...', :voice => 'kate' }
203
+ describe Output::Document do
204
+ describe "when not passing a content type" do
205
+ subject { Output::Document.new :value => ssml_doc }
206
+ its(:content_type) { should be == 'application/ssml+xml' }
207
+ end
107
208
 
108
- its(:voice) { should be == 'kate' }
109
- its(:text) { should be == 'Once upon a time there was a message...' }
110
- end
209
+ describe 'with an SSML document' do
210
+ subject { Output::Document.new :value => ssml_doc, :content_type => 'application/ssml+xml' }
211
+
212
+ its(:content_type) { should be == 'application/ssml+xml' }
213
+
214
+ its(:value) { should be == ssml_doc }
111
215
 
112
- describe "for SSML" do
113
- def ssml_doc(mode = :ordinal)
114
- RubySpeech::SSML.draw do
115
- say_as(:interpret_as => mode) { string '100' }
216
+ describe "comparison" do
217
+ let(:document2) { Output::Document.new :value => ssml_doc }
218
+ let(:document3) { Output::Document.new :value => ssml_doc(:normal) }
219
+
220
+ it { should be == document2 }
221
+ it { should_not be == document3 }
222
+ end
223
+ end
224
+
225
+ describe 'with a urilist' do
226
+ subject { Output::Document.new content_type: 'text/uri-list', value: Punchblock::URIList.new('http://example.com/hello.mp3', 'http://example.com/goodbye.mp3') }
227
+
228
+ its(:value) { should be == Punchblock::URIList.new('http://example.com/hello.mp3', 'http://example.com/goodbye.mp3') }
229
+
230
+ describe "comparison" do
231
+ let(:document2) { Output::Document.new content_type: 'text/uri-list', value: Punchblock::URIList.new('http://example.com/hello.mp3', 'http://example.com/goodbye.mp3') }
232
+ let(:document3) { Output::Document.new value: '<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"><say-as interpret-as="ordinal">100</say-as></speak>' }
233
+ let(:document4) { Output::Document.new content_type: 'text/uri-list', value: Punchblock::URIList.new('http://example.com/hello.mp3') }
234
+ let(:document5) { Output::Document.new content_type: 'text/uri-list', value: Punchblock::URIList.new('http://example.com/goodbye.mp3', 'http://example.com/hello.mp3') }
235
+
236
+ it { should be == document2 }
237
+ it { should_not be == document3 }
238
+ it { should_not be == document4 }
239
+ it { should_not be == document5 }
116
240
  end
117
241
  end
118
242
 
119
- subject { Output.new :ssml => ssml_doc, :voice => 'kate' }
243
+ describe 'with a document reference by URL' do
244
+ let(:url) { 'http://foo.com/bar.grxml' }
120
245
 
121
- its(:voice) { should be == 'kate' }
246
+ subject { Output::Document.new :url => url }
122
247
 
123
- its(:ssml) { should be == ssml_doc }
248
+ its(:url) { should be == url }
249
+ its(:content_type) { should be nil}
124
250
 
125
- describe "comparison" do
126
- let(:output2) { Output.new :ssml => '<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"><say-as interpret-as="ordinal">100</say-as></speak>', :voice => 'kate' }
127
- let(:output3) { Output.new :ssml => ssml_doc, :voice => 'kate' }
128
- let(:output4) { Output.new :ssml => ssml_doc(:normal), :voice => 'kate' }
251
+ describe "comparison" do
252
+ it "should be the same with the same url" do
253
+ Output::Document.new(:url => url).should be == Output::Document.new(:url => url)
254
+ end
129
255
 
130
- it { should be == output2 }
131
- it { should be == output3 }
132
- it { should_not be == output4 }
256
+ it "should be different with a different url" do
257
+ Output::Document.new(:url => url).should_not be == Output::Document.new(:url => 'http://doo.com/dah')
258
+ end
259
+ end
133
260
  end
134
261
  end
135
262
 
136
263
  describe "actions" do
137
- let(:mock_client) { mock 'Client' }
138
- let(:command) { Output.new :text => 'Once upon a time there was a message...', :voice => 'kate' }
264
+ let(:mock_client) { double 'Client' }
265
+ let(:command) { described_class.new }
139
266
 
140
267
  before do
141
268
  command.component_id = 'abc123'
@@ -596,20 +723,25 @@ module Punchblock
596
723
  end
597
724
  end
598
725
 
599
- describe Output::Complete::Success do
600
- let :stanza do
601
- <<-MESSAGE
726
+ {
727
+ Output::Complete::Finish => :finish,
728
+ Output::Complete::MaxTime => :'max-time',
729
+ }.each do |klass, element_name|
730
+ describe klass do
731
+ let :stanza do
732
+ <<-MESSAGE
602
733
  <complete xmlns='urn:xmpp:rayo:ext:1'>
603
- <success xmlns='urn:xmpp:rayo:output:complete:1' />
734
+ <#{element_name} xmlns='urn:xmpp:rayo:output:complete:1' />
604
735
  </complete>
605
- MESSAGE
606
- end
736
+ MESSAGE
737
+ end
607
738
 
608
- subject { RayoNode.import(parse_stanza(stanza).root).reason }
739
+ subject { RayoNode.from_xml(parse_stanza(stanza).root).reason }
609
740
 
610
- it { should be_instance_of Output::Complete::Success }
741
+ it { should be_instance_of klass }
611
742
 
612
- its(:name) { should be == :success }
743
+ its(:name) { should be == element_name }
744
+ end
613
745
  end
614
746
  end
615
- end # Punchblock
747
+ end
@@ -0,0 +1,132 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Punchblock
6
+ module Component
7
+ describe Prompt do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:prompt, 'urn:xmpp:rayo:prompt:1').should be == described_class
10
+ end
11
+
12
+ describe "when setting options in initializer" do
13
+ let(:output) { Output.new :render_document => {content_type: 'text/uri-list', value: Punchblock::URIList.new('http://example.com/hello.mp3')} }
14
+ let(:input) { Input.new :mode => :voice }
15
+ subject { described_class.new output, input, :barge_in => true }
16
+
17
+ its(:output) { should be == output }
18
+ its(:input) { should be == input }
19
+ its(:barge_in) { should be_true }
20
+
21
+ context "with barge-in unset" do
22
+ subject { described_class.new output, input }
23
+
24
+ its(:barge_in) { should be_nil }
25
+ end
26
+
27
+ context "with options for sub-components" do
28
+ subject { described_class.new({renderer: :foo}, {recognizer: :bar}) }
29
+
30
+ its(:output) { should be == Output.new(renderer: :foo) }
31
+ its(:input) { should be == Input.new(recognizer: :bar) }
32
+ end
33
+
34
+ describe "exporting to Rayo" do
35
+ it "should export to XML that can be understood by its parser" do
36
+ new_instance = RayoNode.from_xml subject.to_rayo
37
+ new_instance.should be_instance_of described_class
38
+ new_instance.output.should be == output
39
+ new_instance.input.should be == input
40
+ new_instance.barge_in.should be_true
41
+ end
42
+
43
+ it "should render to a parent node if supplied" do
44
+ doc = Nokogiri::XML::Document.new
45
+ parent = Nokogiri::XML::Node.new 'foo', doc
46
+ doc.root = parent
47
+ rayo_doc = subject.to_rayo(parent)
48
+ rayo_doc.should == parent
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "from a stanza" do
54
+ let :ssml do
55
+ RubySpeech::SSML.draw do
56
+ audio :src => 'http://foo.com/bar.mp3'
57
+ end
58
+ end
59
+
60
+ let :stanza do
61
+ <<-MESSAGE
62
+ <prompt xmlns="urn:xmpp:rayo:prompt:1" barge-in="true">
63
+ <output xmlns="urn:xmpp:rayo:output:1" voice="allison">
64
+ <document content-type="application/ssml+xml">
65
+ <![CDATA[
66
+ <speak version="1.0"
67
+ xmlns="http://www.w3.org/2001/10/synthesis"
68
+ xml:lang="en-US">
69
+ <audio src="http://foo.com/bar.mp3"/>
70
+ </speak>
71
+ ]]>
72
+ </document>
73
+ </output>
74
+ <input xmlns="urn:xmpp:rayo:input:1" mode="voice">
75
+ <grammar content-type="application/grammar+custom">
76
+ <![CDATA[ [5 DIGITS] ]]>
77
+ </grammar>
78
+ </input>
79
+ </prompt>
80
+ MESSAGE
81
+ end
82
+
83
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
84
+
85
+ it { should be_instance_of described_class }
86
+
87
+ its(:barge_in) { should be_true }
88
+ its(:output) { should be == Output.new(:voice => 'allison', :render_document => {:value => ssml}) }
89
+ its(:input) { should be == Input.new(:mode => :voice, :grammar => {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'}) }
90
+ end
91
+
92
+ describe "actions" do
93
+ let(:mock_client) { double 'Client' }
94
+ let(:command) { described_class.new }
95
+
96
+ before do
97
+ command.component_id = 'abc123'
98
+ command.target_call_id = '123abc'
99
+ command.client = mock_client
100
+ end
101
+
102
+ describe '#stop_action' do
103
+ subject { command.stop_action }
104
+
105
+ its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:ext:1"/>' }
106
+ its(:component_id) { should be == 'abc123' }
107
+ its(:target_call_id) { should be == '123abc' }
108
+ end
109
+
110
+ describe '#stop!' do
111
+ describe "when the command is executing" do
112
+ before do
113
+ command.request!
114
+ command.execute!
115
+ end
116
+
117
+ it "should send its command properly" do
118
+ mock_client.should_receive(:execute_command).with(command.stop_action, :target_call_id => '123abc', :component_id => 'abc123')
119
+ command.stop!
120
+ end
121
+ end
122
+
123
+ describe "when the command is not executing" do
124
+ it "should raise an error" do
125
+ lambda { command.stop! }.should raise_error(InvalidActionError, "Cannot stop a Prompt that is new")
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end # Punchblock