punchblock 1.8.0 → 1.8.1
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.
- 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
|