punchblock 0.5.1 → 0.6.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 (64) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/LICENSE.txt +3 -1
  3. data/bin/punchblock-console +19 -1
  4. data/lib/punchblock.rb +11 -4
  5. data/lib/punchblock/command/reject.rb +6 -2
  6. data/lib/punchblock/component.rb +1 -0
  7. data/lib/punchblock/component/asterisk.rb +10 -0
  8. data/lib/punchblock/component/asterisk/agi.rb +11 -0
  9. data/lib/punchblock/component/asterisk/agi/command.rb +157 -0
  10. data/lib/punchblock/component/asterisk/ami.rb +12 -0
  11. data/lib/punchblock/component/asterisk/ami/action.rb +144 -0
  12. data/lib/punchblock/component/input.rb +2 -2
  13. data/lib/punchblock/connection.rb +1 -0
  14. data/lib/punchblock/connection/asterisk.rb +31 -0
  15. data/lib/punchblock/core_ext/celluloid.rb +11 -0
  16. data/lib/punchblock/event.rb +1 -1
  17. data/lib/punchblock/event/asterisk.rb +9 -0
  18. data/lib/punchblock/event/asterisk/ami.rb +11 -0
  19. data/lib/punchblock/event/asterisk/ami/event.rb +66 -0
  20. data/lib/punchblock/event/complete.rb +20 -0
  21. data/lib/punchblock/event/dtmf.rb +19 -0
  22. data/lib/punchblock/event/end.rb +23 -0
  23. data/lib/punchblock/event/offer.rb +23 -0
  24. data/lib/punchblock/header.rb +4 -44
  25. data/lib/punchblock/key_value_pair_node.rb +50 -0
  26. data/lib/punchblock/rayo_node.rb +1 -1
  27. data/lib/punchblock/ref.rb +6 -0
  28. data/lib/punchblock/translator.rb +7 -0
  29. data/lib/punchblock/translator/asterisk.rb +74 -0
  30. data/lib/punchblock/translator/asterisk/ami_action.rb +86 -0
  31. data/lib/punchblock/translator/asterisk/call.rb +25 -0
  32. data/lib/punchblock/translator/asterisk/component.rb +11 -0
  33. data/lib/punchblock/version.rb +1 -1
  34. data/punchblock.gemspec +3 -1
  35. data/spec/punchblock/command/accept_spec.rb +8 -0
  36. data/spec/punchblock/command/answer_spec.rb +8 -0
  37. data/spec/punchblock/command/dial_spec.rb +22 -2
  38. data/spec/punchblock/command/hangup_spec.rb +8 -0
  39. data/spec/punchblock/command/join_spec.rb +21 -0
  40. data/spec/punchblock/command/mute_spec.rb +8 -0
  41. data/spec/punchblock/command/redirect_spec.rb +21 -0
  42. data/spec/punchblock/command/reject_spec.rb +19 -8
  43. data/spec/punchblock/command/unjoin_spec.rb +17 -0
  44. data/spec/punchblock/command/unmute_spec.rb +8 -0
  45. data/spec/punchblock/component/asterisk/agi/command_spec.rb +102 -0
  46. data/spec/punchblock/component/asterisk/ami/action_spec.rb +118 -0
  47. data/spec/punchblock/component/input_spec.rb +40 -0
  48. data/spec/punchblock/component/output_spec.rb +28 -0
  49. data/spec/punchblock/component/record_spec.rb +27 -0
  50. data/spec/punchblock/connection/asterisk_spec.rb +69 -0
  51. data/spec/punchblock/event/asterisk/ami/event_spec.rb +60 -0
  52. data/spec/punchblock/event/complete_spec.rb +8 -0
  53. data/spec/punchblock/event/dtmf_spec.rb +8 -0
  54. data/spec/punchblock/event/end_spec.rb +8 -0
  55. data/spec/punchblock/event/offer_spec.rb +15 -2
  56. data/spec/punchblock/ref_spec.rb +6 -0
  57. data/spec/punchblock/translator/asterisk/ami_action_spec.rb +149 -0
  58. data/spec/punchblock/translator/asterisk/call_spec.rb +18 -0
  59. data/spec/punchblock/translator/asterisk/component_spec.rb +11 -0
  60. data/spec/punchblock/translator/asterisk_spec.rb +150 -0
  61. data/spec/spec_helper.rb +42 -0
  62. metadata +92 -42
  63. data/lib/punchblock/event/info.rb +0 -15
  64. data/spec/punchblock/event/info_spec.rb +0 -30
