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