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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +1 -1
  4. data/lib/punchblock/component/component_node.rb +1 -1
  5. data/lib/punchblock/component/stop.rb +2 -2
  6. data/lib/punchblock/connection/asterisk.rb +3 -3
  7. data/lib/punchblock/connection/freeswitch.rb +2 -2
  8. data/lib/punchblock/connection/xmpp.rb +1 -1
  9. data/lib/punchblock/translator.rb +1 -0
  10. data/lib/punchblock/translator/asterisk.rb +10 -9
  11. data/lib/punchblock/translator/asterisk/call.rb +15 -7
  12. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +1 -1
  13. data/lib/punchblock/translator/asterisk/component/input.rb +3 -4
  14. data/lib/punchblock/translator/asterisk/component/output.rb +6 -6
  15. data/lib/punchblock/translator/asterisk/component/record.rb +7 -7
  16. data/lib/punchblock/translator/asterisk/component/stop_by_redirect.rb +3 -3
  17. data/lib/punchblock/translator/dtmf_recognizer.rb +87 -0
  18. data/lib/punchblock/translator/freeswitch.rb +9 -7
  19. data/lib/punchblock/translator/freeswitch/call.rb +6 -4
  20. data/lib/punchblock/translator/freeswitch/component/input.rb +1 -2
  21. data/lib/punchblock/translator/freeswitch/component/output.rb +1 -2
  22. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +1 -1
  23. data/lib/punchblock/translator/input_component.rb +17 -64
  24. data/lib/punchblock/version.rb +1 -1
  25. data/punchblock.gemspec +1 -1
  26. data/spec/punchblock/component/input_spec.rb +1 -1
  27. data/spec/punchblock/component/output_spec.rb +33 -3
  28. data/spec/punchblock/component/record_spec.rb +1 -1
  29. data/spec/punchblock/connection/asterisk_spec.rb +3 -3
  30. data/spec/punchblock/connection/freeswitch_spec.rb +3 -2
  31. data/spec/punchblock/connection/xmpp_spec.rb +18 -6
  32. data/spec/punchblock/translator/asterisk/call_spec.rb +83 -31
  33. data/spec/punchblock/translator/asterisk/component/input_spec.rb +1 -1
  34. data/spec/punchblock/translator/asterisk/component/output_spec.rb +32 -27
  35. data/spec/punchblock/translator/asterisk/component/record_spec.rb +47 -43
  36. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  37. data/spec/punchblock/translator/asterisk_spec.rb +28 -26
  38. data/spec/punchblock/translator/freeswitch/call_spec.rb +24 -15
  39. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +1 -1
  40. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +2 -3
  41. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +1 -1
  42. data/spec/punchblock/translator/freeswitch_spec.rb +75 -18
  43. data/spec/spec_helper.rb +1 -1
  44. 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! event if call
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! 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! 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! 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! command
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.match(/(?<cid_name>.*)<(?<cid_number>.*)>/) do |m|
132
- cid_name = m[:cid_name].strip
133
- cid_number = m[:cid_number]
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!(*execute_args)
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
- component.process_dtmf! event[:dtmf_digit]
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! complete_reason_for_event(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! success_reason
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
- @grammar = prepare_grammar
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
- begin_initial_timer @initial_timeout/1000 unless @initial_timeout == -1
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
- @buffer << digit
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
- private
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 cancel_initial_timer
72
- return unless instance_variable_defined?(:@initial_timer) && @initial_timer
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 reset_inter_digit_timer
78
- return if @inter_digit_timeout == -1
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
- def cancel_inter_digit_timer
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 success_reason(match)
94
- Punchblock::Component::Input::Complete::Success.new :mode => match.mode,
95
- :confidence => match.confidence,
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Punchblock
4
- VERSION = "1.8.0"
4
+ VERSION = "1.8.1"
5
5
  end
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.12", ">= 0.12.4"]
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"/></speak>', :voice => 'kate' }
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!).once
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!).once.with 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!).once.with command, options
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!).once.with 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!).once.with command, options
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.reset_logger
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(:all) do
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
- its(:call_id) { should be == call_id }
243
- its(:component_id) { should be == component_id }
244
- its(:name) { should be == :item_not_found }
245
- its(:text) { should be == 'Could not find call [id=f6d437f4-1e18-457b-99f8-b5d853f50347]' }
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('Translator::Asterisk').as_null_object }
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!).once.with expected_action
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!).once.with expected_action
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!).once.with expected_action
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!).once.with expected_action
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.translator
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'] = "SIP/5678-00000000"
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
- before do
593
- subject.pending_joins[other_channel] = command
594
- command.request!
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
- it 'retrieves and sets success on the correct Join' do
598
- subject.process_ami_event ami_event
599
- command.response(0.5).should be == true
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) { mock 'Component::Asterisk::AGI::Command', :id => 'foo' }
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!).once
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) { mock 'Component::Asterisk::Output', :id => 'foo' }
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!).once
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) { mock 'Component::Asterisk::Input', :id => 'foo' }
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!).once
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) { mock 'Component::Asterisk::Record', :id => 'foo' }
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!).once
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