punchblock 0.4.0

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