@@ -6,6 +6,14 @@ module Punchblock
6
6
  it 'registers itself' do
7
7
  RayoNode.class_from_registration(:unmute, 'urn:xmpp:rayo:1').should == Unmute
8
8
  end
9
+
10
+ describe "from a stanza" do
11
+ let(:stanza) { '<unmute xmlns="urn:xmpp:rayo:1"/>' }
12
+
13
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
14
+
15
+ it { should be_instance_of Unmute }
16
+ end
9
17
  end
10
18
  end
11
19
  end # Punchblock
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Component
5
+ module Asterisk
6
+ module AGI
7
+ describe Command do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:command, 'urn:xmpp:rayo:asterisk:agi:1').should == Command
10
+ end
11
+
12
+ describe "from a stanza" do
13
+ let :stanza do
14
+ <<-MESSAGE
15
+ <command xmlns="urn:xmpp:rayo:asterisk:agi:1" name="GET VARIABLE">
16
+ <param value="UNIQUEID"/>
17
+ </command>
18
+ MESSAGE
19
+ end
20
+
21
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
22
+
23
+ it { should be_instance_of Command }
24
+
25
+ it_should_behave_like 'event'
26
+
27
+ its(:name) { should == 'GET VARIABLE' }
28
+ its(:params) { should == [Command::Param.new('UNIQUEID')] }
29
+ its(:params_array) { should == ['UNIQUEID'] }
30
+ end
31
+
32
+ describe "when setting options in initializer" do
33
+ subject do
34
+ Command.new :name => 'GET VARIABLE',
35
+ :params => ['UNIQUEID']
36
+ end
37
+
38
+ its(:name) { should == 'GET VARIABLE' }
39
+ its(:params) { should == [Command::Param.new('UNIQUEID')] }
40
+ its(:params_array) { should == ['UNIQUEID'] }
41
+ end
42
+
43
+ class Command
44
+ describe Param do
45
+ it 'will auto-inherit nodes' do
46
+ n = parse_stanza "<param value='bah' />"
47
+ h = Param.new n.root
48
+ h.value.should == 'bah'
49
+ end
50
+
51
+ it 'has a value attribute' do
52
+ n = Param.new 'en'
53
+ n.value.should == 'en'
54
+ n.value = 'de'
55
+ n.value.should == 'de'
56
+ end
57
+
58
+ it 'can determine equality' do
59
+ a = Param.new 'bah'
60
+ a.should == Param.new('bah')
61
+ a.should_not == Param.new('boo')
62
+ end
63
+ end
64
+
65
+ describe Complete::Success do
66
+ let :stanza do
67
+ <<-MESSAGE
68
+ <complete xmlns="urn:xmpp:rayo:ext:1">
69
+ <success xmlns="urn:xmpp:rayo:asterisk:agi:complete:1">
70
+ <code>200</code>
71
+ <result>0</result>
72
+ <data>1187188485.0</data>
73
+ </success>
74
+ </complete>
75
+ MESSAGE
76
+ end
77
+
78
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
79
+
80
+ it { should be_instance_of Complete::Success }
81
+
82
+ its(:name) { should == :success }
83
+ its(:code) { should == 200 }
84
+ its(:result) { should == 0 }
85
+ its(:data) { should == '1187188485.0' }
86
+
87
+ describe "when setting options in initializer" do
88
+ subject do
89
+ Complete::Success.new :code => 200, :result => 0, :data => '1187188485.0'
90
+ end
91
+
92
+ its(:code) { should == 200 }
93
+ its(:result) { should == 0 }
94
+ its(:data) { should == '1187188485.0' }
95
+ end
96
+ end
97
+ end
98
+ end # Command
99
+ end # AGI
100
+ end # Asterisk
101
+ end # Component
102
+ end # Punchblock
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Component
5
+ module Asterisk
6
+ module AMI
7
+ describe Action do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:action, 'urn:xmpp:rayo:asterisk:ami:1').should == Action
10
+ end
11
+
12
+ describe "from a stanza" do
13
+ let :stanza do
14
+ <<-MESSAGE
15
+ <action xmlns="urn:xmpp:rayo:asterisk:ami:1" name="Originate">
16
+ <param name="Channel" value="SIP/101test"/>
17
+ <param name="Context" value="default"/>
18
+ <param name="Exten" value="8135551212"/>
19
+ <param name="Priority" value="1"/>
20
+ <param name="Callerid" value="3125551212"/>
21
+ <param name="Timeout" value="30000"/>
22
+ <param name="Variable" value="var1=23|var2=24|var3=25"/>
23
+ <param name="Async" value="1"/>
24
+ </action>
25
+ MESSAGE
26
+ end
27
+
28
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
29
+
30
+ it { should be_instance_of Action }
31
+
32
+ it_should_behave_like 'event'
33
+
34
+ its(:name) { should == 'Originate' }
35
+ its(:params) { should == [Action::Param.new(:channel, 'SIP/101test'),
36
+ Action::Param.new(:context, 'default'),
37
+ Action::Param.new(:exten, '8135551212'),
38
+ Action::Param.new(:priority, '1'),
39
+ Action::Param.new(:callerid, '3125551212'),
40
+ Action::Param.new(:timeout, '30000'),
41
+ Action::Param.new(:variable, 'var1=23|var2=24|var3=25'),
42
+ Action::Param.new(:async, '1')
43
+ ]}
44
+
45
+ its(:params_hash) { should == {:channel => 'SIP/101test',
46
+ :context => 'default',
47
+ :exten => '8135551212',
48
+ :priority => '1',
49
+ :callerid => '3125551212',
50
+ :timeout => '30000',
51
+ :variable => 'var1=23|var2=24|var3=25',
52
+ :async => '1'} }
53
+ end
54
+
55
+ describe "when setting options in initializer" do
56
+ subject do
57
+ Action.new :name => 'Originate',
58
+ :params => { :channel => 'SIP/101test' }
59
+ end
60
+
61
+ its(:name) { should == 'Originate' }
62
+ its(:params) { should == [Action::Param.new(:channel, 'SIP/101test')]}
63
+ its(:params_hash) { should == { :channel => 'SIP/101test' } }
64
+ end
65
+
66
+ class Action
67
+ describe Param do
68
+ let(:class_name) { Param }
69
+ let(:element_name) { 'param' }
70
+ it_should_behave_like 'key_value_pairs'
71
+ end
72
+
73
+ class Complete
74
+ describe Success do
75
+ let :stanza do
76
+ <<-MESSAGE
77
+ <complete xmlns="urn:xmpp:rayo:ext:1">
78
+ <success xmlns="urn:xmpp:rayo:asterisk:ami:complete:1">
79
+ <message>Originate successfully queued</message>
80
+ <attribute name="Channel" value="SIP/101-3f3f"/>
81
+ <attribute name="State" value="Ring"/>
82
+ </success>
83
+ </complete>
84
+ MESSAGE
85
+ end
86
+
87
+ subject { RayoNode.import(parse_stanza(stanza).root).reason }
88
+
89
+ it { should be_instance_of Success }
90
+
91
+ its(:name) { should == :success }
92
+ its(:message) { should == "Originate successfully queued" }
93
+ its(:attributes) { should == [Attribute.new(:channel, 'SIP/101-3f3f'), Attribute.new(:state, 'Ring')]}
94
+ its(:attributes_hash) { should == {:channel => 'SIP/101-3f3f', :state => 'Ring'} }
95
+
96
+ describe "when setting options in initializer" do
97
+ subject do
98
+ Success.new :message => 'Originate successfully queued', :attributes => {:channel => 'SIP/101-3f3f', :state => 'Ring'}
99
+ end
100
+
101
+ its(:message) { should == 'Originate successfully queued' }
102
+ its(:attributes) { should == [Attribute.new(:channel, 'SIP/101-3f3f'), Attribute.new(:state, 'Ring')]}
103
+ its(:attributes_hash) { should == {:channel => 'SIP/101-3f3f', :state => 'Ring'} }
104
+ end
105
+ end
106
+
107
+ describe Attribute do
108
+ let(:class_name) { Attribute }
109
+ let(:element_name) { 'attribute' }
110
+ it_should_behave_like 'key_value_pairs'
111
+ end
112
+ end
113
+ end
114
+ end # Action
115
+ end # AMI
116
+ end # Asterisk
117
+ end # Component
118
+ end # Punchblock
@@ -37,6 +37,46 @@ module Punchblock
37
37
  its(:min_confidence) { should == 0.5 }
