punchblock 2.6.0 → 2.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/punchblock/component/output.rb +12 -4
- data/lib/punchblock/translator/asterisk.rb +3 -4
- data/lib/punchblock/translator/asterisk/agi_command.rb +3 -0
- data/lib/punchblock/translator/asterisk/call.rb +53 -7
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/input.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +14 -2
- data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +39 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +1 -1
- data/lib/punchblock/version.rb +1 -1
- data/spec/punchblock/translator/asterisk/call_spec.rb +250 -35
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +55 -2
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +24 -5
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +26 -5
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +42 -2
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +479 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +40 -17
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +4 -3
- data/spec/punchblock/translator/asterisk_spec.rb +36 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 818e14c3ff0bfcf90e7e8087256855a16a1298f4
|
4
|
+
data.tar.gz: e0702027282797f03c8c02d2550f94de4b99e4e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8865f044c3d41d8c4410f655fca8c2271a49e29b726bfb6de36a8df936a592a061bd9bf73a6c22e35fe2bc7bfeea478a21b5969b54a218aa1e93d1bc6e43a70
|
7
|
+
data.tar.gz: 2951eeea8e8ddf297280b5e488a33768e24a09eae4a0712f144a27a4d8de696d7d1969742493d90e203db9745342714590df5cf44178587be7ddbbc1a84126de
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# [develop](https://github.com/adhearsion/punchblock)
|
2
2
|
|
3
|
+
# [v2.7.0](https://github.com/adhearsion/punchblock/compare/v2.6.0...v2.7.0) - [2015-06-09](https://rubygems.org/gems/punchblock/versions/2.7.0)
|
4
|
+
* Feature: Support for Asterisk 13 (AMI v2)
|
5
|
+
* Feature: Pass all possible MRCP recognition headers through Asterisk [#244](https://github.com/adhearsion/punchblock/pull/244)
|
6
|
+
* Bugfix: Handle an illegal AMI response by Asterisk as a failure with code -1
|
7
|
+
* Bugfix: Ensure a useful error is raised when attempting to join to a call which doesn't exist [#529](https://github.com/adhearsion/adhearsion/issues/529)
|
8
|
+
* Bugfix: Support SSML in MRCPRecog case on Asterisk. This is the case where a prompt component wants to render using Asterisk and recognise using UniMRCP. This worked fine with a uri-list which already has test coverage, but threw (and silently swallowed) an exception for an SSML doc (as provided by Adhearsion) resulting in a hung call with no audio, and then a timeout exception in Adhearsion. [#241](https://github.com/adhearsion/punchblock/pull/241)
|
9
|
+
|
3
10
|
# [v2.6.0](https://github.com/adhearsion/punchblock/compare/v2.5.3...v2.6.0) - [2015-02-01](https://rubygems.org/gems/punchblock/versions/2.6.0)
|
4
11
|
* Feature: Support recognition-timeout settings on UniMRCP-based ASR on Asterisk ([#228](https://github.com/adhearsion/punchblock/pull/228))
|
5
12
|
* Feature: Implement redirect command on Asterisk ([#219](https://github.com/adhearsion/punchblock/pull/219))
|
@@ -46,6 +46,18 @@ module Punchblock
|
|
46
46
|
super
|
47
47
|
end
|
48
48
|
|
49
|
+
def size
|
50
|
+
if ssml?
|
51
|
+
value.children.count
|
52
|
+
else
|
53
|
+
value.size
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def ssml?
|
58
|
+
content_type == SSML_CONTENT_TYPE
|
59
|
+
end
|
60
|
+
|
49
61
|
private
|
50
62
|
|
51
63
|
def xml_value
|
@@ -58,10 +70,6 @@ module Punchblock
|
|
58
70
|
end
|
59
71
|
end
|
60
72
|
|
61
|
-
def ssml?
|
62
|
-
content_type == SSML_CONTENT_TYPE
|
63
|
-
end
|
64
|
-
|
65
73
|
def urilist?
|
66
74
|
content_type == 'text/uri-list'
|
67
75
|
end
|
@@ -18,7 +18,7 @@ module Punchblock
|
|
18
18
|
autoload :Channel
|
19
19
|
autoload :Component
|
20
20
|
|
21
|
-
attr_reader :ami_client, :connection, :calls
|
21
|
+
attr_reader :ami_client, :connection, :calls, :bridges
|
22
22
|
|
23
23
|
REDIRECT_CONTEXT = 'adhearsion-redirect'
|
24
24
|
REDIRECT_EXTENSION = '1'
|
@@ -47,7 +47,7 @@ module Punchblock
|
|
47
47
|
|
48
48
|
def initialize(ami_client, connection)
|
49
49
|
@ami_client, @connection = ami_client, connection
|
50
|
-
@calls, @components, @channel_to_call_id = {}, {}, {}
|
50
|
+
@calls, @components, @channel_to_call_id, @bridges = {}, {}, {}, {}
|
51
51
|
end
|
52
52
|
|
53
53
|
def register_call(call)
|
@@ -88,7 +88,6 @@ module Punchblock
|
|
88
88
|
handle_varset_ami_event event
|
89
89
|
|
90
90
|
ami_dispatch_to_or_create_call event
|
91
|
-
|
92
91
|
if !ami_event_known_call?(event) && self.class.event_passes_filter?(event)
|
93
92
|
handle_pb_event Event::Asterisk::AMI::Event.new(name: event.name, headers: event.headers)
|
94
93
|
end
|
@@ -208,7 +207,7 @@ module Punchblock
|
|
208
207
|
next if channel.bridged? && !EVENTS_ALLOWED_BRIDGED.include?(event.name)
|
209
208
|
call.process_ami_event event
|
210
209
|
end
|
211
|
-
elsif event.name == "AsyncAGI" && event['SubEvent'] == "Start"
|
210
|
+
elsif event.name == "AsyncAGIStart" || (event.name == "AsyncAGI" && event['SubEvent'] == "Start")
|
212
211
|
handle_async_agi_start_event event
|
213
212
|
end
|
214
213
|
end
|
@@ -23,6 +23,9 @@ module Punchblock
|
|
23
23
|
def parse_result(event)
|
24
24
|
parser = RubyAMI::AGIResultParser.new event['Result']
|
25
25
|
{code: parser.code, result: parser.result, data: parser.data}
|
26
|
+
rescue ArgumentError => e
|
27
|
+
pb_logger.warn "Illegal message received from Asterisk: #{e.message}"
|
28
|
+
{code: -1, result: nil, data: nil}
|
26
29
|
end
|
27
30
|
|
28
31
|
private
|
@@ -13,7 +13,7 @@ module Punchblock
|
|
13
13
|
|
14
14
|
OUTBOUND_CHANNEL_MATCH = /.* <(?<channel>.*)>/.freeze
|
15
15
|
|
16
|
-
attr_reader :id, :channel, :translator, :agi_env, :direction
|
16
|
+
attr_reader :id, :channel, :translator, :agi_env, :direction, :pending_joins
|
17
17
|
|
18
18
|
HANGUP_CAUSE_TO_END_REASON = Hash.new { :error }
|
19
19
|
HANGUP_CAUSE_TO_END_REASON[0] = :hungup
|
@@ -116,19 +116,55 @@ module Punchblock
|
|
116
116
|
when 'Hangup'
|
117
117
|
handle_hangup_event ami_event['Cause'].to_i, ami_event.best_time
|
118
118
|
when 'AsyncAGI'
|
119
|
-
|
120
|
-
component.handle_ami_event ami_event
|
121
|
-
end
|
119
|
+
component_for_command_id_handle ami_event
|
122
120
|
|
123
121
|
if @answered == false && ami_event['SubEvent'] == 'Start'
|
124
122
|
@answered = true
|
125
123
|
send_pb_event Event::Answered.new(timestamp: ami_event.best_time)
|
126
124
|
end
|
125
|
+
when 'AsyncAGIStart'
|
126
|
+
component_for_command_id_handle ami_event
|
127
|
+
|
128
|
+
if @answered == false
|
129
|
+
@answered = true
|
130
|
+
send_pb_event Event::Answered.new(timestamp: ami_event.best_time)
|
131
|
+
end
|
132
|
+
when 'AsyncAGIExec', 'AsyncAGIEnd'
|
133
|
+
component_for_command_id_handle ami_event
|
127
134
|
when 'Newstate'
|
128
135
|
case ami_event['ChannelState']
|
129
136
|
when '5'
|
130
137
|
send_pb_event Event::Ringing.new(timestamp: ami_event.best_time)
|
131
138
|
end
|
139
|
+
when 'BridgeEnter'
|
140
|
+
if other_call_channel = translator.bridges.delete(ami_event['BridgeUniqueid'])
|
141
|
+
if other_call = translator.call_for_channel(other_call_channel)
|
142
|
+
join_command = other_call.pending_joins.delete channel
|
143
|
+
join_command.response = true if join_command
|
144
|
+
|
145
|
+
event = Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
|
146
|
+
send_pb_event event
|
147
|
+
|
148
|
+
other_call_event = Event::Joined.new call_uri: id, timestamp: ami_event.best_time
|
149
|
+
other_call_event.target_call_id = other_call.id
|
150
|
+
translator.handle_pb_event other_call_event
|
151
|
+
end
|
152
|
+
else
|
153
|
+
translator.bridges[ami_event['BridgeUniqueid']] = ami_event['Channel']
|
154
|
+
end
|
155
|
+
when 'BridgeLeave'
|
156
|
+
if other_call_channel = translator.bridges.delete(ami_event['BridgeUniqueid'] + '_leave')
|
157
|
+
if other_call = translator.call_for_channel(other_call_channel)
|
158
|
+
event = Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
|
159
|
+
send_pb_event event
|
160
|
+
|
161
|
+
other_call_event = Event::Unjoined.new call_uri: id, timestamp: ami_event.best_time
|
162
|
+
other_call_event.target_call_id = other_call.id
|
163
|
+
translator.handle_pb_event other_call_event
|
164
|
+
end
|
165
|
+
else
|
166
|
+
translator.bridges[ami_event['BridgeUniqueid'] + '_leave'] = ami_event['Channel']
|
167
|
+
end
|
132
168
|
when 'OriginateResponse'
|
133
169
|
if ami_event['Response'] == 'Failure' && ami_event['Uniqueid'] == '<null>'
|
134
170
|
send_end_event :error, nil, ami_event.best_time
|
@@ -194,8 +230,12 @@ module Punchblock
|
|
194
230
|
command.response = true
|
195
231
|
when Command::Join
|
196
232
|
other_call = translator.call_with_id command.call_uri
|
197
|
-
|
198
|
-
|
233
|
+
if other_call
|
234
|
+
@pending_joins[other_call.channel] = command
|
235
|
+
execute_agi_command 'EXEC Bridge', "#{other_call.channel},F(#{REDIRECT_CONTEXT},#{REDIRECT_EXTENSION},#{REDIRECT_PRIORITY})"
|
236
|
+
else
|
237
|
+
command.response = ProtocolError.new.setup :service_unavailable, "Could not find join party with address #{command.call_uri}", id
|
238
|
+
end
|
199
239
|
when Command::Unjoin
|
200
240
|
other_call = translator.call_with_id command.call_uri
|
201
241
|
redirect_back other_call
|
@@ -264,7 +304,7 @@ module Punchblock
|
|
264
304
|
def execute_agi_command(command, *params)
|
265
305
|
agi = AGICommand.new Punchblock.new_uuid, channel, command, *params
|
266
306
|
response = Celluloid::Future.new
|
267
|
-
register_tmp_handler :ami, name: 'AsyncAGI', [:[], 'SubEvent'] => 'Exec', [:[], 'CommandID'] => agi.id do |event|
|
307
|
+
register_tmp_handler :ami, [{name: 'AsyncAGI', [:[], 'SubEvent'] => 'Exec'}, {name: 'AsyncAGIExec'}], [{[:[], 'CommandID'] => agi.id}, {[:[], 'CommandId'] => agi.id}] do |event|
|
268
308
|
response.signal Celluloid::SuccessResponse.new(nil, event)
|
269
309
|
end
|
270
310
|
agi.execute @ami_client
|
@@ -353,6 +393,12 @@ module Punchblock
|
|
353
393
|
end
|
354
394
|
end
|
355
395
|
|
396
|
+
def component_for_command_id_handle(ami_event)
|
397
|
+
if component = component_with_id(ami_event['CommandID'] || ami_event['CommandId'])
|
398
|
+
component.handle_ami_event ami_event
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
356
402
|
def variable_for_headers(headers)
|
357
403
|
variables = { :punchblock_call_id => id }
|
358
404
|
header_counter = 51
|
@@ -20,7 +20,7 @@ module Punchblock
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def handle_ami_event(event)
|
23
|
-
if event.name == 'AsyncAGI' && event['SubEvent'] == 'Exec'
|
23
|
+
if (event.name == 'AsyncAGI' && event['SubEvent'] == 'Exec') || event.name == 'AsyncAGIExec'
|
24
24
|
send_complete_event success_reason(event)
|
25
25
|
if @component_node.name == 'ASYNCAGI BREAK' && @call.channel_var('PUNCHBLOCK_END_ON_ASYNCAGI_BREAK')
|
26
26
|
@call.handle_hangup_event nil, event.best_time
|
@@ -58,7 +58,7 @@ module Punchblock
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def register_dtmf_event_handler
|
61
|
-
@dtmf_handler_id = call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
|
61
|
+
@dtmf_handler_id = call.register_handler :ami, [{:name => 'DTMF', [:[], 'End'] => 'Yes'}, {:name => 'DTMFEnd'}] do |event|
|
62
62
|
process_dtmf event['Digit']
|
63
63
|
end
|
64
64
|
end
|
@@ -17,7 +17,7 @@ module Punchblock
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def register_dtmf_event_handler
|
20
|
-
call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
|
20
|
+
call.register_handler :ami, [{:name => 'DTMF', [:[], 'End'] => 'Yes'}, {:name => 'DTMFEnd'}] do |event|
|
21
21
|
process_dtmf event['Digit']
|
22
22
|
end
|
23
23
|
end
|
@@ -17,7 +17,7 @@ module Punchblock
|
|
17
17
|
raise OptionError, 'A document is required.' unless output_node.render_documents.count > 0
|
18
18
|
raise OptionError, 'Only one document is allowed.' if output_node.render_documents.count > 1
|
19
19
|
raise OptionError, 'Only inline documents are allowed.' if first_doc.url
|
20
|
-
raise OptionError, 'Only one audio file is allowed.' if first_doc.
|
20
|
+
raise OptionError, 'Only one audio file is allowed.' if first_doc.size > 1
|
21
21
|
|
22
22
|
raise OptionError, 'A grammar is required.' unless input_node.grammars.count > 0
|
23
23
|
|
@@ -41,7 +41,19 @@ module Punchblock
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def audio_filename
|
44
|
-
first_doc.
|
44
|
+
path = if first_doc.ssml?
|
45
|
+
first_doc.value.children.first.src
|
46
|
+
else
|
47
|
+
first_doc.value.first
|
48
|
+
end.sub('file://', '')
|
49
|
+
|
50
|
+
dir = File.dirname(path)
|
51
|
+
basename = File.basename(path, '.*')
|
52
|
+
if dir == '.'
|
53
|
+
basename
|
54
|
+
else
|
55
|
+
File.join(dir, basename)
|
56
|
+
end
|
45
57
|
end
|
46
58
|
|
47
59
|
def unimrcp_app_options
|
@@ -45,6 +45,13 @@ module Punchblock
|
|
45
45
|
raise OptionError, "An initial-timeout value must be -1 or a positive integer." if @initial_timeout < -1
|
46
46
|
raise OptionError, "An inter-digit-timeout value must be -1 or a positive integer." if @inter_digit_timeout < -1
|
47
47
|
raise OptionError, "A recognition-timeout value must be -1, 0, or a positive integer." if @recognition_timeout < -1
|
48
|
+
raise OptionError, "A max-silence value must be -1, 0, or a positive integer." if @max_silence < -1
|
49
|
+
raise OptionError, "A speech-complete-timeout value must be -1, 0, or a positive integer." if @speech_complete_timeout < -1
|
50
|
+
raise OptionError, "A hotword-max-duration value must be -1, 0, or a positive integer." if @hotword_max_duration < -1
|
51
|
+
raise OptionError, "A hotword-min-duration value must be -1, 0, or a positive integer." if @hotword_min_duration < -1
|
52
|
+
raise OptionError, "A dtmf-terminate-timeout value must be -1, 0, or a positive integer." if @dtmf_terminate_timeout < -1
|
53
|
+
raise OptionError, "An n-best-list-length value must be a positive integer." if @n_best_list_length && @n_best_list_length < 1
|
54
|
+
raise OptionError, "A speed-vs-accuracy value must be a positive integer." if @speed_vs_accuracy && @speed_vs_accuracy < 0
|
48
55
|
end
|
49
56
|
|
50
57
|
def execute_app(app, *args)
|
@@ -60,6 +67,23 @@ module Punchblock
|
|
60
67
|
opts[:ct] = input_node.min_confidence if input_node.min_confidence
|
61
68
|
opts[:sl] = input_node.sensitivity if input_node.sensitivity
|
62
69
|
opts[:t] = input_node.recognition_timeout if @recognition_timeout > -1
|
70
|
+
opts[:sint] = input_node.max_silence if @max_silence > -1
|
71
|
+
opts[:sct] = @speech_complete_timeout if @speech_complete_timeout > -1
|
72
|
+
|
73
|
+
opts[:sva] = @speed_vs_accuracy if @speed_vs_accuracy
|
74
|
+
opts[:nb] = @n_best_list_length if @n_best_list_length
|
75
|
+
opts[:sit] = @start_input_timers unless @start_input_timers.nil?
|
76
|
+
opts[:dtt] = @dtmf_terminate_timeout if @dtmf_terminate_timeout > -1
|
77
|
+
opts[:sw] = @save_waveform unless @save_waveform.nil?
|
78
|
+
opts[:nac] = @new_audio_channel unless @new_audio_channel.nil?
|
79
|
+
opts[:rm] = @recognition_mode if @recognition_mode
|
80
|
+
opts[:hmaxd] = @hotword_max_duration if @hotword_max_duration > -1
|
81
|
+
opts[:hmind] = @hotword_min_duration if @hotword_min_duration > -1
|
82
|
+
opts[:cdb] = @clear_dtmf_buffer unless @clear_dtmf_buffer.nil?
|
83
|
+
opts[:enm] = @early_no_match unless @early_no_match.nil?
|
84
|
+
opts[:iwu] = @input_waveform_uri if @input_waveform_uri
|
85
|
+
opts[:mt] = @media_type if @media_type
|
86
|
+
|
63
87
|
yield opts
|
64
88
|
end
|
65
89
|
end
|
@@ -68,6 +92,21 @@ module Punchblock
|
|
68
92
|
@initial_timeout = input_node.initial_timeout || -1
|
69
93
|
@inter_digit_timeout = input_node.inter_digit_timeout || -1
|
70
94
|
@recognition_timeout = input_node.recognition_timeout || -1
|
95
|
+
@max_silence = input_node.max_silence || -1
|
96
|
+
@speech_complete_timeout = input_node.headers['Speech-Complete-Timeout'] || -1
|
97
|
+
@speed_vs_accuracy = input_node.headers['Speed-Vs-Accuracy']
|
98
|
+
@n_best_list_length = input_node.headers['N-Best-List-Length']
|
99
|
+
@start_input_timers = input_node.headers['Start-Input-Timers']
|
100
|
+
@dtmf_terminate_timeout = input_node.headers['DTMF-Terminate-Timeout'] || -1
|
101
|
+
@save_waveform = input_node.headers['Save-Waveform']
|
102
|
+
@new_audio_channel = input_node.headers['New-Audio-Channel']
|
103
|
+
@recognition_mode = input_node.headers['Recognition-Mode']
|
104
|
+
@hotword_max_duration = input_node.headers['Hotword-Max-Duration'] || -1
|
105
|
+
@hotword_min_duration = input_node.headers['Hotword-Min-Duration'] || -1
|
106
|
+
@clear_dtmf_buffer = input_node.headers['Clear-DTMF-Buffer']
|
107
|
+
@early_no_match = input_node.headers['Early-No-Match']
|
108
|
+
@input_waveform_uri = input_node.headers['Input-Waveform-URI']
|
109
|
+
@media_type = input_node.headers['Media-Type']
|
71
110
|
end
|
72
111
|
|
73
112
|
def grammars
|
@@ -107,7 +107,7 @@ module Punchblock
|
|
107
107
|
send_progress_if_necessary
|
108
108
|
|
109
109
|
if interrupt
|
110
|
-
call.register_handler :ami, :name => 'DTMF', [:[], 'End'] => 'Yes' do |event|
|
110
|
+
call.register_handler :ami, [{:name => 'DTMF', [:[], 'End'] => 'Yes'}, {:name => 'DTMFEnd'}] do |event|
|
111
111
|
stop_by_redirect finish_reason
|
112
112
|
end
|
113
113
|
end
|
@@ -18,7 +18,7 @@ module Punchblock
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def stop_by_redirect(complete_reason)
|
21
|
-
call.register_handler :ami,
|
21
|
+
call.register_handler :ami, [{name: 'AsyncAGI', [:[], 'SubEvent'] => 'Start'}, {name: 'AsyncAGIExec'}] do |event|
|
22
22
|
send_complete_event complete_reason
|
23
23
|
end
|
24
24
|
call.redirect_back
|
data/lib/punchblock/version.rb
CHANGED
@@ -516,31 +516,80 @@ module Punchblock
|
|
516
516
|
let :component do
|
517
517
|
Component::Asterisk::AGICommand.new mock_component_node, subject
|
518
518
|
end
|
519
|
-
|
520
|
-
let(:ami_event) do
|
521
|
-
RubyAMI::Event.new "AsyncAGI",
|
522
|
-
"SubEvent" => "End",
|
523
|
-
"Channel" => "SIP/1234-00000000",
|
524
|
-
"CommandID" => component.id,
|
525
|
-
"Command" => "EXEC ANSWER",
|
526
|
-
"Result" => "200%20result=123%20(timeout)%0A"
|
527
|
-
end
|
528
|
-
|
529
519
|
before do
|
530
520
|
subject.register_component component
|
531
521
|
end
|
532
522
|
|
533
|
-
|
534
|
-
|
535
|
-
|
523
|
+
context 'with Asterisk 11 AsyncAGI SubEvent' do
|
524
|
+
let(:ami_event) do
|
525
|
+
RubyAMI::Event.new "AsyncAGI",
|
526
|
+
"SubEvent" => "End",
|
527
|
+
"Channel" => "SIP/1234-00000000",
|
528
|
+
"CommandID" => component.id,
|
529
|
+
"Command" => "EXEC ANSWER",
|
530
|
+
"Result" => "200%20result=123%20(timeout)%0A"
|
531
|
+
end
|
532
|
+
|
533
|
+
it 'should send the event to the component' do
|
534
|
+
expect(component).to receive(:handle_ami_event).once.with ami_event
|
535
|
+
subject.process_ami_event ami_event
|
536
|
+
end
|
537
|
+
|
538
|
+
it 'should not send an answered event' do
|
539
|
+
expect(translator).to receive(:handle_pb_event).with(kind_of(Punchblock::Event::Answered)).never
|
540
|
+
subject.process_ami_event ami_event
|
541
|
+
end
|
536
542
|
end
|
537
543
|
|
538
|
-
|
539
|
-
|
540
|
-
|
544
|
+
context 'with Asterisk 13 AsyncAGIEnd and CommandId with a lowercase d' do
|
545
|
+
let(:ami_event) do
|
546
|
+
RubyAMI::Event.new "AsyncAGIEnd",
|
547
|
+
"Channel" => "SIP/1234-00000000",
|
548
|
+
"CommandId" => component.id,
|
549
|
+
"Command" => "EXEC ANSWER",
|
550
|
+
"Result" => "200%20result=123%20(timeout)%0A"
|
551
|
+
end
|
552
|
+
|
553
|
+
it 'should send the event to the component' do
|
554
|
+
expect(component).to receive(:handle_ami_event).once.with ami_event
|
555
|
+
subject.process_ami_event ami_event
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'should not send an answered event' do
|
559
|
+
expect(translator).to receive(:handle_pb_event).with(kind_of(Punchblock::Event::Answered)).never
|
560
|
+
subject.process_ami_event ami_event
|
561
|
+
end
|
541
562
|
end
|
542
563
|
end
|
543
564
|
|
565
|
+
def should_send_answered_event
|
566
|
+
expected_answered = Punchblock::Event::Answered.new
|
567
|
+
expected_answered.target_call_id = subject.id
|
568
|
+
expect(translator).to receive(:handle_pb_event).with expected_answered
|
569
|
+
subject.process_ami_event ami_event
|
570
|
+
end
|
571
|
+
|
572
|
+
def answered_should_be_true
|
573
|
+
subject.process_ami_event ami_event
|
574
|
+
expect(subject.answered?).to be_true
|
575
|
+
end
|
576
|
+
|
577
|
+
def should_only_send_one_answered_event
|
578
|
+
expected_answered = Punchblock::Event::Answered.new
|
579
|
+
expected_answered.target_call_id = subject.id
|
580
|
+
expect(translator).to receive(:handle_pb_event).with(expected_answered).once
|
581
|
+
subject.process_ami_event ami_event
|
582
|
+
subject.process_ami_event ami_event
|
583
|
+
end
|
584
|
+
|
585
|
+
def should_use_ami_timestamp_for_rayo_event
|
586
|
+
expected_answered = Punchblock::Event::Answered.new target_call_id: subject.id,
|
587
|
+
timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
|
588
|
+
expect(translator).to receive(:handle_pb_event).with expected_answered
|
589
|
+
|
590
|
+
subject.process_ami_event ami_event
|
591
|
+
end
|
592
|
+
|
544
593
|
context 'with an AsyncAGI Start event' do
|
545
594
|
let(:ami_event) do
|
546
595
|
RubyAMI::Event.new "AsyncAGI",
|
@@ -550,24 +599,16 @@ module Punchblock
|
|
550
599
|
end
|
551
600
|
|
552
601
|
it 'should send an answered event' do
|
553
|
-
|
554
|
-
expected_answered.target_call_id = subject.id
|
555
|
-
expect(translator).to receive(:handle_pb_event).with expected_answered
|
556
|
-
subject.process_ami_event ami_event
|
602
|
+
should_send_answered_event
|
557
603
|
end
|
558
604
|
|
559
605
|
it '#answered? should be true' do
|
560
|
-
|
561
|
-
expect(subject.answered?).to be_true
|
606
|
+
answered_should_be_true
|
562
607
|
end
|
563
608
|
|
564
609
|
context "for a second time" do
|
565
610
|
it 'should only send one answered event' do
|
566
|
-
|
567
|
-
expected_answered.target_call_id = subject.id
|
568
|
-
expect(translator).to receive(:handle_pb_event).with(expected_answered).once
|
569
|
-
subject.process_ami_event ami_event
|
570
|
-
subject.process_ami_event ami_event
|
611
|
+
should_only_send_one_answered_event
|
571
612
|
end
|
572
613
|
end
|
573
614
|
|
@@ -581,11 +622,42 @@ module Punchblock
|
|
581
622
|
end
|
582
623
|
|
583
624
|
it "should use the AMI timestamp for the Rayo event" do
|
584
|
-
|
585
|
-
|
586
|
-
|
625
|
+
should_use_ami_timestamp_for_rayo_event
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
587
629
|
|
588
|
-
|
630
|
+
context 'with an Asterisk 13 AsyncAGIStart event' do
|
631
|
+
let(:ami_event) do
|
632
|
+
RubyAMI::Event.new "AsyncAGIStart",
|
633
|
+
"Channel" => "SIP/1234-00000000",
|
634
|
+
"Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A"
|
635
|
+
end
|
636
|
+
|
637
|
+
it 'should send an answered event' do
|
638
|
+
should_send_answered_event
|
639
|
+
end
|
640
|
+
|
641
|
+
it '#answered? should be true' do
|
642
|
+
answered_should_be_true
|
643
|
+
end
|
644
|
+
|
645
|
+
context "for a second time" do
|
646
|
+
it 'should only send one answered event' do
|
647
|
+
should_only_send_one_answered_event
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
context "when the AMI event has a timestamp" do
|
652
|
+
let :ami_event do
|
653
|
+
RubyAMI::Event.new "AsyncAGIStart",
|
654
|
+
"Channel" => "SIP/1234-00000000",
|
655
|
+
"Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A",
|
656
|
+
'Timestamp' => '1393368380.572575'
|
657
|
+
end
|
658
|
+
|
659
|
+
it "should use the AMI timestamp for the Rayo event" do
|
660
|
+
should_use_ami_timestamp_for_rayo_event
|
589
661
|
end
|
590
662
|
end
|
591
663
|
end
|
@@ -736,6 +808,124 @@ module Punchblock
|
|
736
808
|
end
|
737
809
|
end
|
738
810
|
|
811
|
+
context 'with a BridgeEnter event' do
|
812
|
+
let(:bridge_uniqueid) { "1234-5678" }
|
813
|
+
let(:call_channel) { "SIP/foo" }
|
814
|
+
let :ami_event do
|
815
|
+
RubyAMI::Event.new 'BridgeEnter',
|
816
|
+
'Privilege' => "call,all",
|
817
|
+
'BridgeUniqueid' => bridge_uniqueid,
|
818
|
+
'Channel' => call_channel
|
819
|
+
end
|
820
|
+
|
821
|
+
context 'when the event is received the first time' do
|
822
|
+
it 'sets an entry in translator.bridges' do
|
823
|
+
subject.process_ami_event ami_event
|
824
|
+
expect(translator.bridges[bridge_uniqueid]).to eq call_channel
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
context 'when the event is received a second time for the same BridgeUniqueid' do
|
829
|
+
let(:other_channel) { 'SIP/5678-00000000' }
|
830
|
+
let :other_call do
|
831
|
+
Call.new other_channel, translator, ami_client, connection
|
832
|
+
end
|
833
|
+
let(:other_call_id) { other_call.id }
|
834
|
+
|
835
|
+
let :command do
|
836
|
+
Punchblock::Command::Join.new call_uri: other_call_id
|
837
|
+
end
|
838
|
+
|
839
|
+
let :ami_event do
|
840
|
+
RubyAMI::Event.new 'BridgeEnter',
|
841
|
+
'Privilege' => "call,all",
|
842
|
+
'BridgeUniqueid' => bridge_uniqueid,
|
843
|
+
'Channel' => call_channel
|
844
|
+
end
|
845
|
+
|
846
|
+
let :expected_joined do
|
847
|
+
Punchblock::Event::Joined.new target_call_id: subject.id,
|
848
|
+
call_uri: other_call_id
|
849
|
+
end
|
850
|
+
|
851
|
+
let :expected_joined_other do
|
852
|
+
Punchblock::Event::Joined.new target_call_id: other_call_id,
|
853
|
+
call_uri: subject.id
|
854
|
+
end
|
855
|
+
|
856
|
+
before do
|
857
|
+
translator.bridges[bridge_uniqueid] = other_channel
|
858
|
+
translator.register_call other_call
|
859
|
+
|
860
|
+
other_call.pending_joins[channel] = command
|
861
|
+
command.request!
|
862
|
+
expect(subject).to receive(:execute_agi_command).and_return code: 200
|
863
|
+
subject.execute_command command
|
864
|
+
end
|
865
|
+
|
866
|
+
it 'sends the correct Joined events' do
|
867
|
+
expect(translator).to receive(:handle_pb_event).with expected_joined
|
868
|
+
expect(translator).to receive(:handle_pb_event).with expected_joined_other
|
869
|
+
subject.process_ami_event ami_event
|
870
|
+
expect(command.response(0.5)).to eq(true)
|
871
|
+
end
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
context 'with a BridgeLeave event' do
|
876
|
+
let(:bridge_uniqueid) { "1234-5678" }
|
877
|
+
let(:call_channel) { "SIP/foo-1234" }
|
878
|
+
let :ami_event do
|
879
|
+
RubyAMI::Event.new 'BridgeLeave',
|
880
|
+
'Privilege' => "call,all",
|
881
|
+
'BridgeUniqueid' => bridge_uniqueid,
|
882
|
+
'Channel' => call_channel
|
883
|
+
end
|
884
|
+
|
885
|
+
context 'when the event is received the first time' do
|
886
|
+
it 'sets an entry in translator.bridges' do
|
887
|
+
subject.process_ami_event ami_event
|
888
|
+
expect(translator.bridges[bridge_uniqueid + '_leave']).to eq call_channel
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
context 'when the event is received a second time for the same BridgeUniqueid' do
|
893
|
+
let(:other_channel) { 'SIP/5678-00000000' }
|
894
|
+
let :other_call do
|
895
|
+
Call.new other_channel, translator, ami_client, connection
|
896
|
+
end
|
897
|
+
let(:other_call_id) { other_call.id }
|
898
|
+
|
899
|
+
let :ami_event do
|
900
|
+
RubyAMI::Event.new 'BridgeLeave',
|
901
|
+
'Privilege' => "call,all",
|
902
|
+
'BridgeUniqueid' => bridge_uniqueid,
|
903
|
+
'Channel' => call_channel
|
904
|
+
end
|
905
|
+
|
906
|
+
let :expected_unjoined do
|
907
|
+
Punchblock::Event::Unjoined.new target_call_id: subject.id,
|
908
|
+
call_uri: other_call_id
|
909
|
+
end
|
910
|
+
|
911
|
+
let :expected_unjoined_other do
|
912
|
+
Punchblock::Event::Unjoined.new target_call_id: other_call_id,
|
913
|
+
call_uri: subject.id
|
914
|
+
end
|
915
|
+
|
916
|
+
before do
|
917
|
+
translator.bridges[bridge_uniqueid + '_leave'] = other_channel
|
918
|
+
translator.register_call other_call
|
919
|
+
end
|
920
|
+
|
921
|
+
it 'sends the correct Unjoined events' do
|
922
|
+
expect(translator).to receive(:handle_pb_event).with expected_unjoined
|
923
|
+
expect(translator).to receive(:handle_pb_event).with expected_unjoined_other
|
924
|
+
subject.process_ami_event ami_event
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
739
929
|
context 'with a BridgeExec event' do
|
740
930
|
let :ami_event do
|
741
931
|
RubyAMI::Event.new 'BridgeExec',
|
@@ -794,10 +984,10 @@ module Punchblock
|
|
794
984
|
|
795
985
|
context 'with a Bridge event' do
|
796
986
|
let(:other_channel) { 'SIP/5678-00000000' }
|
797
|
-
let(:other_call_id) { 'def567' }
|
798
987
|
let :other_call do
|
799
988
|
Call.new other_channel, translator, ami_client, connection
|
800
989
|
end
|
990
|
+
let(:other_call_id) { other_call.id }
|
801
991
|
|
802
992
|
let :ami_event do
|
803
993
|
RubyAMI::Event.new 'Bridge',
|
@@ -828,7 +1018,6 @@ module Punchblock
|
|
828
1018
|
before do
|
829
1019
|
translator.register_call other_call
|
830
1020
|
expect(translator).to receive(:call_for_channel).with(other_channel).and_return(other_call)
|
831
|
-
expect(other_call).to receive(:id).and_return other_call_id
|
832
1021
|
end
|
833
1022
|
|
834
1023
|
context "of state 'Link'" do
|
@@ -1379,6 +1568,15 @@ module Punchblock
|
|
1379
1568
|
subject.execute_command command
|
1380
1569
|
end
|
1381
1570
|
|
1571
|
+
context "when the other call doesn't exist" do
|
1572
|
+
let(:other_call) { nil }
|
1573
|
+
|
1574
|
+
it "returns an error" do
|
1575
|
+
subject.execute_command command
|
1576
|
+
expect(command.response(0.5)).to eq(ProtocolError.new.setup(:service_unavailable, "Could not find join party with address #{other_call_id}", subject.id))
|
1577
|
+
end
|
1578
|
+
end
|
1579
|
+
|
1382
1580
|
context "when the AMI command raises an error" do
|
1383
1581
|
let(:message) { 'Some error' }
|
1384
1582
|
let(:error) { RubyAMI::Error.new.tap { |e| e.message = message } }
|
@@ -1758,6 +1956,7 @@ module Punchblock
|
|
1758
1956
|
end
|
1759
1957
|
end
|
1760
1958
|
|
1959
|
+
|
1761
1960
|
describe 'when receiving an AsyncAGI event' do
|
1762
1961
|
context 'of type Exec' do
|
1763
1962
|
let(:ami_event) do
|
@@ -1771,11 +1970,27 @@ module Punchblock
|
|
1771
1970
|
|
1772
1971
|
it 'should return the result' do
|
1773
1972
|
fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
|
1774
|
-
|
1775
1973
|
sleep 0.25
|
1776
|
-
|
1777
1974
|
subject.process_ami_event ami_event
|
1975
|
+
expect(fut.value).to eq({code: 200, result: 123, data: 'timeout'})
|
1976
|
+
end
|
1977
|
+
end
|
1978
|
+
end
|
1778
1979
|
|
1980
|
+
describe 'when receiving an Asterisk 13 AsyncAGIExec event' do
|
1981
|
+
context 'without a subtype' do
|
1982
|
+
let(:ami_event) do
|
1983
|
+
RubyAMI::Event.new 'AsyncAGIExec',
|
1984
|
+
"Channel" => channel,
|
1985
|
+
"CommandId" => Punchblock.new_uuid,
|
1986
|
+
"Command" => "EXEC ANSWER",
|
1987
|
+
"Result" => "200%20result=123%20(timeout)%0A"
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
it 'should return the result' do
|
1991
|
+
fut = Celluloid::Future.new { subject.execute_agi_command 'EXEC ANSWER' }
|
1992
|
+
sleep 0.25
|
1993
|
+
subject.process_ami_event ami_event
|
1779
1994
|
expect(fut.value).to eq({code: 200, result: 123, data: 'timeout'})
|
1780
1995
|
end
|
1781
1996
|
end
|