punchblock 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. data/.document +5 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.markdown +31 -0
  7. data/Rakefile +23 -0
  8. data/assets/ozone/ask-1.0.xsd +56 -0
  9. data/assets/ozone/conference-1.0.xsd +17 -0
  10. data/assets/ozone/ozone-1.0.xsd +127 -0
  11. data/assets/ozone/say-1.0.xsd +24 -0
  12. data/assets/ozone/transfer-1.0.xsd +32 -0
  13. data/bin/punchblock-console +125 -0
  14. data/lib/punchblock/command/accept.rb +30 -0
  15. data/lib/punchblock/command/answer.rb +30 -0
  16. data/lib/punchblock/command/dial.rb +88 -0
  17. data/lib/punchblock/command/hangup.rb +25 -0
  18. data/lib/punchblock/command/join.rb +81 -0
  19. data/lib/punchblock/command/mute.rb +7 -0
  20. data/lib/punchblock/command/redirect.rb +49 -0
  21. data/lib/punchblock/command/reject.rb +61 -0
  22. data/lib/punchblock/command/unjoin.rb +50 -0
  23. data/lib/punchblock/command/unmute.rb +7 -0
  24. data/lib/punchblock/command.rb +16 -0
  25. data/lib/punchblock/command_node.rb +46 -0
  26. data/lib/punchblock/component/input.rb +320 -0
  27. data/lib/punchblock/component/output.rb +449 -0
  28. data/lib/punchblock/component/record.rb +216 -0
  29. data/lib/punchblock/component/tropo/ask.rb +197 -0
  30. data/lib/punchblock/component/tropo/conference.rb +328 -0
  31. data/lib/punchblock/component/tropo/say.rb +113 -0
  32. data/lib/punchblock/component/tropo/transfer.rb +178 -0
  33. data/lib/punchblock/component/tropo.rb +12 -0
  34. data/lib/punchblock/component.rb +73 -0
  35. data/lib/punchblock/connection.rb +209 -0
  36. data/lib/punchblock/core_ext/blather/stanza/presence.rb +11 -0
  37. data/lib/punchblock/core_ext/blather/stanza.rb +26 -0
  38. data/lib/punchblock/dsl.rb +46 -0
  39. data/lib/punchblock/event/answered.rb +7 -0
  40. data/lib/punchblock/event/complete.rb +65 -0
  41. data/lib/punchblock/event/dtmf.rb +19 -0
  42. data/lib/punchblock/event/end.rb +15 -0
  43. data/lib/punchblock/event/info.rb +15 -0
  44. data/lib/punchblock/event/joined.rb +50 -0
  45. data/lib/punchblock/event/offer.rb +29 -0
  46. data/lib/punchblock/event/ringing.rb +7 -0
  47. data/lib/punchblock/event/unjoined.rb +50 -0
  48. data/lib/punchblock/event.rb +16 -0
  49. data/lib/punchblock/generic_connection.rb +18 -0
  50. data/lib/punchblock/has_headers.rb +34 -0
  51. data/lib/punchblock/header.rb +47 -0
  52. data/lib/punchblock/media_container.rb +39 -0
  53. data/lib/punchblock/media_node.rb +17 -0
  54. data/lib/punchblock/protocol_error.rb +16 -0
  55. data/lib/punchblock/rayo_node.rb +88 -0
  56. data/lib/punchblock/ref.rb +26 -0
  57. data/lib/punchblock/version.rb +3 -0
  58. data/lib/punchblock.rb +42 -0
  59. data/log/.gitkeep +0 -0
  60. data/punchblock.gemspec +42 -0
  61. data/spec/punchblock/command/accept_spec.rb +13 -0
  62. data/spec/punchblock/command/answer_spec.rb +13 -0
  63. data/spec/punchblock/command/dial_spec.rb +54 -0
  64. data/spec/punchblock/command/hangup_spec.rb +13 -0
  65. data/spec/punchblock/command/join_spec.rb +21 -0
  66. data/spec/punchblock/command/mute_spec.rb +11 -0
  67. data/spec/punchblock/command/redirect_spec.rb +19 -0
  68. data/spec/punchblock/command/reject_spec.rb +43 -0
  69. data/spec/punchblock/command/unjoin_spec.rb +19 -0
  70. data/spec/punchblock/command/unmute_spec.rb +11 -0
  71. data/spec/punchblock/command_node_spec.rb +80 -0
  72. data/spec/punchblock/component/input_spec.rb +188 -0
  73. data/spec/punchblock/component/output_spec.rb +531 -0
  74. data/spec/punchblock/component/record_spec.rb +235 -0
  75. data/spec/punchblock/component/tropo/ask_spec.rb +183 -0
  76. data/spec/punchblock/component/tropo/conference_spec.rb +360 -0
  77. data/spec/punchblock/component/tropo/say_spec.rb +171 -0
  78. data/spec/punchblock/component/tropo/transfer_spec.rb +153 -0
  79. data/spec/punchblock/component_spec.rb +126 -0
  80. data/spec/punchblock/connection_spec.rb +194 -0
  81. data/spec/punchblock/event/answered_spec.rb +23 -0
  82. data/spec/punchblock/event/complete_spec.rb +80 -0
  83. data/spec/punchblock/event/dtmf_spec.rb +24 -0
  84. data/spec/punchblock/event/end_spec.rb +30 -0
  85. data/spec/punchblock/event/info_spec.rb +30 -0
  86. data/spec/punchblock/event/joined_spec.rb +32 -0
  87. data/spec/punchblock/event/offer_spec.rb +35 -0
  88. data/spec/punchblock/event/ringing_spec.rb +23 -0
  89. data/spec/punchblock/event/unjoined_spec.rb +32 -0
  90. data/spec/punchblock/header_spec.rb +44 -0
  91. data/spec/punchblock/protocol_error_spec.rb +9 -0
  92. data/spec/punchblock/ref_spec.rb +21 -0
  93. data/spec/spec_helper.rb +43 -0
  94. metadata +353 -0
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Component
5
+ describe Record do
6
+ it 'registers itself' do
7
+ RayoNode.class_from_registration(:record, 'urn:xmpp:rayo:record:1').should == Record
8
+ end
9
+
10
+ describe "when setting options in initializer" do
11
+ subject do
12
+ Record.new :format => 'WAV',
13
+ :start_beep => true,
14
+ :start_paused => false,
15
+ :stop_beep => true,
16
+ :max_duration => 500000,
17
+ :initial_timeout => 10000,
18
+ :final_timeout => 30000
19
+ end
20
+
21
+ its(:format) { should == 'WAV' }
22
+ its(:start_beep) { should == true }
23
+ its(:start_paused) { should == false }
24
+ its(:stop_beep) { should == true }
25
+ its(:max_duration) { should == 500000 }
26
+ its(:initial_timeout) { should == 10000 }
27
+ its(:final_timeout) { should == 30000 }
28
+ end
29
+
30
+ describe "actions" do
31
+ let(:command) { Record.new }
32
+
33
+ before do
34
+ command.component_id = 'abc123'
35
+ command.call_id = '123abc'
36
+ command.connection = Connection.new :username => '123', :password => '123'
37
+ end
38
+
39
+ describe '#pause_action' do
40
+ subject { command.pause_action }
41
+
42
+ its(:to_xml) { should == '<pause xmlns="urn:xmpp:rayo:record:1"/>' }
43
+ its(:component_id) { should == 'abc123' }
44
+ its(:call_id) { should == '123abc' }
45
+ end
46
+
47
+ describe '#pause!' do
48
+ describe "when the command is executing" do
49
+ before do
50
+ command.request!
51
+ command.execute!
52
+ end
53
+
54
+ it "should send its command properly" do
55
+ Connection.any_instance.expects(:write).with('123abc', command.pause_action, 'abc123').returns true
56
+ command.expects :paused!
57
+ command.pause!
58
+ end
59
+ end
60
+
61
+ describe "when the command is not executing" do
62
+ it "should raise an error" do
63
+ lambda { command.pause! }.should raise_error(InvalidActionError, "Cannot pause a Record that is not executing")
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#paused!" do
69
+ before do
70
+ subject.request!
71
+ subject.execute!
72
+ subject.paused!
73
+ end
74
+
75
+ its(:state_name) { should == :paused }
76
+
77
+ it "should raise a StateMachine::InvalidTransition when received a second time" do
78
+ lambda { subject.paused! }.should raise_error(StateMachine::InvalidTransition)
79
+ end
80
+ end
81
+
82
+ describe '#resume_action' do
83
+ subject { command.resume_action }
84
+
85
+ its(:to_xml) { should == '<resume xmlns="urn:xmpp:rayo:record:1"/>' }
86
+ its(:component_id) { should == 'abc123' }
87
+ its(:call_id) { should == '123abc' }
88
+ end
89
+
90
+ describe '#resume!' do
91
+ describe "when the command is paused" do
92
+ before do
93
+ command.request!
94
+ command.execute!
95
+ command.paused!
96
+ end
97
+
98
+ it "should send its command properly" do
99
+ Connection.any_instance.expects(:write).with('123abc', command.resume_action, 'abc123').returns true
100
+ command.expects :resumed!
101
+ command.resume!
102
+ end
103
+ end
104
+
105
+ describe "when the command is not paused" do
106
+ it "should raise an error" do
107
+ lambda { command.resume! }.should raise_error(InvalidActionError, "Cannot resume a Record that is not paused.")
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "#resumed!" do
113
+ before do
114
+ subject.request!
115
+ subject.execute!
116
+ subject.paused!
117
+ subject.resumed!
118
+ end
119
+
120
+ its(:state_name) { should == :executing }
121
+
122
+ it "should raise a StateMachine::InvalidTransition when received a second time" do
123
+ lambda { subject.resumed! }.should raise_error(StateMachine::InvalidTransition)
124
+ end
125
+ end
126
+
127
+ describe '#stop_action' do
128
+ subject { command.stop_action }
129
+
130
+ its(:to_xml) { should == '<stop xmlns="urn:xmpp:rayo:1"/>' }
131
+ its(:component_id) { should == 'abc123' }
132
+ its(:call_id) { should == '123abc' }
133
+ end
134
+
135
+ describe '#stop!' do
136
+ describe "when the command is executing" do
137
+ before do
138
+ command.request!
139
+ command.execute!
140
+ end
141
+
142
+ it "should send its command properly" do
143
+ Connection.any_instance.expects(:write).with('123abc', command.stop_action, 'abc123')
144
+ command.stop!
145
+ end
146
+ end
147
+
148
+ describe "when the command is not executing" do
149
+ it "should raise an error" do
150
+ lambda { command.stop! }.should raise_error(InvalidActionError, "Cannot stop a Record that is not executing")
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ describe Record::Complete::Success do
157
+ let :stanza do
158
+ <<-MESSAGE
159
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
160
+ <success xmlns='urn:xmpp:rayo:record:complete:1'/>
161
+ <recording xmlns='urn:xmpp:rayo:record:complete:1' uri="file:/tmp/rayo7451601434771683422.mp3"/>
162
+ </complete>
163
+ MESSAGE
164
+ end
165
+
166
+ describe "#reason" do
167
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
168
+
169
+ it { should be_instance_of Record::Complete::Success }
170
+
171
+ its(:name) { should == :success }
172
+ end
173
+
174
+ describe "#recording" do
175
+ subject { RayoNode.import(parse_stanza(stanza).root).recording }
176
+
177
+ it { should be_instance_of Record::Recording }
178
+ its(:uri) { should == "file:/tmp/rayo7451601434771683422.mp3" }
179
+ end
180
+ end
181
+
182
+ describe Event::Complete::Stop do
183
+ let :stanza do
184
+ <<-MESSAGE
185
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
186
+ <stop xmlns='urn:xmpp:rayo:ext:complete:1' />
187
+ <recording xmlns='urn:xmpp:rayo:record:complete:1' uri="file:/tmp/rayo7451601434771683422.mp3"/>
188
+ </complete>
189
+ MESSAGE
190
+ end
191
+
192
+ describe "#reason" do
193
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
194
+
195
+ it { should be_instance_of Event::Complete::Stop }
196
+
197
+ its(:name) { should == :stop }
198
+ end
199
+
200
+ describe "#recording" do
201
+ subject { RayoNode.import(parse_stanza(stanza).root).recording }
202
+
203
+ it { should be_instance_of Record::Recording }
204
+ its(:uri) { should == "file:/tmp/rayo7451601434771683422.mp3" }
205
+ end
206
+ end
207
+
208
+ describe Event::Complete::Hangup do
209
+ let :stanza do
210
+ <<-MESSAGE
211
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
212
+ <hangup xmlns='urn:xmpp:rayo:ext:complete:1' />
213
+ <recording xmlns='urn:xmpp:rayo:record:complete:1' uri="file:/tmp/rayo7451601434771683422.mp3"/>
214
+ </complete>
215
+ MESSAGE
216
+ end
217
+
218
+ describe "#reason" do
219
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
220
+
221
+ it { should be_instance_of Event::Complete::Hangup }
222
+
223
+ its(:name) { should == :hangup }
224
+ end
225
+
226
+ describe "#recording" do
227
+ subject { RayoNode.import(parse_stanza(stanza).root).recording }
228
+
229
+ it { should be_instance_of Record::Recording }
230
+ its(:uri) { should == "file:/tmp/rayo7451601434771683422.mp3" }
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end # Punchblock
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Component
5
+ module Tropo
6
+ describe Ask do
7
+ it 'registers itself' do
8
+ RayoNode.class_from_registration(:ask, 'urn:xmpp:tropo:ask:1').should == Ask
9
+ end
10
+
11
+ describe "when setting options in initializer" do
12
+ subject do
13
+ Ask.new :choices => {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'},
14
+ :prompt => {:text => 'Please enter your postal code.', :voice => 'kate'},
15
+ :bargein => true,
16
+ :min_confidence => 0.3,
17
+ :mode => :speech,
18
+ :recognizer => 'en-US',
19
+ :terminator => '#',
20
+ :timeout => 12000
21
+ end
22
+
23
+
24
+ its(:choices) { should == Ask::Choices.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') }
25
+ its(:prompt) { should == Ask::Prompt.new(:voice => 'kate', :text => 'Please enter your postal code.') }
26
+ its(:bargein) { should == true }
27
+ its(:min_confidence) { should == 0.3 }
28
+ its(:mode) { should == :speech }
29
+ its(:recognizer) { should == 'en-US' }
30
+ its(:terminator) { should == '#' }
31
+ its(:timeout) { should == 12000 }
32
+ end
33
+
34
+ describe Ask::Choices do
35
+ describe "when not passing a grammar" do
36
+ subject { Ask::Choices.new :value => '[5 DIGITS]' }
37
+ its(:content_type) { should == 'application/grammar+grxml' }
38
+ end
39
+
40
+ describe 'with a simple grammar' do
41
+ subject { Ask::Choices.new :value => '[5 DIGITS]', :content_type => 'application/grammar+custom' }
42
+
43
+ let(:expected_message) { "<![CDATA[ [5 DIGITS] ]]>" }
44
+
45
+ it "should wrap grammar in CDATA" do
46
+ subject.child.to_xml.should == expected_message.strip
47
+ end
48
+ end
49
+
50
+ describe 'with a GRXML grammar' do
51
+ subject { Ask::Choices.new :value => grxml, :content_type => 'application/grammar+grxml' }
52
+
53
+ let :grxml do
54
+ <<-GRXML
55
+ <grammar xmlns="http://www.w3.org/2001/06/grammar" root="MAINRULE">
56
+ <rule id="MAINRULE">
57
+ <one-of>
58
+ <item>
59
+ <item repeat="0-1"> need a</item>
60
+ <item repeat="0-1"> i need a</item>
61
+ <one-of>
62
+ <item> clue </item>
63
+ </one-of>
64
+ <tag> out.concept = "clue";</tag>
65
+ </item>
66
+ <item>
67
+ <item repeat="0-1"> have an</item>
68
+ <item repeat="0-1"> i have an</item>
69
+ <one-of>
70
+ <item> answer </item>
71
+ </one-of>
72
+ <tag> out.concept = "answer";</tag>
73
+ </item>
74
+ </one-of>
75
+ </rule>
76
+ </grammar>
77
+ GRXML
78
+ end
79
+
80
+ let(:expected_message) { "<![CDATA[ #{grxml} ]]>" }
81
+
82
+ it "should wrap GRXML in CDATA" do
83
+ subject.child.to_xml.should == expected_message.strip
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "actions" do
89
+ let(:command) { Ask.new :choices => '[5 DIGITS]' }
90
+
91
+ before do
92
+ command.component_id = 'abc123'
93
+ command.call_id = '123abc'
94
+ command.connection = Connection.new :username => '123', :password => '123'
95
+ end
96
+
97
+ describe '#stop_action' do
98
+ subject { command.stop_action }
99
+
100
+ its(:to_xml) { should == '<stop xmlns="urn:xmpp:rayo:1"/>' }
101
+ its(:component_id) { should == 'abc123' }
102
+ its(:call_id) { should == '123abc' }
103
+ end
104
+
105
+ describe '#stop!' do
106
+ describe "when the command is executing" do
107
+ before do
108
+ command.request!
109
+ command.execute!
110
+ end
111
+
112
+ it "should send its command properly" do
113
+ Connection.any_instance.expects(:write).with('123abc', command.stop_action, 'abc123')
114
+ command.stop!
115
+ end
116
+ end
117
+
118
+ describe "when the command is not executing" do
119
+ it "should raise an error" do
120
+ lambda { command.stop! }.should raise_error(InvalidActionError, "Cannot stop a Ask that is not executing")
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ describe Ask::Complete::Success do
127
+ let :stanza do
128
+ <<-MESSAGE
129
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
130
+ <success mode="speech" confidence="0.45" xmlns='urn:xmpp:tropo:ask:complete:1'>
131
+ <interpretation>1234</interpretation>
132
+ <utterance>one two three four</utterance>
133
+ </success>
134
+ </complete>
135
+ MESSAGE
136
+ end
137
+
138
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
139
+
140
+ it { should be_instance_of Ask::Complete::Success }
141
+
142
+ its(:name) { should == :success }
143
+ its(:mode) { should == :speech }
144
+ its(:confidence) { should == 0.45 }
145
+ its(:interpretation) { should == '1234' }
146
+ its(:utterance) { should == 'one two three four' }
147
+ end
148
+
149
+ describe Ask::Complete::NoMatch do
150
+ let :stanza do
151
+ <<-MESSAGE
152
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
153
+ <nomatch xmlns='urn:xmpp:tropo:ask:complete:1' />
154
+ </complete>
155
+ MESSAGE
156
+ end
157
+
158
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
159
+
160
+ it { should be_instance_of Ask::Complete::NoMatch }
161
+
162
+ its(:name) { should == :nomatch }
163
+ end
164
+
165
+ describe Ask::Complete::NoInput do
166
+ let :stanza do
167
+ <<-MESSAGE
168
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
169
+ <noinput xmlns='urn:xmpp:tropo:ask:complete:1' />
170
+ </complete>
171
+ MESSAGE
172
+ end
173
+
174
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
175
+
176
+ it { should be_instance_of Ask::Complete::NoInput }
177
+
178
+ its(:name) { should == :noinput }
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end # Punchblock