38
38
  end
39
39
 
40
+ describe "from a stanza" do
41
+ let :stanza do
42
+ <<-MESSAGE
43
+ <input xmlns="urn:xmpp:rayo:input:1"
44
+ mode="speech"
45
+ terminator="#"
46
+ max-digits="10"
47
+ recognizer="en-US"
48
+ initial-timeout="2000"
49
+ inter-digit-timeout="2000"
50
+ term-timeout="2000"
51
+ complete-timeout="2000"
52
+ incomplete-timeout="2000"
53
+ sensitivity="0.5"
54
+ min-confidence="0.5">
55
+ <grammar content-type="application/grammar+custom">
56
+ <![CDATA[ [5 DIGITS] ]]>
57
+ </grammar>
58
+ </input>
59
+ MESSAGE
60
+ end
61
+
62
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
63
+
64
+ it { should be_instance_of Input }
65
+
66
+ its(:grammar) { should == Input::Grammar.new(:value => '[5 DIGITS]', :content_type => 'application/grammar+custom') }
67
+ its(:mode) { should == :speech }
68
+ its(:terminator) { should == '#' }
69
+ its(:max_digits) { should == 10 }
70
+ its(:recognizer) { should == 'en-US' }
71
+ its(:initial_timeout) { should == 2000 }
72
+ its(:inter_digit_timeout) { should == 2000 }
73
+ its(:term_timeout) { should == 2000 }
74
+ its(:complete_timeout) { should == 2000 }
75
+ its(:incomplete_timeout) { should == 2000 }
76
+ its(:sensitivity) { should == 0.5 }
77
+ its(:min_confidence) { should == 0.5 }
78
+ end
79
+
40
80
  describe Input::Grammar do
