punchblock 0.6.1 → 0.6.2
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/CHANGELOG.md +4 -0
- data/lib/punchblock.rb +15 -13
- data/lib/punchblock/client.rb +1 -0
- data/lib/punchblock/component.rb +1 -1
- data/lib/punchblock/connection/asterisk.rb +2 -2
- data/lib/punchblock/connection/xmpp.rb +8 -9
- data/lib/punchblock/core_ext/ruby.rb +24 -0
- data/lib/punchblock/has_headers.rb +4 -0
- data/lib/punchblock/translator/asterisk.rb +40 -9
- data/lib/punchblock/translator/asterisk/call.rb +107 -3
- data/lib/punchblock/translator/asterisk/component.rb +9 -3
- data/lib/punchblock/translator/asterisk/component/asterisk.rb +14 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +84 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +96 -0
- data/lib/punchblock/version.rb +1 -1
- data/spec/punchblock/connection/xmpp_spec.rb +3 -1
- data/spec/punchblock/translator/asterisk/call_spec.rb +206 -0
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +143 -0
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +159 -0
- data/spec/punchblock/translator/asterisk_spec.rb +90 -10
- metadata +46 -41
- data/lib/punchblock/translator/asterisk/ami_action.rb +0 -86
- data/spec/punchblock/translator/asterisk/ami_action_spec.rb +0 -149
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module Punchblock
|
|
4
|
+
module Translator
|
|
5
|
+
class Asterisk
|
|
6
|
+
module Component
|
|
7
|
+
module Asterisk
|
|
8
|
+
class AGICommand < Component
|
|
9
|
+
attr_reader :action
|
|
10
|
+
|
|
11
|
+
def initialize(component_node, call)
|
|
12
|
+
@component_node, @call = component_node, call
|
|
13
|
+
@id = UUIDTools::UUID.random_create.to_s
|
|
14
|
+
@action = create_action
|
|
15
|
+
pb_logger.debug "Starting up..."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute
|
|
19
|
+
@call.send_ami_action! @action
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def handle_ami_event(event)
|
|
23
|
+
pb_logger.debug "Handling AMI event: #{event.inspect}"
|
|
24
|
+
if event.name == 'AsyncAGI'
|
|
25
|
+
if event['SubEvent'] == 'Exec'
|
|
26
|
+
pb_logger.debug "Received AsyncAGI:Exec event, sending complete event."
|
|
27
|
+
send_event complete_event(success_reason(event))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_agi_result(result)
|
|
33
|
+
match = URI.decode(result).chomp.match(/^(\d{3}) result=(-?\d*) ?(\(?.*\)?)?$/)
|
|
34
|
+
if match
|
|
35
|
+
data = match[3] ? match[3].gsub(/(^\()|(\)$)/, '') : nil
|
|
36
|
+
[match[1].to_i, match[2].to_i, data]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def create_action
|
|
43
|
+
RubyAMI::Action.new 'AGI', 'Channel' => @call.channel, 'Command' => @component_node.name, 'CommandID' => id do |response|
|
|
44
|
+
handle_response response
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def handle_response(response)
|
|
49
|
+
pb_logger.debug "Handling response: #{response.inspect}"
|
|
50
|
+
case response
|
|
51
|
+
when RubyAMI::Error
|
|
52
|
+
set_node_response false
|
|
53
|
+
when RubyAMI::Response
|
|
54
|
+
set_node_response Ref.new :id => id
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_node_response(value)
|
|
59
|
+
pb_logger.debug "Setting response on component node to #{value}"
|
|
60
|
+
@component_node.response = value
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def success_reason(event)
|
|
64
|
+
code, result, data = parse_agi_result event['Result']
|
|
65
|
+
Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new :code => code, :result => result, :data => data
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def complete_event(reason)
|
|
69
|
+
Punchblock::Event::Complete.new.tap do |c|
|
|
70
|
+
c.reason = reason
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def send_event(event)
|
|
75
|
+
event.component_id = id
|
|
76
|
+
pb_logger.debug "Sending event #{event.inspect}"
|
|
77
|
+
@component_node.add_event event
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Punchblock
|
|
2
|
+
module Translator
|
|
3
|
+
class Asterisk
|
|
4
|
+
module Component
|
|
5
|
+
module Asterisk
|
|
6
|
+
class AMIAction < Component
|
|
7
|
+
attr_reader :action
|
|
8
|
+
|
|
9
|
+
def initialize(component_node, translator)
|
|
10
|
+
@component_node, @translator = component_node, translator
|
|
11
|
+
@action = create_action
|
|
12
|
+
@id = @action.action_id
|
|
13
|
+
pb_logger.debug "Starting up..."
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute
|
|
17
|
+
send_action
|
|
18
|
+
send_ref
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def create_action
|
|
24
|
+
headers = {}
|
|
25
|
+
@component_node.params_hash.each_pair do |key, value|
|
|
26
|
+
headers[key.to_s.capitalize] = value
|
|
27
|
+
end
|
|
28
|
+
RubyAMI::Action.new @component_node.name, headers do |response|
|
|
29
|
+
handle_response response
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def send_action
|
|
34
|
+
@translator.send_ami_action! @action
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def send_ref
|
|
38
|
+
@component_node.response = Ref.new :id => @action.action_id
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def handle_response(response)
|
|
42
|
+
pb_logger.debug "Handling response #{response.inspect}"
|
|
43
|
+
case response
|
|
44
|
+
when RubyAMI::Error
|
|
45
|
+
send_event complete_event(error_reason(response))
|
|
46
|
+
when RubyAMI::Response
|
|
47
|
+
send_events
|
|
48
|
+
send_event complete_event(success_reason(response))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def error_reason(response)
|
|
53
|
+
Punchblock::Event::Complete::Error.new :details => response.message
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def success_reason(response)
|
|
57
|
+
headers = response.headers
|
|
58
|
+
headers.merge! @extra_complete_attributes if @extra_complete_attributes
|
|
59
|
+
headers.delete 'ActionID'
|
|
60
|
+
Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => headers.delete('Message'), :attributes => headers
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def complete_event(reason)
|
|
64
|
+
Punchblock::Event::Complete.new.tap do |c|
|
|
65
|
+
c.reason = reason
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def send_events
|
|
70
|
+
return unless @action.has_causal_events?
|
|
71
|
+
@action.events.each do |e|
|
|
72
|
+
if e.name.downcase == @action.causal_event_terminator_name
|
|
73
|
+
@extra_complete_attributes = e.headers
|
|
74
|
+
else
|
|
75
|
+
send_event pb_event_from_ami_event(e)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def pb_event_from_ami_event(ami_event)
|
|
81
|
+
headers = ami_event.headers
|
|
82
|
+
headers.delete 'ActionID'
|
|
83
|
+
Event::Asterisk::AMI::Event.new :name => ami_event.name, :attributes => headers
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def send_event(event)
|
|
87
|
+
event.component_id = id
|
|
88
|
+
pb_logger.debug "Sending event #{event}"
|
|
89
|
+
@component_node.add_event event
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/punchblock/version.rb
CHANGED
|
@@ -19,8 +19,10 @@ module Punchblock
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'should properly set the Blather logger' do
|
|
22
|
-
|
|
22
|
+
Punchblock.logger = :foo
|
|
23
|
+
XMPP.new :username => '1@call.rayo.net', :password => 1
|
|
23
24
|
Blather.logger.should be :foo
|
|
25
|
+
Punchblock.reset_logger
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
it "looking up original command by command ID" do
|
|
@@ -4,6 +4,66 @@ module Punchblock
|
|
|
4
4
|
module Translator
|
|
5
5
|
class Asterisk
|
|
6
6
|
describe Call do
|
|
7
|
+
let(:channel) { 'SIP/foo' }
|
|
8
|
+
let(:translator) { stub_everything 'Translator::Asterisk' }
|
|
9
|
+
let(:env) { "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A" }
|
|
10
|
+
let(:agi_env) do
|
|
11
|
+
{
|
|
12
|
+
:agi_request => 'async',
|
|
13
|
+
:agi_channel => 'SIP/1234-00000000',
|
|
14
|
+
:agi_language => 'en',
|
|
15
|
+
:agi_type => 'SIP',
|
|
16
|
+
:agi_uniqueid => '1320835995.0',
|
|
17
|
+
:agi_version => '1.8.4.1',
|
|
18
|
+
:agi_callerid => '5678',
|
|
19
|
+
:agi_calleridname => 'Jane Smith',
|
|
20
|
+
:agi_callingpres => '0',
|
|
21
|
+
:agi_callingani2 => '0',
|
|
22
|
+
:agi_callington => '0',
|
|
23
|
+
:agi_callingtns => '0',
|
|
24
|
+
:agi_dnid => '1000',
|
|
25
|
+
:agi_rdnis => 'unknown',
|
|
26
|
+
:agi_context => 'default',
|
|
27
|
+
:agi_extension => '1000',
|
|
28
|
+
:agi_priority => '1',
|
|
29
|
+
:agi_enhanced => '0.0',
|
|
30
|
+
:agi_accountcode => '',
|
|
31
|
+
:agi_threadid => '4366221312'
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
let :sip_headers do
|
|
36
|
+
{
|
|
37
|
+
:x_agi_request => 'async',
|
|
38
|
+
:x_agi_channel => 'SIP/1234-00000000',
|
|
39
|
+
:x_agi_language => 'en',
|
|
40
|
+
:x_agi_type => 'SIP',
|
|
41
|
+
:x_agi_uniqueid => '1320835995.0',
|
|
42
|
+
:x_agi_version => '1.8.4.1',
|
|
43
|
+
:x_agi_callerid => '5678',
|
|
44
|
+
:x_agi_calleridname => 'Jane Smith',
|
|
45
|
+
:x_agi_callingpres => '0',
|
|
46
|
+
:x_agi_callingani2 => '0',
|
|
47
|
+
:x_agi_callington => '0',
|
|
48
|
+
:x_agi_callingtns => '0',
|
|
49
|
+
:x_agi_dnid => '1000',
|
|
50
|
+
:x_agi_rdnis => 'unknown',
|
|
51
|
+
:x_agi_context => 'default',
|
|
52
|
+
:x_agi_extension => '1000',
|
|
53
|
+
:x_agi_priority => '1',
|
|
54
|
+
:x_agi_enhanced => '0.0',
|
|
55
|
+
:x_agi_accountcode => '',
|
|
56
|
+
:x_agi_threadid => '4366221312'
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
subject { Call.new channel, translator, env }
|
|
61
|
+
|
|
62
|
+
its(:id) { should be_a String }
|
|
63
|
+
its(:channel) { should == channel }
|
|
64
|
+
its(:translator) { should be translator }
|
|
65
|
+
its(:agi_env) { should == agi_env }
|
|
66
|
+
|
|
7
67
|
describe '#register_component' do
|
|
8
68
|
it 'should make the component accessible by ID' do
|
|
9
69
|
component_id = 'abc123'
|
|
@@ -12,6 +72,152 @@ module Punchblock
|
|
|
12
72
|
subject.component_with_id(component_id).should be component
|
|
13
73
|
end
|
|
14
74
|
end
|
|
75
|
+
|
|
76
|
+
describe '#send_offer' do
|
|
77
|
+
it 'sends an offer to the translator' do
|
|
78
|
+
expected_offer = Punchblock::Event::Offer.new :call_id => subject.id,
|
|
79
|
+
:to => '1000',
|
|
80
|
+
:from => 'sip:5678',
|
|
81
|
+
:headers => sip_headers
|
|
82
|
+
translator.expects(:handle_pb_event!).with expected_offer
|
|
83
|
+
subject.send_offer
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe '#process_ami_event' do
|
|
88
|
+
context 'with a Hangup event' do
|
|
89
|
+
let :ami_event do
|
|
90
|
+
RubyAMI::Event.new('Hangup').tap do |e|
|
|
91
|
+
e['Uniqueid'] = "1320842458.8"
|
|
92
|
+
e['Calleridnum'] = "5678"
|
|
93
|
+
e['Calleridname'] = "Jane Smith"
|
|
94
|
+
e['Cause'] = "0"
|
|
95
|
+
e['Cause-txt'] = "Unknown"
|
|
96
|
+
e['Channel'] = "SIP/1234-00000000"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'should send an end event to the translator' do
|
|
101
|
+
expected_end_event = Punchblock::Event::End.new :reason => :hangup,
|
|
102
|
+
:call_id => subject.id
|
|
103
|
+
translator.expects(:handle_pb_event!).with expected_end_event
|
|
104
|
+
subject.process_ami_event ami_event
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'with an event for a known AGI command component' do
|
|
109
|
+
let(:mock_component_node) { mock 'Punchblock::Component::Asterisk::AGI::Command', :name => 'EXEC ANSWER' }
|
|
110
|
+
let :component do
|
|
111
|
+
Component::Asterisk::AGICommand.new mock_component_node, subject.translator
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
let(:ami_event) do
|
|
115
|
+
RubyAMI::Event.new("AGIExec").tap do |e|
|
|
116
|
+
e["SubEvent"] = "End"
|
|
117
|
+
e["Channel"] = "SIP/1234-00000000"
|
|
118
|
+
e["CommandId"] = component.id
|
|
119
|
+
e["Command"] = "EXEC ANSWER"
|
|
120
|
+
e["ResultCode"] = "200"
|
|
121
|
+
e["Result"] = "Success"
|
|
122
|
+
e["Data"] = "FOO"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
before do
|
|
127
|
+
subject.register_component component
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'should send the event to the component' do
|
|
131
|
+
component.expects(:handle_ami_event!).once.with ami_event
|
|
132
|
+
subject.process_ami_event ami_event
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe '#execute_command' do
|
|
138
|
+
let :expected_agi_complete_event do
|
|
139
|
+
Punchblock::Event::Complete.new.tap do |c|
|
|
140
|
+
c.reason = Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new :code => 200,
|
|
141
|
+
:result => 'Success',
|
|
142
|
+
:data => 'FOO'
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
before do
|
|
147
|
+
command.request!
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
context 'with an accept command' do
|
|
151
|
+
let(:command) { Command::Accept.new }
|
|
152
|
+
|
|
153
|
+
it "should send an EXEC RINGING AGI command and set the command's response" do
|
|
154
|
+
subject.execute_command command
|
|
155
|
+
agi_command = subject.actor_subject.instance_variable_get(:'@current_agi_command')
|
|
156
|
+
agi_command.name.should == "EXEC RINGING"
|
|
157
|
+
agi_command.execute!
|
|
158
|
+
agi_command.add_event expected_agi_complete_event
|
|
159
|
+
command.response(0.5).should be true
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context 'with an answer command' do
|
|
164
|
+
let(:command) { Command::Answer.new }
|
|
165
|
+
|
|
166
|
+
it "should send an EXEC ANSWER AGI command and set the command's response" do
|
|
167
|
+
subject.execute_command command
|
|
168
|
+
agi_command = subject.actor_subject.instance_variable_get(:'@current_agi_command')
|
|
169
|
+
agi_command.name.should == "EXEC ANSWER"
|
|
170
|
+
agi_command.execute!
|
|
171
|
+
agi_command.add_event expected_agi_complete_event
|
|
172
|
+
command.response(0.5).should be true
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context 'with a hangup command' do
|
|
177
|
+
let(:command) { Command::Hangup.new }
|
|
178
|
+
|
|
179
|
+
it "should send a Hangup AMI command and set the command's response" do
|
|
180
|
+
subject.execute_command command
|
|
181
|
+
ami_action = subject.actor_subject.instance_variable_get(:'@current_ami_action')
|
|
182
|
+
ami_action.name.should == "hangup"
|
|
183
|
+
ami_action << RubyAMI::Response.new
|
|
184
|
+
command.response(0.5).should be true
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
context 'with a component' do
|
|
189
|
+
let :command do
|
|
190
|
+
Punchblock::Component::Asterisk::AGI::Command.new :name => 'Answer'
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
let(:mock_action) { mock 'Component::Asterisk::AGI::Command', :id => 'foo' }
|
|
194
|
+
|
|
195
|
+
it 'should create a component actor and execute it asynchronously' do
|
|
196
|
+
Component::Asterisk::AGICommand.expects(:new).once.with(command, subject).returns mock_action
|
|
197
|
+
mock_action.expects(:execute!).once
|
|
198
|
+
subject.execute_command command
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe '#send_agi_action' do
|
|
204
|
+
it 'should send an appropriate AsyncAGI AMI action' do
|
|
205
|
+
pending
|
|
206
|
+
subject.actor_subject.expects(:send_ami_action).once.with('AGI', 'Command' => 'FOO', 'Channel' => subject.channel)
|
|
207
|
+
subject.send_agi_action 'FOO'
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe '#send_ami_action' do
|
|
212
|
+
let(:component_id) { UUIDTools::UUID.random_create }
|
|
213
|
+
before { UUIDTools::UUID.stubs :random_create => component_id }
|
|
214
|
+
|
|
215
|
+
it 'should send the action to the AMI client' do
|
|
216
|
+
action = RubyAMI::Action.new 'foo', :foo => :bar
|
|
217
|
+
translator.expects(:send_ami_action!).once.with action
|
|
218
|
+
subject.send_ami_action 'foo', :foo => :bar
|
|
219
|
+
end
|
|
220
|
+
end
|
|
15
221
|
end
|
|
16
222
|
end
|
|
17
223
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module Punchblock
|
|
4
|
+
module Translator
|
|
5
|
+
class Asterisk
|
|
6
|
+
module Component
|
|
7
|
+
module Asterisk
|
|
8
|
+
describe AGICommand do
|
|
9
|
+
let(:channel) { 'SIP/foo' }
|
|
10
|
+
let(:mock_call) { mock 'Call', :channel => channel }
|
|
11
|
+
let(:component_id) { UUIDTools::UUID.random_create }
|
|
12
|
+
|
|
13
|
+
before { UUIDTools::UUID.stubs :random_create => component_id }
|
|
14
|
+
|
|
15
|
+
let :command do
|
|
16
|
+
Punchblock::Component::Asterisk::AGI::Command.new :name => 'EXEC ANSWER'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
subject { AGICommand.new command, mock_call }
|
|
20
|
+
|
|
21
|
+
let :expected_action do
|
|
22
|
+
RubyAMI::Action.new 'AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => component_id
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context 'initial execution' do
|
|
26
|
+
it 'should send the appropriate RubyAMI::Action' do
|
|
27
|
+
mock_call.expects(:send_ami_action!).once.with(expected_action).returns(expected_action)
|
|
28
|
+
subject.execute
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'when the AMI action completes' do
|
|
33
|
+
before do
|
|
34
|
+
command.request!
|
|
35
|
+
command.execute!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
let :expected_response do
|
|
39
|
+
Ref.new :id => component_id
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
let :response do
|
|
43
|
+
RubyAMI::Response.new.tap do |r|
|
|
44
|
+
r['ActionID'] = "552a9d9f-46d7-45d8-a257-06fe95f48d99"
|
|
45
|
+
r['Message'] = 'Added AGI command to queue'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'should send the component node a ref with the action ID' do
|
|
50
|
+
command.expects(:response=).once.with(expected_response)
|
|
51
|
+
subject.action << response
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'with an error' do
|
|
55
|
+
let :error do
|
|
56
|
+
RubyAMI::Error.new.tap { |e| e.message = 'Action failed' }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'should send the component node false' do
|
|
60
|
+
command.expects(:response=).once.with false
|
|
61
|
+
subject.action << error
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe 'when receiving an AsyncAGI event' do
|
|
67
|
+
before do
|
|
68
|
+
command.request!
|
|
69
|
+
command.execute!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'of type start'
|
|
73
|
+
|
|
74
|
+
context 'of type Exec' do
|
|
75
|
+
let(:ami_event) do
|
|
76
|
+
RubyAMI::Event.new("AsyncAGI").tap do |e|
|
|
77
|
+
e["SubEvent"] = "Exec"
|
|
78
|
+
e["Channel"] = channel
|
|
79
|
+
e["CommandId"] = component_id
|
|
80
|
+
e["Command"] = "EXEC ANSWER"
|
|
81
|
+
e["Result"] = "200%20result=123%20(timeout)%0A"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
let :expected_complete_reason do
|
|
86
|
+
Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new :code => 200,
|
|
87
|
+
:result => 123,
|
|
88
|
+
:data => 'timeout'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'should send a complete event' do
|
|
92
|
+
subject.handle_ami_event ami_event
|
|
93
|
+
|
|
94
|
+
command.should be_complete
|
|
95
|
+
|
|
96
|
+
complete_event = command.complete_event.resource(0.5)
|
|
97
|
+
|
|
98
|
+
complete_event.component_id.should == subject.id
|
|
99
|
+
complete_event.reason.should == expected_complete_reason
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe '#parse_agi_result' do
|
|
105
|
+
context 'with a simple result with no data' do
|
|
106
|
+
let(:result_string) { "200%20result=123%0A" }
|
|
107
|
+
|
|
108
|
+
it 'should provide the code and result' do
|
|
109
|
+
code, result, data = subject.parse_agi_result result_string
|
|
110
|
+
code.should == 200
|
|
111
|
+
result.should == 123
|
|
112
|
+
data.should == ''
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
context 'with a result and data in parens' do
|
|
117
|
+
let(:result_string) { "200%20result=-123%20(timeout)%0A" }
|
|
118
|
+
|
|
119
|
+
it 'should provide the code and result' do
|
|
120
|
+
code, result, data = subject.parse_agi_result result_string
|
|
121
|
+
code.should == 200
|
|
122
|
+
result.should == -123
|
|
123
|
+
data.should == 'timeout'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context 'with a result and key-value data' do
|
|
128
|
+
let(:result_string) { "200%20result=123%20foo=bar%0A" }
|
|
129
|
+
|
|
130
|
+
it 'should provide the code and result' do
|
|
131
|
+
code, result, data = subject.parse_agi_result result_string
|
|
132
|
+
code.should == 200
|
|
133
|
+
result.should == 123
|
|
134
|
+
data.should == 'foo=bar'
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|