punchblock 1.8.0 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -1
- data/lib/punchblock/component/component_node.rb +1 -1
- data/lib/punchblock/component/stop.rb +2 -2
- data/lib/punchblock/connection/asterisk.rb +3 -3
- data/lib/punchblock/connection/freeswitch.rb +2 -2
- data/lib/punchblock/connection/xmpp.rb +1 -1
- data/lib/punchblock/translator.rb +1 -0
- data/lib/punchblock/translator/asterisk.rb +10 -9
- data/lib/punchblock/translator/asterisk/call.rb +15 -7
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +1 -1
- data/lib/punchblock/translator/asterisk/component/input.rb +3 -4
- data/lib/punchblock/translator/asterisk/component/output.rb +6 -6
- data/lib/punchblock/translator/asterisk/component/record.rb +7 -7
- data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +3 -3
- data/lib/punchblock/translator/dtmf_recognizer.rb +87 -0
- data/lib/punchblock/translator/freeswitch.rb +9 -7
- data/lib/punchblock/translator/freeswitch/call.rb +6 -4
- data/lib/punchblock/translator/freeswitch/component/input.rb +1 -2
- data/lib/punchblock/translator/freeswitch/component/output.rb +1 -2
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +1 -1
- data/lib/punchblock/translator/input_component.rb +17 -64
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +1 -1
- data/spec/punchblock/component/input_spec.rb +1 -1
- data/spec/punchblock/component/output_spec.rb +33 -3
- data/spec/punchblock/component/record_spec.rb +1 -1
- data/spec/punchblock/connection/asterisk_spec.rb +3 -3
- data/spec/punchblock/connection/freeswitch_spec.rb +3 -2
- data/spec/punchblock/connection/xmpp_spec.rb +18 -6
- data/spec/punchblock/translator/asterisk/call_spec.rb +83 -31
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +1 -1
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +32 -27
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +47 -43
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk_spec.rb +28 -26
- data/spec/punchblock/translator/freeswitch/call_spec.rb +24 -15
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +1 -1
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +2 -3
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +1 -1
- data/spec/punchblock/translator/freeswitch_spec.rb +75 -18
- data/spec/spec_helper.rb +1 -1
- metadata +24 -74
@@ -19,6 +19,8 @@ module Punchblock
|
|
19
19
|
|
20
20
|
trap_exit :actor_died
|
21
21
|
|
22
|
+
finalizer :finalize
|
23
|
+
|
22
24
|
def initialize(connection, media_engine = nil, default_voice = nil)
|
23
25
|
@connection, @media_engine, @default_voice = connection, media_engine, default_voice
|
24
26
|
@calls, @components = {}, {}
|
@@ -60,18 +62,18 @@ module Punchblock
|
|
60
62
|
call = Call.new event[:unique_id], current_actor, event.content.select { |k,v| k.to_s =~ /variable/ }, stream, @media_engine, @default_voice
|
61
63
|
link call
|
62
64
|
register_call call
|
63
|
-
call.send_offer
|
65
|
+
call.async.send_offer
|
64
66
|
end
|
65
67
|
|
66
|
-
register_handler :es, [:has_key?, :other_leg_unique_id] => true do |event|
|
68
|
+
register_handler :es, :event_name => ['CHANNEL_BRIDGE', 'CHANNEL_UNBRIDGE'], [:has_key?, :other_leg_unique_id] => true do |event|
|
67
69
|
call = call_with_id event[:other_leg_unique_id]
|
68
|
-
call.handle_es_event
|
70
|
+
call.async.handle_es_event event if call
|
69
71
|
throw :pass
|
70
72
|
end
|
71
73
|
|
72
74
|
register_handler :es, lambda { |event| es_event_known_call? event } do |event|
|
73
75
|
call = call_with_id event[:unique_id]
|
74
|
-
call.handle_es_event
|
76
|
+
call.async.handle_es_event event
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -113,7 +115,7 @@ module Punchblock
|
|
113
115
|
|
114
116
|
def execute_call_command(command)
|
115
117
|
if call = call_with_id(command.target_call_id)
|
116
|
-
call.execute_command
|
118
|
+
call.async.execute_command command
|
117
119
|
else
|
118
120
|
command.response = ProtocolError.new.setup :item_not_found, "Could not find a call with ID #{command.target_call_id}", command.target_call_id
|
119
121
|
end
|
@@ -121,7 +123,7 @@ module Punchblock
|
|
121
123
|
|
122
124
|
def execute_component_command(command)
|
123
125
|
if (component = component_with_id(command.component_id))
|
124
|
-
component.execute_command
|
126
|
+
component.async.execute_command command
|
125
127
|
else
|
126
128
|
command.response = ProtocolError.new.setup :item_not_found, "Could not find a component with ID #{command.component_id}", command.target_call_id, command.component_id
|
127
129
|
end
|
@@ -132,7 +134,7 @@ module Punchblock
|
|
132
134
|
when Punchblock::Command::Dial
|
133
135
|
call = Call.new_link Punchblock.new_uuid, current_actor, nil, stream, @media_engine, @default_voice
|
134
136
|
register_call call
|
135
|
-
call.dial
|
137
|
+
call.async.dial command
|
136
138
|
else
|
137
139
|
command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
|
138
140
|
end
|
@@ -128,9 +128,11 @@ module Punchblock
|
|
128
128
|
@direction = :outbound
|
129
129
|
|
130
130
|
cid_number, cid_name = dial_command.from, nil
|
131
|
-
dial_command.from
|
132
|
-
cid_name
|
133
|
-
|
131
|
+
if dial_command.from
|
132
|
+
dial_command.from.match(/(?<cid_name>.*)<(?<cid_number>.*)>/) do |m|
|
133
|
+
cid_name = m[:cid_name].strip
|
134
|
+
cid_number = m[:cid_number]
|
135
|
+
end
|
134
136
|
end
|
135
137
|
|
136
138
|
options = {
|
@@ -246,7 +248,7 @@ module Punchblock
|
|
246
248
|
def execute_component(type, command, *execute_args)
|
247
249
|
type.new_link(command, current_actor).tap do |component|
|
248
250
|
register_component component
|
249
|
-
component.execute
|
251
|
+
component.async.execute(*execute_args)
|
250
252
|
end
|
251
253
|
end
|
252
254
|
|
@@ -11,10 +11,9 @@ module Punchblock
|
|
11
11
|
private
|
12
12
|
|
13
13
|
def register_dtmf_event_handler
|
14
|
-
component = current_actor
|
15
14
|
call.register_handler :es, :event_name => 'DTMF' do |event|
|
16
15
|
safe_from_dead_actors do
|
17
|
-
|
16
|
+
@recognizer << event[:dtmf_digit]
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -9,7 +9,6 @@ module Punchblock
|
|
9
9
|
|
10
10
|
def validate
|
11
11
|
super
|
12
|
-
raise OptionError, "A voice value is unsupported." if @component_node.voice
|
13
12
|
filenames
|
14
13
|
end
|
15
14
|
|
@@ -36,7 +35,7 @@ module Punchblock
|
|
36
35
|
def playback(path)
|
37
36
|
op = current_actor
|
38
37
|
register_handler :es, :event_name => 'CHANNEL_EXECUTE_COMPLETE' do |event|
|
39
|
-
op.send_complete_event
|
38
|
+
op.async.send_complete_event complete_reason_for_event(event)
|
40
39
|
end
|
41
40
|
application 'playback', path
|
42
41
|
end
|
@@ -10,7 +10,7 @@ module Punchblock
|
|
10
10
|
def do_output(engine, default_voice = nil)
|
11
11
|
op = current_actor
|
12
12
|
register_handler :es, :event_name => 'CHANNEL_EXECUTE_COMPLETE' do |event|
|
13
|
-
op.send_complete_event
|
13
|
+
op.async.send_complete_event success_reason
|
14
14
|
end
|
15
15
|
voice = @component_node.voice || default_voice || 'kal'
|
16
16
|
application :speak, [engine, voice, document].join('|')
|
@@ -3,19 +3,16 @@
|
|
3
3
|
module Punchblock
|
4
4
|
module Translator
|
5
5
|
module InputComponent
|
6
|
-
def setup
|
7
|
-
@buffer = ""
|
8
|
-
@initial_timeout = @component_node.initial_timeout || -1
|
9
|
-
@inter_digit_timeout = @component_node.inter_digit_timeout || -1
|
10
|
-
end
|
11
|
-
|
12
6
|
def execute
|
13
7
|
validate
|
14
|
-
send_ref
|
15
8
|
|
16
|
-
|
9
|
+
component = current_actor
|
10
|
+
@recognizer = DTMFRecognizer.new current_actor,
|
11
|
+
@component_node.grammar.value,
|
12
|
+
(@component_node.initial_timeout || -1),
|
13
|
+
(@component_node.inter_digit_timeout || -1)
|
17
14
|
|
18
|
-
|
15
|
+
send_ref
|
19
16
|
|
20
17
|
@dtmf_handler_id = register_dtmf_event_handler
|
21
18
|
rescue OptionError => e
|
@@ -23,16 +20,7 @@ module Punchblock
|
|
23
20
|
end
|
24
21
|
|
25
22
|
def process_dtmf(digit)
|
26
|
-
@
|
27
|
-
cancel_initial_timer
|
28
|
-
case (match = @grammar.match @buffer.dup)
|
29
|
-
when RubySpeech::GRXML::Match
|
30
|
-
complete success_reason(match)
|
31
|
-
when RubySpeech::GRXML::NoMatch
|
32
|
-
complete Punchblock::Component::Input::Complete::NoMatch.new
|
33
|
-
when RubySpeech::GRXML::PotentialMatch
|
34
|
-
reset_inter_digit_timer
|
35
|
-
end
|
23
|
+
@recognizer << digit
|
36
24
|
end
|
37
25
|
|
38
26
|
def execute_command(command)
|
@@ -45,62 +33,27 @@ module Punchblock
|
|
45
33
|
end
|
46
34
|
end
|
47
35
|
|
48
|
-
|
49
|
-
|
50
|
-
def validate
|
51
|
-
raise OptionError, 'A grammar document is required.' unless @component_node.grammar
|
52
|
-
raise OptionError, 'A mode value other than DTMF is unsupported.' unless @component_node.mode == :dtmf
|
53
|
-
raise OptionError, 'An initial timeout value that is negative (and not -1) is invalid.' unless @initial_timeout >= -1
|
54
|
-
raise OptionError, 'An inter-digit timeout value that is negative (and not -1) is invalid.' unless @inter_digit_timeout >= -1
|
55
|
-
end
|
56
|
-
|
57
|
-
def prepare_grammar
|
58
|
-
@component_node.grammar.value.clone.tap do |grammar|
|
59
|
-
grammar.inline!
|
60
|
-
grammar.tokenize!
|
61
|
-
grammar.normalize_whitespace
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def begin_initial_timer(timeout)
|
66
|
-
@initial_timer = after timeout do
|
67
|
-
complete Punchblock::Component::Input::Complete::NoInput.new
|
68
|
-
end
|
36
|
+
def match(mode, confidence, utterance, interpretation)
|
37
|
+
complete Punchblock::Component::Input::Complete::Success.new(:mode => mode, :confidence => confidence, :utterance => utterance, :interpretation => interpretation)
|
69
38
|
end
|
70
39
|
|
71
|
-
def
|
72
|
-
|
73
|
-
@initial_timer.cancel
|
74
|
-
@initial_timer = nil
|
40
|
+
def nomatch
|
41
|
+
complete Punchblock::Component::Input::Complete::NoMatch.new
|
75
42
|
end
|
76
43
|
|
77
|
-
def
|
78
|
-
|
79
|
-
@inter_digit_timer ||= begin
|
80
|
-
after @inter_digit_timeout/1000 do
|
81
|
-
complete Punchblock::Component::Input::Complete::NoMatch.new
|
82
|
-
end
|
83
|
-
end
|
84
|
-
@inter_digit_timer.reset
|
44
|
+
def noinput
|
45
|
+
complete Punchblock::Component::Input::Complete::NoInput.new
|
85
46
|
end
|
86
47
|
|
87
|
-
|
88
|
-
return unless instance_variable_defined?(:@inter_digit_timer) && @inter_digit_timer
|
89
|
-
@inter_digit_timer.cancel
|
90
|
-
@inter_digit_timer = nil
|
91
|
-
end
|
48
|
+
private
|
92
49
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
96
|
-
:utterance => match.utterance,
|
97
|
-
:interpretation => match.interpretation
|
50
|
+
def validate
|
51
|
+
raise OptionError, 'A grammar document is required.' unless @component_node.grammar
|
52
|
+
raise OptionError, 'A mode value other than DTMF is unsupported.' unless @component_node.mode == :dtmf
|
98
53
|
end
|
99
54
|
|
100
55
|
def complete(reason)
|
101
56
|
unregister_dtmf_event_handler
|
102
|
-
cancel_initial_timer
|
103
|
-
cancel_inter_digit_timer
|
104
57
|
send_complete_event reason
|
105
58
|
end
|
106
59
|
end
|
data/lib/punchblock/version.rb
CHANGED
data/punchblock.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_runtime_dependency %q<state_machine>, ["~> 1.0"]
|
30
30
|
s.add_runtime_dependency %q<future-resource>, ["~> 1.0"]
|
31
31
|
s.add_runtime_dependency %q<has-guarded-handlers>, ["~> 1.5"]
|
32
|
-
s.add_runtime_dependency %q<celluloid>, ["~> 0.
|
32
|
+
s.add_runtime_dependency %q<celluloid>, ["~> 0.13"]
|
33
33
|
s.add_runtime_dependency %q<ruby_ami>, ["~> 1.2", ">= 1.2.1"]
|
34
34
|
s.add_runtime_dependency %q<ruby_fs>, ["~> 1.0"]
|
35
35
|
s.add_runtime_dependency %q<ruby_speech>, ["~> 1.0"]
|
@@ -150,7 +150,7 @@ module Punchblock
|
|
150
150
|
describe '#stop_action' do
|
151
151
|
subject { command.stop_action }
|
152
152
|
|
153
|
-
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:1"/>' }
|
153
|
+
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:ext:1"/>' }
|
154
154
|
its(:component_id) { should be == 'abc123' }
|
155
155
|
its(:target_call_id) { should be == '123abc' }
|
156
156
|
end
|
@@ -70,6 +70,36 @@ module Punchblock
|
|
70
70
|
its(:voice) { should be == 'allison' }
|
71
71
|
its(:renderer) { should be == 'swift' }
|
72
72
|
its(:text) { should be == 'Hello world' }
|
73
|
+
|
74
|
+
context "with SSML" do
|
75
|
+
let :stanza do
|
76
|
+
<<-MESSAGE
|
77
|
+
<output xmlns='urn:xmpp:rayo:output:1'
|
78
|
+
interrupt-on='speech'
|
79
|
+
start-offset='2000'
|
80
|
+
start-paused='false'
|
81
|
+
repeat-interval='2000'
|
82
|
+
repeat-times='10'
|
83
|
+
max-time='30000'
|
84
|
+
voice='allison'
|
85
|
+
renderer='swift'>
|
86
|
+
<speak version="1.0"
|
87
|
+
xmlns="http://www.w3.org/2001/10/synthesis"
|
88
|
+
xml:lang="en-US">
|
89
|
+
<say-as interpret-as="ordinal">100</say-as>
|
90
|
+
</speak>
|
91
|
+
</output>
|
92
|
+
MESSAGE
|
93
|
+
end
|
94
|
+
|
95
|
+
def ssml_doc(mode = :ordinal)
|
96
|
+
RubySpeech::SSML.draw do
|
97
|
+
say_as(:interpret_as => mode) { string '100' }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
its(:ssml) { should be == ssml_doc }
|
102
|
+
end
|
73
103
|
end
|
74
104
|
|
75
105
|
describe "for text" do
|
@@ -82,7 +112,7 @@ module Punchblock
|
|
82
112
|
describe "for SSML" do
|
83
113
|
def ssml_doc(mode = :ordinal)
|
84
114
|
RubySpeech::SSML.draw do
|
85
|
-
say_as(:interpret_as => mode) { 100 }
|
115
|
+
say_as(:interpret_as => mode) { string '100' }
|
86
116
|
end
|
87
117
|
end
|
88
118
|
|
@@ -93,7 +123,7 @@ module Punchblock
|
|
93
123
|
its(:ssml) { should be == ssml_doc }
|
94
124
|
|
95
125
|
describe "comparison" do
|
96
|
-
let(:output2) { Output.new :ssml => '<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"><say-as interpret-as="ordinal"
|
126
|
+
let(:output2) { Output.new :ssml => '<speak xmlns="http://www.w3.org/2001/10/synthesis" version="1.0" xml:lang="en-US"><say-as interpret-as="ordinal">100</say-as></speak>', :voice => 'kate' }
|
97
127
|
let(:output3) { Output.new :ssml => ssml_doc, :voice => 'kate' }
|
98
128
|
let(:output4) { Output.new :ssml => ssml_doc(:normal), :voice => 'kate' }
|
99
129
|
|
@@ -204,7 +234,7 @@ module Punchblock
|
|
204
234
|
describe '#stop_action' do
|
205
235
|
subject { command.stop_action }
|
206
236
|
|
207
|
-
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:1"/>' }
|
237
|
+
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:ext:1"/>' }
|
208
238
|
its(:component_id) { should be == 'abc123' }
|
209
239
|
its(:target_call_id) { should be == '123abc' }
|
210
240
|
end
|
@@ -209,7 +209,7 @@ module Punchblock
|
|
209
209
|
describe '#stop_action' do
|
210
210
|
subject { command.stop_action }
|
211
211
|
|
212
|
-
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:1"/>' }
|
212
|
+
its(:to_xml) { should be == '<stop xmlns="urn:xmpp:rayo:ext:1"/>' }
|
213
213
|
its(:component_id) { should be == 'abc123' }
|
214
214
|
its(:target_call_id) { should be == '123abc' }
|
215
215
|
end
|
@@ -49,14 +49,14 @@ module Punchblock
|
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'shuts down the translator' do
|
52
|
-
subject.translator.should_receive(:shutdown
|
52
|
+
subject.translator.async.should_receive(:shutdown).once
|
53
53
|
subject.stop
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'sends events from RubyAMI to the translator' do
|
58
58
|
event = mock 'RubyAMI::Event'
|
59
|
-
subject.translator.should_receive(:handle_ami_event
|
59
|
+
subject.translator.async.should_receive(:handle_ami_event).once.with event
|
60
60
|
subject.ami_client.handle_event event
|
61
61
|
end
|
62
62
|
|
@@ -64,7 +64,7 @@ module Punchblock
|
|
64
64
|
it 'sends a command to the translator' do
|
65
65
|
command = mock 'Command'
|
66
66
|
options = {:foo => :bar}
|
67
|
-
subject.translator.should_receive(:execute_command
|
67
|
+
subject.translator.async.should_receive(:execute_command).once.with command, options
|
68
68
|
subject.write command, options
|
69
69
|
end
|
70
70
|
end
|
@@ -63,7 +63,8 @@ module Punchblock
|
|
63
63
|
|
64
64
|
it 'sends events from RubyFS to the translator' do
|
65
65
|
event = mock 'RubyFS::Event'
|
66
|
-
subject.translator.should_receive(:handle_es_event
|
66
|
+
subject.translator.async.should_receive(:handle_es_event).once.with event
|
67
|
+
subject.translator.async.should_receive(:handle_es_event).once.with RubyFS::Stream::Disconnected.new
|
67
68
|
subject.stream.fire_event event
|
68
69
|
end
|
69
70
|
|
@@ -71,7 +72,7 @@ module Punchblock
|
|
71
72
|
it 'sends a command to the translator' do
|
72
73
|
command = mock 'Command'
|
73
74
|
options = {:foo => :bar}
|
74
|
-
subject.translator.should_receive(:execute_command
|
75
|
+
subject.translator.async.should_receive(:execute_command).once.with command, options
|
75
76
|
subject.write command, options
|
76
77
|
end
|
77
78
|
end
|
@@ -64,10 +64,11 @@ module Punchblock
|
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'should properly set the Blather logger' do
|
67
|
+
old_logger = Punchblock.logger
|
67
68
|
Punchblock.logger = :foo
|
68
69
|
XMPP.new :username => '1@call.rayo.net', :password => 1
|
69
70
|
Blather.logger.should be :foo
|
70
|
-
Punchblock.
|
71
|
+
Punchblock.logger = old_logger
|
71
72
|
end
|
72
73
|
|
73
74
|
it "looking up original command by command ID" do
|
@@ -232,17 +233,28 @@ module Punchblock
|
|
232
233
|
let(:example_error) { import_stanza error_xml }
|
233
234
|
let(:cmd) { Component::Output.new }
|
234
235
|
|
235
|
-
before
|
236
|
+
before do
|
236
237
|
cmd.request!
|
237
238
|
connection.__send__ :handle_error, example_error, cmd
|
238
239
|
end
|
239
240
|
|
240
241
|
subject { cmd.response }
|
241
242
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
243
|
+
it "should have the correct call ID" do
|
244
|
+
subject.call_id.should be == call_id
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should have the correct component ID" do
|
248
|
+
subject.component_id.should be == component_id
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should have the correct name" do
|
252
|
+
subject.name.should be == :item_not_found
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should have the correct text" do
|
256
|
+
subject.text.should be == 'Could not find call [id=f6d437f4-1e18-457b-99f8-b5d853f50347]'
|
257
|
+
end
|
246
258
|
end
|
247
259
|
|
248
260
|
describe "#prep_command_for_execution" do
|
@@ -7,7 +7,7 @@ module Punchblock
|
|
7
7
|
class Asterisk
|
8
8
|
describe Call do
|
9
9
|
let(:channel) { 'SIP/foo' }
|
10
|
-
let(:translator) { stub('
|
10
|
+
let(:translator) { Asterisk.new stub('AMI Client').as_null_object, stub('connection').as_null_object }
|
11
11
|
let(:agi_env) do
|
12
12
|
{
|
13
13
|
:agi_request => 'async',
|
@@ -65,6 +65,8 @@ module Punchblock
|
|
65
65
|
its(:translator) { should be translator }
|
66
66
|
its(:agi_env) { should be == agi_env }
|
67
67
|
|
68
|
+
before { translator.stub :handle_pb_event }
|
69
|
+
|
68
70
|
describe '#shutdown' do
|
69
71
|
it 'should terminate the actor' do
|
70
72
|
subject.shutdown
|
@@ -170,7 +172,7 @@ module Punchblock
|
|
170
172
|
:variable => "punchblock_call_id=#{subject.id}"
|
171
173
|
}).tap { |a| a.request! }
|
172
174
|
|
173
|
-
translator.should_receive(:execute_global_command
|
175
|
+
translator.async.should_receive(:execute_global_command).once.with expected_action
|
174
176
|
subject.dial dial_command
|
175
177
|
end
|
176
178
|
|
@@ -188,7 +190,7 @@ module Punchblock
|
|
188
190
|
:variable => "punchblock_call_id=#{subject.id}"
|
189
191
|
}).tap { |a| a.request! }
|
190
192
|
|
191
|
-
translator.should_receive(:execute_global_command
|
193
|
+
translator.async.should_receive(:execute_global_command).once.with expected_action
|
192
194
|
subject.dial dial_command
|
193
195
|
end
|
194
196
|
end
|
@@ -210,7 +212,7 @@ module Punchblock
|
|
210
212
|
:timeout => 10000
|
211
213
|
}).tap { |a| a.request! }
|
212
214
|
|
213
|
-
translator.should_receive(:execute_global_command
|
215
|
+
translator.async.should_receive(:execute_global_command).once.with expected_action
|
214
216
|
subject.dial dial_command
|
215
217
|
end
|
216
218
|
end
|
@@ -231,7 +233,7 @@ module Punchblock
|
|
231
233
|
:variable => "punchblock_call_id=#{subject.id},SIPADDHEADER51=\"X-foo: bar\",SIPADDHEADER52=\"X-doo: dah\""
|
232
234
|
}).tap { |a| a.request! }
|
233
235
|
|
234
|
-
translator.should_receive(:execute_global_command
|
236
|
+
translator.async.should_receive(:execute_global_command).once.with expected_action
|
235
237
|
subject.dial dial_command
|
236
238
|
end
|
237
239
|
end
|
@@ -302,6 +304,20 @@ module Punchblock
|
|
302
304
|
subject.process_ami_event ami_event
|
303
305
|
end
|
304
306
|
|
307
|
+
it "should not allow commands to be executed while components are shutting down" do
|
308
|
+
comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf
|
309
|
+
comp_command.request!
|
310
|
+
component = subject.execute_command comp_command
|
311
|
+
comp_command.response(0.1).should be_a Ref
|
312
|
+
|
313
|
+
subject.async.process_ami_event ami_event
|
314
|
+
|
315
|
+
comp_command = Punchblock::Component::Input.new :grammar => {:value => '<grammar/>'}, :mode => :dtmf
|
316
|
+
comp_command.request!
|
317
|
+
subject.execute_command comp_command
|
318
|
+
comp_command.response(0.1).should == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{subject.id}", subject.id)
|
319
|
+
end
|
320
|
+
|
305
321
|
context "with an undefined cause" do
|
306
322
|
let(:cause) { '0' }
|
307
323
|
let(:cause_txt) { 'Undefined' }
|
@@ -429,7 +445,7 @@ module Punchblock
|
|
429
445
|
context 'with an event for a known AGI command component' do
|
430
446
|
let(:mock_component_node) { mock 'Punchblock::Component::Asterisk::AGI::Command', :name => 'EXEC ANSWER', :params_array => [] }
|
431
447
|
let :component do
|
432
|
-
Component::Asterisk::AGICommand.new mock_component_node, subject
|
448
|
+
Component::Asterisk::AGICommand.new mock_component_node, subject
|
433
449
|
end
|
434
450
|
|
435
451
|
let(:ami_event) do
|
@@ -579,24 +595,54 @@ module Punchblock
|
|
579
595
|
e['Privilege'] = "call,all"
|
580
596
|
e['Response'] = "Success"
|
581
597
|
e['Channel1'] = "SIP/foo"
|
582
|
-
e['Channel2'] =
|
598
|
+
e['Channel2'] = other_channel
|
583
599
|
end
|
584
600
|
end
|
585
601
|
|
586
602
|
let(:other_channel) { 'SIP/5678-00000000' }
|
587
|
-
let(:other_call_id) { 'def567' }
|
588
|
-
let :command do
|
589
|
-
Punchblock::Command::Join.new :call_id => other_call_id
|
590
|
-
end
|
591
603
|
|
592
|
-
|
593
|
-
|
594
|
-
|
604
|
+
context "when a join has been executed against another call" do
|
605
|
+
let :other_call do
|
606
|
+
Call.new other_channel, translator
|
607
|
+
end
|
608
|
+
|
609
|
+
let(:other_call_id) { other_call.id }
|
610
|
+
let :command do
|
611
|
+
Punchblock::Command::Join.new :call_id => other_call_id
|
612
|
+
end
|
613
|
+
|
614
|
+
before do
|
615
|
+
translator.register_call other_call
|
616
|
+
command.request!
|
617
|
+
subject.execute_command command
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'retrieves and sets success on the correct Join' do
|
621
|
+
subject.process_ami_event ami_event
|
622
|
+
command.response(0.5).should be == true
|
623
|
+
end
|
624
|
+
|
625
|
+
context "with the channel names reversed" do
|
626
|
+
let :ami_event do
|
627
|
+
RubyAMI::Event.new('BridgeExec').tap do |e|
|
628
|
+
e['Privilege'] = "call,all"
|
629
|
+
e['Response'] = "Success"
|
630
|
+
e['Channel1'] = other_channel
|
631
|
+
e['Channel2'] = "SIP/foo"
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
it 'retrieves and sets success on the correct Join' do
|
636
|
+
subject.process_ami_event ami_event
|
637
|
+
command.response(0.5).should be == true
|
638
|
+
end
|
639
|
+
end
|
595
640
|
end
|
596
641
|
|
597
|
-
|
598
|
-
|
599
|
-
|
642
|
+
context "with no matching join command" do
|
643
|
+
it "should do nothing" do
|
644
|
+
expect { subject.process_ami_event ami_event }.not_to raise_error
|
645
|
+
end
|
600
646
|
end
|
601
647
|
end
|
602
648
|
|
@@ -859,12 +905,12 @@ module Punchblock
|
|
859
905
|
Punchblock::Component::Asterisk::AGI::Command.new :name => 'Answer'
|
860
906
|
end
|
861
907
|
|
862
|
-
let(:mock_action) {
|
908
|
+
let(:mock_action) { Translator::Asterisk::Component::Asterisk::AGICommand.new(command, subject) }
|
863
909
|
|
864
910
|
it 'should create an AGI command component actor and execute it asynchronously' do
|
865
911
|
mock_action.should_receive(:internal=).never
|
866
912
|
Component::Asterisk::AGICommand.should_receive(:new_link).once.with(command, subject).and_return mock_action
|
867
|
-
mock_action.should_receive(:execute
|
913
|
+
mock_action.async.should_receive(:execute).once
|
868
914
|
subject.execute_command command
|
869
915
|
end
|
870
916
|
end
|
@@ -874,12 +920,12 @@ module Punchblock
|
|
874
920
|
Punchblock::Component::Output.new
|
875
921
|
end
|
876
922
|
|
877
|
-
let(:mock_action) {
|
923
|
+
let(:mock_action) { Translator::Asterisk::Component::Output.new(command, subject) }
|
878
924
|
|
879
925
|
it 'should create an Output component and execute it asynchronously' do
|
880
926
|
Component::Output.should_receive(:new_link).once.with(command, subject).and_return mock_action
|
881
927
|
mock_action.should_receive(:internal=).never
|
882
|
-
mock_action.should_receive(:execute
|
928
|
+
mock_action.async.should_receive(:execute).once
|
883
929
|
subject.execute_command command
|
884
930
|
end
|
885
931
|
end
|
@@ -889,12 +935,12 @@ module Punchblock
|
|
889
935
|
Punchblock::Component::Input.new
|
890
936
|
end
|
891
937
|
|
892
|
-
let(:mock_action) {
|
938
|
+
let(:mock_action) { Translator::Asterisk::Component::Input.new(command, subject) }
|
893
939
|
|
894
940
|
it 'should create an Input component and execute it asynchronously' do
|
895
941
|
Component::Input.should_receive(:new_link).once.with(command, subject).and_return mock_action
|
896
942
|
mock_action.should_receive(:internal=).never
|
897
|
-
mock_action.should_receive(:execute
|
943
|
+
mock_action.async.should_receive(:execute).once
|
898
944
|
subject.execute_command command
|
899
945
|
end
|
900
946
|
end
|
@@ -904,12 +950,12 @@ module Punchblock
|
|
904
950
|
Punchblock::Component::Record.new
|
905
951
|
end
|
906
952
|
|
907
|
-
let(:mock_action) {
|
953
|
+
let(:mock_action) { Translator::Asterisk::Component::Record.new(command, subject) }
|
908
954
|
|
909
955
|
it 'should create a Record component and execute it asynchronously' do
|
910
956
|
Component::Record.should_receive(:new_link).once.with(command, subject).and_return mock_action
|
911
957
|
mock_action.should_receive(:internal=).never
|
912
|
-
mock_action.should_receive(:execute
|
958
|
+
mock_action.async.should_receive(:execute).once
|
913
959
|
subject.execute_command command
|
914
960
|
end
|
915
961
|
end
|
@@ -974,6 +1020,18 @@ module Punchblock
|
|
974
1020
|
subject.execute_command subsequent_command
|
975
1021
|
subsequent_command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{comp_id} for call #{subject.id}", subject.id, comp_id)
|
976
1022
|
end
|
1023
|
+
|
1024
|
+
context "when we dispatch the command to it" do
|
1025
|
+
it 'sends an error in response to the command' do
|
1026
|
+
component = subject.component_with_id comp_id
|
1027
|
+
|
1028
|
+
component.should_receive(:execute_command).and_raise(Celluloid::DeadActorError)
|
1029
|
+
|
1030
|
+
subsequent_command.request!
|
1031
|
+
subject.execute_command subsequent_command
|
1032
|
+
subsequent_command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{comp_id} for call #{subject.id}", subject.id, comp_id)
|
1033
|
+
end
|
1034
|
+
end
|
977
1035
|
end
|
978
1036
|
|
979
1037
|
context "for an unknown component ID" do
|
@@ -1015,12 +1073,6 @@ module Punchblock
|
|
1015
1073
|
agi_command.name.should be == "EXEC Bridge"
|
1016
1074
|
agi_command.params_array.should be == [other_channel]
|
1017
1075
|
end
|
1018
|
-
|
1019
|
-
it "adds the join to the @pending_joins hash" do
|
1020
|
-
translator.should_receive(:call_with_id).with(other_call_id).and_return(other_call)
|
1021
|
-
subject.execute_command command
|
1022
|
-
subject.pending_joins[other_channel].should be command
|
1023
|
-
end
|
1024
1076
|
end
|
1025
1077
|
|
1026
1078
|
context "with an unjoin command" do
|