41
81
  describe "when not passing a grammar" do
42
82
  subject { Input::Grammar.new :value => '[5 DIGITS]' }
@@ -27,6 +27,34 @@ module Punchblock
27
27
  its(:voice) { should == 'allison' }
28
28
  end
29
29
 
30
+ describe "from a stanza" do
31
+ let :stanza do
32
+ <<-MESSAGE
33
+ <output xmlns='urn:xmpp:rayo:output:1'
34
+ interrupt-on='speech'
35
+ start-offset='2000'
36
+ start-paused='false'
37
+ repeat-interval='2000'
38
+ repeat-times='10'
39
+ max-time='30000'
40
+ voice='allison'>Hello world</output>
41
+ MESSAGE
42
+ end
43
+
44
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
45
+
46
+ it { should be_instance_of Output }
47
+
48
+ its(:interrupt_on) { should == :speech }
49
+ its(:start_offset) { should == 2000 }
50
+ its(:start_paused) { should == false }
51
+ its(:repeat_interval) { should == 2000 }
52
+ its(:repeat_times) { should == 10 }
53
+ its(:max_time) { should == 30000 }
54
+ its(:voice) { should == 'allison' }
55
+ its(:text) { should == 'Hello world' }
56
+ end
57
+
30
58
  describe "for text" do
31
59
  subject { Output.new :text => 'Once upon a time there was a message...', :voice => 'kate' }
32
60
 
@@ -27,6 +27,33 @@ module Punchblock
27
27
  its(:final_timeout) { should == 30000 }
28
28
  end
29
29
 
30
+ describe "from a stanza" do
31
+ let :stanza do
32
+ <<-MESSAGE
33
+ <record xmlns="urn:xmpp:rayo:record:1"
34
+ format="WAV"
35
+ start-beep="true"
36
+ start-paused="false"
37
+ stop-beep="true"
38
+ max-duration="500000"
39
+ initial-timeout="10000"
40
+ final-timeout="30000"/>
41
+ MESSAGE
42
+ end
43
+
44
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
45
+
46
+ it { should be_instance_of Record }
47
+
48
+ its(:format) { should == 'WAV' }
49
+ its(:start_beep) { should == true }
50
+ its(:start_paused) { should == false }
51
+ its(:stop_beep) { should == true }
52
+ its(:max_duration) { should == 500000 }
53
+ its(:initial_timeout) { should == 10000 }
54
+ its(:final_timeout) { should == 30000 }
55
+ end
56
+
30
57
  describe "actions" do
31
58
  let(:mock_client) { mock 'Client' }
