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,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Command
5
+ describe Redirect do
6
+ it 'registers itself' do
7
+ RayoNode.class_from_registration(:redirect, 'urn:xmpp:rayo:1').should == Redirect
8
+ end
9
+
10
+ describe "when setting options in initializer" do
11
+ subject { Redirect.new :to => 'tel:+14045551234', :headers => { :x_skill => 'agent', :x_customer_id => 8877 } }
12
+
13
+ it_should_behave_like 'command_headers'
14
+
15
+ its(:to) { should == 'tel:+14045551234' }
16
+ end
17
+ end # Redirect
18
+ end # Command
19
+ end # Punchblock
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Command
5
+ describe Reject do
6
+ it 'registers itself' do
7
+ RayoNode.class_from_registration(:reject, 'urn:xmpp:rayo:1').should == Reject
8
+ end
9
+
10
+ describe "when setting options in initializer" do
11
+ subject { Reject.new :reason => :busy, :headers => { :x_skill => 'agent', :x_customer_id => 8877 } }
12
+
13
+ it_should_behave_like 'command_headers'
14
+
15
+ its(:reason) { should == :busy }
16
+ end
17
+
18
+ describe "with the reason" do
19
+ [:decline, :busy, :error].each do |reason|
20
+ describe reason do
21
+ subject { Reject.new :reason => reason }
22
+
23
+ let :expected_message do
24
+ <<-MESSAGE
25
+ <reject xmlns="urn:xmpp:rayo:1">
26
+ <#{reason}/>
27
+ </reject>
28
+ MESSAGE
29
+ end
30
+
31
+ its(:reason) { should == reason }
32
+ end
33
+ end
34
+
35
+ describe "blahblahblah" do
36
+ it "should raise an error" do
37
+ expect { Reject.new(:reason => :blahblahblah) }.to raise_error ArgumentError
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end # Punchblock
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Command
5
+ describe Unjoin do
6
+
7
+ it 'registers itself' do
8
+ RayoNode.class_from_registration(:unjoin, 'urn:xmpp:rayo:1').should == Unjoin
9
+ end
10
+
11
+ describe "when setting options in initializer" do
12
+ subject { Unjoin.new :other_call_id => 'abc123', :mixer_id => 'blah' }
13
+
14
+ its(:other_call_id) { should == 'abc123' }
15
+ its(:mixer_id) { should == 'blah' }
16
+ end
17
+ end
18
+ end
19
+ end # Punchblock
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Command
5
+ describe Unmute do
6
+ it 'registers itself' do
7
+ RayoNode.class_from_registration(:unmute, 'urn:xmpp:rayo:1').should == Unmute
8
+ end
9
+ end
10
+ end
11
+ end # Punchblock
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Command
5
+ describe CommandNode do
6
+ its(:state_name) { should == :new }
7
+
8
+ describe "#new" do
9
+ describe "with a call/component ID" do
10
+ let(:call_id) { 'abc123' }
11
+ let(:component_id) { 'abc123' }
12
+ let(:args) { [{:call_id => call_id, :component_id => component_id}] }
13
+
14
+ subject { CommandNode.new *args }
15
+
16
+ its(:call_id) { should == call_id }
17
+ its(:component_id) { should == component_id }
18
+ end
19
+ end
20
+
21
+ describe "#request!" do
22
+ before { subject.request! }
23
+
24
+ its(:state_name) { should == :requested }
25
+
26
+ it "should raise a StateMachine::InvalidTransition when received a second time" do
27
+ lambda { subject.request! }.should raise_error(StateMachine::InvalidTransition)
28
+ end
29
+
30
+ it "should prevent altering attributes" do
31
+ lambda { subject.write_attr :foo, 'bar' }.should raise_error(StandardError, "Cannot alter attributes of a requested command")
32
+ end
33
+ end
34
+
35
+ describe "#execute!" do
36
+ describe "without sending" do
37
+ it "should raise a StateMachine::InvalidTransition" do
38
+ lambda { subject.execute! }.should raise_error(StateMachine::InvalidTransition)
39
+ end
40
+ end
41
+
42
+ describe "after sending" do
43
+ before do
44
+ subject.request!
45
+ subject.execute!
46
+ end
47
+
48
+ its(:state_name) { should == :executing }
49
+ end
50
+ end
51
+
52
+ describe "#complete!" do
53
+ before do
54
+ subject.request!
55
+ subject.execute!
56
+ subject.complete!
57
+ end
58
+
59
+ its(:state_name) { should == :complete }
60
+
61
+ it "should raise a StateMachine::InvalidTransition when received a second time" do
62
+ lambda { subject.complete! }.should raise_error(StateMachine::InvalidTransition)
63
+ end
64
+ end # #complete!
65
+
66
+ describe "#response=" do
67
+ it "should set the command to executing status" do
68
+ subject.expects(:execute!).once
69
+ subject.response = :foo
70
+ end
71
+
72
+ it "should be a no-op if the response has already been set" do
73
+ subject.expects(:execute!).once
74
+ subject.response = :foo
75
+ lambda { subject.response = :bar }.should_not raise_error
76
+ end
77
+ end
78
+ end # CommandNode
79
+ end # Command
80
+ end # Punchblock
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Component
5
+ describe Input do
6
+ it 'registers itself' do
7
+ RayoNode.class_from_registration(:input, 'urn:xmpp:rayo:input:1').should == Input
8
+ end
9
+
10
+ describe "when setting options in initializer" do
11
+ subject do
12
+ Input.new :grammar => {:value => '[5 DIGITS]', :content_type => 'application/grammar+custom'},
13
+ :mode => :speech,
14
+ :terminator => '#',
15
+ :max_digits => 10,
16
+ :recognizer => 'en-US',
17
+ :initial_timeout => 2000,
18
+ :inter_digit_timeout => 2000,
19
+ :term_timeout => 2000,
20
+ :complete_timeout => 2000,
21
+ :incomplete_timeout => 2000,
22
+ :sensitivity => 0.5,
23
+ :min_confidence => 0.5
24
+ end
25
+
26
+ its(:grammar) { should == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') }
27
+ its(:mode) { should == :speech }
28
+ its(:terminator) { should == '#' }
29
+ its(:max_digits) { should == 10 }
30
+ its(:recognizer) { should == 'en-US' }
31
+ its(:initial_timeout) { should == 2000 }
32
+ its(:inter_digit_timeout) { should == 2000 }
33
+ its(:term_timeout) { should == 2000 }
34
+ its(:complete_timeout) { should == 2000 }
35
+ its(:incomplete_timeout) { should == 2000 }
36
+ its(:sensitivity) { should == 0.5 }
37
+ its(:min_confidence) { should == 0.5 }
38
+ end
39
+
40
+ describe Input::Grammar do
41
+ describe "when not passing a grammar" do
42
+ subject { Input::Grammar.new :value => '[5 DIGITS]' }
43
+ its(:content_type) { should == 'application/grammar+grxml' }
44
+ end
45
+
46
+ describe 'with a simple grammar' do
47
+ subject { Input::Grammar.new :value => '[5 DIGITS]', :content_type => 'application/grammar+custom' }
48
+
49
+ let(:expected_message) { "<![CDATA[ [5 DIGITS] ]]>" }
50
+
51
+ it "should wrap grammar in CDATA" do
52
+ subject.child.to_xml.should == expected_message.strip
53
+ end
54
+ end
55
+
56
+ describe 'with a GRXML grammar' do
57
+ subject { Input::Grammar.new :value => grxml, :content_type => 'application/grammar+grxml' }
58
+
59
+ let :grxml do
60
+ <<-GRXML
61
+ <grammar xmlns="http://www.w3.org/2001/06/grammar" root="MAINRULE">
62
+ <rule id="MAINRULE">
63
+ <one-of>
64
+ <item>
65
+ <item repeat="0-1"> need a</item>
66
+ <item repeat="0-1"> i need a</item>
67
+ <one-of>
68
+ <item> clue </item>
69
+ </one-of>
70
+ <tag> out.concept = "clue";</tag>
71
+ </item>
72
+ <item>
73
+ <item repeat="0-1"> have an</item>
74
+ <item repeat="0-1"> i have an</item>
75
+ <one-of>
76
+ <item> answer </item>
77
+ </one-of>
78
+ <tag> out.concept = "answer";</tag>
79
+ </item>
80
+ </one-of>
81
+ </rule>
82
+ </grammar>
83
+ GRXML
84
+ end
85
+
86
+ let(:expected_message) { "<![CDATA[ #{grxml} ]]>" }
87
+
88
+ it "should wrap GRXML in CDATA" do
89
+ subject.child.to_xml.should == expected_message.strip
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "actions" do
95
+ let(:command) { Input.new :grammar => '[5 DIGITS]' }
96
+
97
+ before do
98
+ command.component_id = 'abc123'
99
+ command.call_id = '123abc'
100
+ command.connection = Connection.new :username => '123', :password => '123'
101
+ end
102
+
103
+ describe '#stop_action' do
104
+ subject { command.stop_action }
105
+
106
+ its(:to_xml) { should == '<stop xmlns="urn:xmpp:rayo:1"/>' }
107
+ its(:component_id) { should == 'abc123' }
108
+ its(:call_id) { should == '123abc' }
109
+ end
110
+
111
+ describe '#stop!' do
112
+ describe "when the command is executing" do
113
+ before do
114
+ command.request!
115
+ command.execute!
116
+ end
117
+
118
+ it "should send its command properly" do
119
+ Connection.any_instance.expects(:write).with('123abc', command.stop_action, 'abc123')
120
+ command.stop!
121
+ end
122
+ end
123
+
124
+ describe "when the command is not executing" do
125
+ it "should raise an error" do
126
+ lambda { command.stop! }.should raise_error(InvalidActionError, "Cannot stop a Input that is not executing")
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ describe Input::Complete::Success do
133
+ let :stanza do
134
+ <<-MESSAGE
135
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
136
+ <success mode="speech" confidence="0.45" xmlns='urn:xmpp:rayo:input:complete:1'>
137
+ <interpretation>1234</interpretation>
138
+ <utterance>one two three four</utterance>
139
+ </success>
140
+ </complete>
141
+ MESSAGE
142
+ end
143
+
144
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
145
+
146
+ it { should be_instance_of Input::Complete::Success }
147
+
148
+ its(:name) { should == :success }
149
+ its(:mode) { should == :speech }
150
+ its(:confidence) { should == 0.45 }
151
+ its(:interpretation) { should == '1234' }
152
+ its(:utterance) { should == 'one two three four' }
153
+ end
154
+
155
+ describe Input::Complete::NoMatch do
156
+ let :stanza do
157
+ <<-MESSAGE
158
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
159
+ <nomatch xmlns='urn:xmpp:rayo:input:complete:1' />
160
+ </complete>
161
+ MESSAGE
162
+ end
163
+
164
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
165
+
166
+ it { should be_instance_of Input::Complete::NoMatch }
167
+
168
+ its(:name) { should == :nomatch }
169
+ end
170
+
171
+ describe Input::Complete::NoInput do
172
+ let :stanza do
173
+ <<-MESSAGE
174
+ <complete xmlns='urn:xmpp:rayo:ext:1'>
175
+ <noinput xmlns='urn:xmpp:rayo:input:complete:1' />
176
+ </complete>
177
+ MESSAGE
178
+ end
179
+
180
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
181
+
182
+ it { should be_instance_of Input::Complete::NoInput }
183
+
184
+ its(:name) { should == :noinput }
185
+ end
186
+ end
187
+ end
188
+ end # Punchblock