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.
- data/.document +5 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +31 -0
- data/Rakefile +23 -0
- data/assets/ozone/ask-1.0.xsd +56 -0
- data/assets/ozone/conference-1.0.xsd +17 -0
- data/assets/ozone/ozone-1.0.xsd +127 -0
- data/assets/ozone/say-1.0.xsd +24 -0
- data/assets/ozone/transfer-1.0.xsd +32 -0
- data/bin/punchblock-console +125 -0
- data/lib/punchblock/command/accept.rb +30 -0
- data/lib/punchblock/command/answer.rb +30 -0
- data/lib/punchblock/command/dial.rb +88 -0
- data/lib/punchblock/command/hangup.rb +25 -0
- data/lib/punchblock/command/join.rb +81 -0
- data/lib/punchblock/command/mute.rb +7 -0
- data/lib/punchblock/command/redirect.rb +49 -0
- data/lib/punchblock/command/reject.rb +61 -0
- data/lib/punchblock/command/unjoin.rb +50 -0
- data/lib/punchblock/command/unmute.rb +7 -0
- data/lib/punchblock/command.rb +16 -0
- data/lib/punchblock/command_node.rb +46 -0
- data/lib/punchblock/component/input.rb +320 -0
- data/lib/punchblock/component/output.rb +449 -0
- data/lib/punchblock/component/record.rb +216 -0
- data/lib/punchblock/component/tropo/ask.rb +197 -0
- data/lib/punchblock/component/tropo/conference.rb +328 -0
- data/lib/punchblock/component/tropo/say.rb +113 -0
- data/lib/punchblock/component/tropo/transfer.rb +178 -0
- data/lib/punchblock/component/tropo.rb +12 -0
- data/lib/punchblock/component.rb +73 -0
- data/lib/punchblock/connection.rb +209 -0
- data/lib/punchblock/core_ext/blather/stanza/presence.rb +11 -0
- data/lib/punchblock/core_ext/blather/stanza.rb +26 -0
- data/lib/punchblock/dsl.rb +46 -0
- data/lib/punchblock/event/answered.rb +7 -0
- data/lib/punchblock/event/complete.rb +65 -0
- data/lib/punchblock/event/dtmf.rb +19 -0
- data/lib/punchblock/event/end.rb +15 -0
- data/lib/punchblock/event/info.rb +15 -0
- data/lib/punchblock/event/joined.rb +50 -0
- data/lib/punchblock/event/offer.rb +29 -0
- data/lib/punchblock/event/ringing.rb +7 -0
- data/lib/punchblock/event/unjoined.rb +50 -0
- data/lib/punchblock/event.rb +16 -0
- data/lib/punchblock/generic_connection.rb +18 -0
- data/lib/punchblock/has_headers.rb +34 -0
- data/lib/punchblock/header.rb +47 -0
- data/lib/punchblock/media_container.rb +39 -0
- data/lib/punchblock/media_node.rb +17 -0
- data/lib/punchblock/protocol_error.rb +16 -0
- data/lib/punchblock/rayo_node.rb +88 -0
- data/lib/punchblock/ref.rb +26 -0
- data/lib/punchblock/version.rb +3 -0
- data/lib/punchblock.rb +42 -0
- data/log/.gitkeep +0 -0
- data/punchblock.gemspec +42 -0
- data/spec/punchblock/command/accept_spec.rb +13 -0
- data/spec/punchblock/command/answer_spec.rb +13 -0
- data/spec/punchblock/command/dial_spec.rb +54 -0
- data/spec/punchblock/command/hangup_spec.rb +13 -0
- data/spec/punchblock/command/join_spec.rb +21 -0
- data/spec/punchblock/command/mute_spec.rb +11 -0
- data/spec/punchblock/command/redirect_spec.rb +19 -0
- data/spec/punchblock/command/reject_spec.rb +43 -0
- data/spec/punchblock/command/unjoin_spec.rb +19 -0
- data/spec/punchblock/command/unmute_spec.rb +11 -0
- data/spec/punchblock/command_node_spec.rb +80 -0
- data/spec/punchblock/component/input_spec.rb +188 -0
- data/spec/punchblock/component/output_spec.rb +531 -0
- data/spec/punchblock/component/record_spec.rb +235 -0
- data/spec/punchblock/component/tropo/ask_spec.rb +183 -0
- data/spec/punchblock/component/tropo/conference_spec.rb +360 -0
- data/spec/punchblock/component/tropo/say_spec.rb +171 -0
- data/spec/punchblock/component/tropo/transfer_spec.rb +153 -0
- data/spec/punchblock/component_spec.rb +126 -0
- data/spec/punchblock/connection_spec.rb +194 -0
- data/spec/punchblock/event/answered_spec.rb +23 -0
- data/spec/punchblock/event/complete_spec.rb +80 -0
- data/spec/punchblock/event/dtmf_spec.rb +24 -0
- data/spec/punchblock/event/end_spec.rb +30 -0
- data/spec/punchblock/event/info_spec.rb +30 -0
- data/spec/punchblock/event/joined_spec.rb +32 -0
- data/spec/punchblock/event/offer_spec.rb +35 -0
- data/spec/punchblock/event/ringing_spec.rb +23 -0
- data/spec/punchblock/event/unjoined_spec.rb +32 -0
- data/spec/punchblock/header_spec.rb +44 -0
- data/spec/punchblock/protocol_error_spec.rb +9 -0
- data/spec/punchblock/ref_spec.rb +21 -0
- data/spec/spec_helper.rb +43 -0
- 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,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
|