32
59
  let(:command) { Record.new }
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ module Connection
5
+ describe Asterisk do
6
+ let :options do
7
+ {
8
+ :host => '127.0.0.1',
9
+ :port => 5038,
10
+ :username => 'test',
11
+ :password => 'test'
12
+ }
13
+ end
14
+
15
+ let(:mock_event_handler) { stub_everything 'Event Handler' }
16
+
17
+ subject { Asterisk.new options }
18
+
19
+ before do
20
+ subject.event_handler = mock_event_handler
21
+ end
22
+
23
+ its(:ami_client) { should be_a RubyAMI::Client }
24
+
25
+ it 'should set the connection on the translator' do
26
+ subject.translator.connection.should be subject
27
+ end
28
+
29
+ describe '#run' do
30
+ it 'starts the RubyAMI::Client' do
31
+ subject.ami_client.expects(:start).once
32
+ subject.run
33
+ end
34
+ end
35
+
36
+ describe '#stop' do
37
+ it 'stops the RubyAMI::Client' do
38
+ subject.ami_client.expects(:stop).once
39
+ subject.stop
40
+ end
41
+ end
42
+
43
+ it 'sends events from RubyAMI to the translator' do
44
+ event = mock 'RubyAMI::Event'
45
+ subject.translator.expects(:handle_ami_event!).once.with event
46
+ subject.ami_client.handle_event event
47
+ end
48
+
49
+ describe '#write' do
50
+ it 'sends a command to the translator' do
51
+ command = mock 'Command'
52
+ options = {:foo => :bar}
53
+ subject.translator.expects(:execute_command!).once.with command, options
54
+ subject.write command, options
55
+ end
56
+ end
57
+
58
+ describe 'when a rayo event is received from the translator' do
59
+ it 'should call the event handler with the event' do
60
+ offer = Event::Offer.new
61
+ offer.call_id = '9f00061'
62
+
63
+ mock_event_handler.expects(:call).once.with offer
64
+ subject.handle_event offer
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ module Punchblock
4
+ class Event
5
+ module Asterisk
6
+ module AMI
7
+ describe Event do
8
+ it 'registers itself' do
9
+ RayoNode.class_from_registration(:event, 'urn:xmpp:rayo:asterisk:ami:1').should == Event
10
+ end
11
+
12
+ describe "from a stanza" do
13
+ let :stanza do
14
+ <<-MESSAGE
15
+ <event xmlns="urn:xmpp:rayo:asterisk:ami:1" name="Newchannel">
16
+ <attribute name="Channel" value="SIP/101-3f3f"/>
17
+ <attribute name="State" value="Ring"/>
18
+ <attribute name="Callerid" value="101"/>
19
+ <attribute name="Uniqueid" value="1094154427.10"/>
20
+ </event>
21
+ MESSAGE
22
+ end
23
+
24
+ subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
25
+
26
+ it { should be_instance_of Event }
27
+
28
+ it_should_behave_like 'event'
29
+
30
+ its(:name) { should == 'Newchannel' }
31
+ its(:attributes) { should == [Event::Attribute.new(:channel, 'SIP/101-3f3f'), Event::Attribute.new(:state, 'Ring'), Event::Attribute.new(:callerid, '101'), Event::Attribute.new(:uniqueid, '1094154427.10')]}
32
+ its(:attributes_hash) { should == {:channel => 'SIP/101-3f3f', :state => 'Ring', :callerid => '101', :uniqueid => '1094154427.10'} }
33
+ end
34
+
35
+ describe "when setting options in initializer" do
36
+ subject do
37
+ Event.new :name => 'Newchannel',
38
+ :attributes => {:channel => 'SIP/101-3f3f',
39
+ :state => 'Ring',
40
+ :callerid => '101',
41
+ :uniqueid => '1094154427.10'}
42
+ end
43
+
44
+ its(:name) { should == 'Newchannel' }
45
+ its(:attributes) { should == [Event::Attribute.new(:channel, 'SIP/101-3f3f'), Event::Attribute.new(:state, 'Ring'), Event::Attribute.new(:callerid, '101'), Event::Attribute.new(:uniqueid, '1094154427.10')]}
46
+ its(:attributes_hash) { should == {:channel => 'SIP/101-3f3f', :state => 'Ring', :callerid => '101', :uniqueid => '1094154427.10'} }
47
+ end
48
+
49
+ class Event
50
+ describe Attribute do
51
+ let(:class_name) { Attribute }
52
+ let(:element_name) { 'attribute' }
53
+ it_should_behave_like 'key_value_pairs'
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end # Punchblock