punchblock 1.8.2 → 1.9.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/lib/punchblock/component/asterisk/agi/command.rb +0 -11
  5. data/lib/punchblock/connection/asterisk.rb +3 -3
  6. data/lib/punchblock/translator/asterisk/agi_command.rb +40 -0
  7. data/lib/punchblock/translator/asterisk/call.rb +56 -47
  8. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +13 -37
  9. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +24 -41
  10. data/lib/punchblock/translator/asterisk/component/input.rb +2 -1
  11. data/lib/punchblock/translator/asterisk/component/output.rb +16 -21
  12. data/lib/punchblock/translator/asterisk/component/record.rb +11 -19
  13. data/lib/punchblock/translator/asterisk/component.rb +12 -9
  14. data/lib/punchblock/translator/asterisk.rb +16 -22
  15. data/lib/punchblock/translator/dtmf_recognizer.rb +4 -4
  16. data/lib/punchblock/translator/freeswitch/component/input.rb +2 -1
  17. data/lib/punchblock/translator/input_component.rb +2 -2
  18. data/lib/punchblock/version.rb +1 -1
  19. data/punchblock.gemspec +1 -1
  20. data/spec/punchblock/connection/asterisk_spec.rb +8 -7
  21. data/spec/punchblock/translator/asterisk/call_spec.rb +262 -229
  22. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +57 -29
  23. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +40 -46
  24. data/spec/punchblock/translator/asterisk/component/input_spec.rb +7 -7
  25. data/spec/punchblock/translator/asterisk/component/output_spec.rb +84 -53
  26. data/spec/punchblock/translator/asterisk/component/record_spec.rb +55 -83
  27. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +5 -1
  28. data/spec/punchblock/translator/asterisk/component_spec.rb +2 -10
  29. data/spec/punchblock/translator/asterisk_spec.rb +73 -100
  30. metadata +5 -10
@@ -11,8 +11,9 @@ module Punchblock
11
11
  include HasMockCallbackConnection
12
12
 
13
13
  let(:channel) { 'SIP/foo' }
14
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection }
15
- let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator }
14
+ let(:ami_client) { stub('AMI Client').as_null_object }
15
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
16
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator, ami_client, connection }
16
17
  let(:component_id) { Punchblock.new_uuid }
17
18
 
18
19
  before { stub_uuids component_id }
@@ -23,31 +24,27 @@ module Punchblock
23
24
 
24
25
  subject { AGICommand.new original_command, mock_call }
25
26
 
26
- let :expected_action do
27
- RubyAMI::Action.new 'AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => component_id
27
+ let :response do
28
+ RubyAMI::Response.new
28
29
  end
29
30
 
30
31
  context 'initial execution' do
31
32
  before { original_command.request! }
32
33
 
33
- it 'should send the appropriate RubyAMI::Action' do
34
- mock_call.should_receive(:send_ami_action).once.with(expected_action).and_return(expected_action)
34
+ it 'should send the appropriate action' do
35
+ ami_client.should_receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => component_id).and_return(response)
35
36
  subject.execute
36
37
  end
37
38
 
38
39
  context 'with some parameters' do
39
40
  let(:params) { [1000, 'foo'] }
40
41
 
41
- let :expected_action do
42
- RubyAMI::Action.new 'AGI', 'Channel' => channel, 'Command' => 'WAIT FOR DIGIT "1000" "foo"', 'CommandID' => component_id
43
- end
44
-
45
42
  let :original_command do
46
43
  Punchblock::Component::Asterisk::AGI::Command.new :name => 'WAIT FOR DIGIT', :params => params
47
44
  end
48
45
 
49
- it 'should send the appropriate RubyAMI::Action' do
50
- mock_call.should_receive(:send_ami_action).once.with(expected_action).and_return(expected_action)
46
+ it 'should send the appropriate action' do
47
+ ami_client.should_receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'WAIT FOR DIGIT "1000" "foo"', 'CommandID' => component_id).and_return(response)
51
48
  subject.execute
52
49
  end
53
50
  end
@@ -56,7 +53,6 @@ module Punchblock
56
53
  context 'when the AMI action completes' do
57
54
  before do
58
55
  original_command.request!
59
- original_command.execute!
60
56
  end
61
57
 
62
58
  let :expected_response do
@@ -64,25 +60,26 @@ module Punchblock
64
60
  end
65
61
 
66
62
  let :response do
67
- RubyAMI::Response.new.tap do |r|
68
- r['ActionID'] = "552a9d9f-46d7-45d8-a257-06fe95f48d99"
69
- r['Message'] = 'Added AGI original_command to queue'
70
- end
63
+ RubyAMI::Response.new 'ActionID' => "552a9d9f-46d7-45d8-a257-06fe95f48d99",
64
+ 'Message' => 'Added AGI original_command to queue'
71
65
  end
72
66
 
73
67
  it 'should send the component node a ref with the action ID' do
74
- original_command.should_receive(:response=).once.with(expected_response)
75
- subject.action << response
68
+ ami_client.should_receive(:send_action).once.and_return response
69
+ subject.execute
70
+ original_command.response(1).should eql(expected_response)
76
71
  end
77
72
 
78
73
  context 'with an error' do
79
- let :error do
74
+ let :response do
80
75
  RubyAMI::Error.new.tap { |e| e.message = 'Action failed' }
81
76
  end
82
77
 
83
78
  it 'should send the component node false' do
84
- original_command.should_receive(:response=).once.with false
85
- subject.action << error
79
+ ami_client.should_receive(:send_action).once.and_raise response
80
+ subject.execute
81
+ original_command.response(1).should be_false
82
+ subject.should_not be_alive
86
83
  end
87
84
  end
88
85
  end
@@ -97,13 +94,12 @@ module Punchblock
97
94
 
98
95
  context 'of type Exec' do
99
96
  let(:ami_event) do
100
- RubyAMI::Event.new("AsyncAGI").tap do |e|
101
- e["SubEvent"] = "Exec"
102
- e["Channel"] = channel
103
- e["CommandId"] = component_id
104
- e["Command"] = "EXEC ANSWER"
105
- e["Result"] = "200%20result=123%20(timeout)%0A"
106
- end
97
+ RubyAMI::Event.new 'AsyncAGI',
98
+ "SubEvent" => "Exec",
99
+ "Channel" => channel,
100
+ "CommandId" => component_id,
101
+ "Command" => "EXEC ANSWER",
102
+ "Result" => "200%20result=123%20(timeout)%0A"
107
103
  end
108
104
 
109
105
  let :expected_complete_reason do
@@ -122,6 +118,38 @@ module Punchblock
122
118
  complete_event.component_id.should be == component_id.to_s
123
119
  complete_event.reason.should be == expected_complete_reason
124
120
  end
121
+
122
+ context "when the command was ASYNCAGI BREAK" do
123
+ let :original_command do
124
+ Punchblock::Component::Asterisk::AGI::Command.new :name => 'ASYNCAGI BREAK'
125
+ end
126
+
127
+ let(:chan_var) { nil }
128
+
129
+ before do
130
+ response = RubyAMI::Response.new 'Value' => chan_var
131
+ ami_client.should_receive(:send_action).once.with('GetVar', 'Channel' => channel, 'Variable' => 'PUNCHBLOCK_END_ON_ASYNCAGI_BREAK').and_return response
132
+ end
133
+
134
+ it 'should not send an end (hangup) event to the translator' do
135
+ translator.should_receive(:handle_pb_event).once.with kind_of(Punchblock::Event::Complete)
136
+ translator.should_receive(:handle_pb_event).never.with kind_of(Punchblock::Event::End)
137
+ subject.handle_ami_event ami_event
138
+ end
139
+
140
+ context "when the PUNCHBLOCK_END_ON_ASYNCAGI_BREAK channel var is set" do
141
+ let(:chan_var) { 'true' }
142
+
143
+ it 'should send an end (hangup) event to the translator' do
144
+ expected_end_event = Punchblock::Event::End.new reason: :hangup,
145
+ target_call_id: mock_call.id
146
+
147
+ translator.should_receive(:handle_pb_event).once.with kind_of(Punchblock::Event::Complete)
148
+ translator.should_receive(:handle_pb_event).once.with expected_end_event
149
+ subject.handle_ami_event ami_event
150
+ end
151
+ end
152
+ end
125
153
  end
126
154
  end
127
155
  end
@@ -10,18 +10,19 @@ module Punchblock
10
10
  describe AMIAction do
11
11
  include HasMockCallbackConnection
12
12
 
13
- let(:mock_translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection }
13
+ let(:ami_client) { stub('AMI Client').as_null_object }
14
+ let(:mock_translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
14
15
 
15
16
  let :original_command do
16
17
  Punchblock::Component::Asterisk::AMI::Action.new :name => 'ExtensionStatus', :params => { :context => 'default', :exten => 'idonno' }
17
18
  end
18
19
 
19
- subject { AMIAction.new original_command, mock_translator }
20
-
21
- let :expected_action do
22
- RubyAMI::Action.new 'ExtensionStatus', 'Context' => 'default', 'Exten' => 'idonno'
20
+ before do
21
+ original_command.request!
23
22
  end
24
23
 
24
+ subject { AMIAction.new original_command, mock_translator, ami_client }
25
+
25
26
  context 'initial execution' do
26
27
  let(:component_id) { Punchblock.new_uuid }
27
28
 
@@ -31,28 +32,21 @@ module Punchblock
31
32
 
32
33
  before { stub_uuids component_id }
33
34
 
34
- it 'should send the appropriate RubyAMI::Action and send the component node a ref with the action ID' do
35
- mock_translator.should_receive(:send_ami_action).once.with(expected_action).and_return(expected_action)
36
- original_command.should_receive(:response=).once.with(expected_response)
35
+ it 'should send the appropriate RubyAMI::Action and send the component node a ref' do
36
+ ami_client.should_receive(:send_action).once.with('ExtensionStatus', 'Context' => 'default', 'Exten' => 'idonno').and_return(RubyAMI::Response.new)
37
37
  subject.execute
38
+ original_command.response(1).should == expected_response
38
39
  end
39
40
  end
40
41
 
41
42
  context 'when the AMI action completes' do
42
- before do
43
- original_command.request!
44
- original_command.execute!
45
- end
46
-
47
43
  let :response do
48
- RubyAMI::Response.new.tap do |r|
49
- r['ActionID'] = "552a9d9f-46d7-45d8-a257-06fe95f48d99"
50
- r['Message'] = 'Channel status will follow'
51
- r["Exten"] = "idonno"
52
- r["Context"] = "default"
53
- r["Hint"] = ""
54
- r["Status"] = "-1"
55
- end
44
+ RubyAMI::Response.new 'ActionID' => '552a9d9f-46d7-45d8-a257-06fe95f48d99',
45
+ 'Message' => 'Channel status will follow',
46
+ 'Exten' => 'idonno',
47
+ 'Context' => 'default',
48
+ 'Hint' => '',
49
+ 'Status' => '-1'
56
50
  end
57
51
 
58
52
  let :expected_complete_reason do
@@ -61,8 +55,9 @@ module Punchblock
61
55
 
62
56
  context 'for a non-causal action' do
63
57
  it 'should send a complete event to the component node' do
58
+ ami_client.should_receive(:send_action).once.and_return response
64
59
  subject.wrapped_object.should_receive(:send_complete_event).once.with expected_complete_reason
65
- subject.handle_response response
60
+ subject.execute
66
61
  end
67
62
  end
68
63
 
@@ -76,24 +71,20 @@ module Punchblock
76
71
  end
77
72
 
78
73
  let :event do
79
- RubyAMI::Event.new('CoreShowChannel').tap do |e|
80
- e['ActionID'] = "552a9d9f-46d7-45d8-a257-06fe95f48d99"
81
- e['Channel'] = 'SIP/127.0.0.1-00000013'
82
- e['UniqueID'] = '1287686437.19'
83
- e['Context'] = 'adhearsion'
84
- e['Extension'] = '23432'
85
- e['Priority'] = '2'
86
- e['ChannelState'] = '6'
87
- e['ChannelStateDesc'] = 'Up'
88
- end
74
+ RubyAMI::Event.new 'CoreShowChannel', 'ActionID' => "552a9d9f-46d7-45d8-a257-06fe95f48d99",
75
+ 'Channel' => 'SIP/127.0.0.1-00000013',
76
+ 'UniqueID' => '1287686437.19',
77
+ 'Context' => 'adhearsion',
78
+ 'Extension' => '23432',
79
+ 'Priority' => '2',
80
+ 'ChannelState' => '6',
81
+ 'ChannelStateDesc' => 'Up'
89
82
  end
90
83
 
91
84
  let :terminating_event do
92
- RubyAMI::Event.new('CoreShowChannelsComplete').tap do |e|
93
- e['EventList'] = 'Complete'
94
- e['ListItems'] = '3'
95
- e['ActionID'] = 'umtLtvSg-RN5n-GEay-Z786-YdiaSLNXkcYN'
96
- end
85
+ RubyAMI::Event.new 'CoreShowChannelsComplete', 'EventList' => 'Complete',
86
+ 'ListItems' => '3',
87
+ 'ActionID' => 'umtLtvSg-RN5n-GEay-Z786-YdiaSLNXkcYN'
97
88
  end
98
89
 
99
90
  let :event_node do
@@ -112,21 +103,22 @@ module Punchblock
112
103
  Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :message => 'Channel status will follow', :attributes => {:exten => "idonno", :context => "default", :hint => "", :status => "-1", :eventlist => 'Complete', :listitems => '3'}
113
104
  end
114
105
 
106
+ before { ami_client.should_receive(:send_action).once.and_return response }
107
+
115
108
  it 'should send events to the component node' do
116
109
  event_node
117
110
  original_command.register_handler :internal, Punchblock::Event::Asterisk::AMI::Event do |event|
118
111
  @event = event
119
112
  end
120
- action = subject.action
121
- action << event
122
- subject.handle_response response
123
- action << terminating_event
113
+ response.events << event << terminating_event
114
+ subject.execute
124
115
  @event.should be == event_node
125
116
  end
126
117
 
127
118
  it 'should send a complete event to the component node' do
128
- subject.action << response
129
- subject.action << terminating_event
119
+ response.events << event << terminating_event
120
+
121
+ subject.execute
130
122
 
131
123
  original_command.complete_event(0.5).reason.should be == expected_complete_reason
132
124
  end
@@ -138,12 +130,14 @@ module Punchblock
138
130
  end
139
131
 
140
132
  let :expected_complete_reason do
141
- Punchblock::Event::Complete::Error.new :details => 'Action failed'
133
+ Punchblock::Event::Complete::Error.new :details => 'Action failed', component_id: subject.id
142
134
  end
143
135
 
144
136
  it 'should send a complete event to the component node' do
145
- subject.wrapped_object.should_receive(:send_complete_event).once.with expected_complete_reason
146
- subject.handle_response error
137
+ ami_client.should_receive(:send_action).once.and_raise error
138
+ expected_complete_reason
139
+ subject.execute
140
+ original_command.complete_event(0.5).reason.should be == expected_complete_reason
147
141
  end
148
142
  end
149
143
  end
@@ -10,8 +10,9 @@ module Punchblock
10
10
  include HasMockCallbackConnection
11
11
 
12
12
  let(:media_engine) { nil }
13
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
14
- let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
13
+ let(:ami_client) { mock('AMI') }
14
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
15
+ let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
15
16
  let(:original_command_options) { {} }
16
17
 
17
18
  let :original_command do
@@ -53,11 +54,10 @@ module Punchblock
53
54
  end
54
55
 
55
56
  def ami_event_for_dtmf(digit, position)
56
- RubyAMI::Event.new('DTMF').tap do |e|
57
- e['Digit'] = digit.to_s
58
- e['Start'] = position == :start ? 'Yes' : 'No'
59
- e['End'] = position == :end ? 'Yes' : 'No'
60
- end
57
+ RubyAMI::Event.new 'DTMF',
58
+ 'Digit' => digit.to_s,
59
+ 'Start' => position == :start ? 'Yes' : 'No',
60
+ 'End' => position == :end ? 'Yes' : 'No'
61
61
  end
62
62
 
63
63
  def send_ami_events_for_dtmf(digit)
@@ -10,8 +10,9 @@ module Punchblock
10
10
  include HasMockCallbackConnection
11
11
 
12
12
  let(:media_engine) { nil }
13
- let(:translator) { Punchblock::Translator::Asterisk.new mock('AMI'), connection, media_engine }
14
- let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator }
13
+ let(:ami_client) { mock('AMI') }
14
+ let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
15
+ let(:mock_call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
15
16
 
16
17
  let :original_command do
17
18
  Punchblock::Component::Output.new command_options
@@ -70,24 +71,32 @@ module Punchblock
70
71
  end
71
72
 
72
73
  it "should execute Swift" do
73
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options
74
+ mock_call.should_receive(:execute_agi_command).once.with 'EXEC Swift', ssml_with_options
74
75
  subject.execute
75
76
  end
76
77
 
77
78
  it 'should send a complete event when Swift completes' do
78
- async_proxy = mock_call.async
79
- def async_proxy.send_agi_action(*args, &block)
80
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
81
- end
79
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
82
80
  subject.execute
83
81
  original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
84
82
  end
85
83
 
84
+ context "when we get a RubyAMI Error" do
85
+ it "should send an error complete event" do
86
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
87
+ mock_call.should_receive(:execute_agi_command).and_raise error
88
+ subject.execute
89
+ complete_reason = original_command.complete_event(0.1).reason
90
+ complete_reason.should be_a Punchblock::Event::Complete::Error
91
+ complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
92
+ end
93
+ end
94
+
86
95
  describe 'interrupt_on' do
87
96
  context "set to nil" do
88
97
  let(:command_opts) { { :interrupt_on => nil } }
89
98
  it "should not add interrupt arguments" do
90
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options
99
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
91
100
  subject.execute
92
101
  end
93
102
  end
@@ -96,7 +105,7 @@ module Punchblock
96
105
  let(:command_opts) { { :interrupt_on => :any } }
97
106
  it "should add the interrupt options to the argument" do
98
107
  expect_answered
99
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options('', '|1|1')
108
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
100
109
  subject.execute
101
110
  end
102
111
  end
@@ -105,7 +114,7 @@ module Punchblock
105
114
  let(:command_opts) { { :interrupt_on => :dtmf } }
106
115
  it "should add the interrupt options to the argument" do
107
116
  expect_answered
108
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options('', '|1|1')
117
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('', '|1|1')).and_return code: 200, result: 1
109
118
  subject.execute
110
119
  end
111
120
  end
@@ -124,7 +133,7 @@ module Punchblock
124
133
  context "set to nil" do
125
134
  let(:command_opts) { { :voice => nil } }
126
135
  it "should not add a voice at the beginning of the argument" do
127
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options
136
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options).and_return code: 200, result: 1
128
137
  subject.execute
129
138
  end
130
139
  end
@@ -132,7 +141,7 @@ module Punchblock
132
141
  context "set to Leonard" do
133
142
  let(:command_opts) { { :voice => "Leonard" } }
134
143
  it "should add a voice at the beginning of the argument" do
135
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Swift', ssml_with_options('Leonard^', '')
144
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Swift', ssml_with_options('Leonard^', '')).and_return code: 200, result: 1
136
145
  subject.execute
137
146
  end
138
147
  end
@@ -159,14 +168,14 @@ module Punchblock
159
168
  end
160
169
 
161
170
  def expect_mrcpsynth_with_options(options)
162
- mock_call.async.should_receive(:send_agi_action).once.with do |*args|
171
+ mock_call.should_receive(:execute_agi_command).once.with do |*args|
163
172
  args[0].should be == 'EXEC MRCPSynth'
164
173
  args[2].should match options
165
- end
174
+ end.and_return code: 200, result: 1
166
175
  end
167
176
 
168
177
  it "should execute MRCPSynth" do
169
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, ''
178
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC MRCPSynth', ssml_doc.to_s.squish.gsub(/["\\]/) { |m| "\\#{m}" }, '').and_return code: 200, result: 1
170
179
  subject.execute
171
180
  end
172
181
 
@@ -178,23 +187,31 @@ module Punchblock
178
187
  end
179
188
 
180
189
  it 'should escape TTS strings containing a comma' do
181
- mock_call.async.should_receive(:send_agi_action).once.with do |*args|
190
+ mock_call.should_receive(:execute_agi_command).once.with do |*args|
182
191
  args[0].should be == 'EXEC MRCPSynth'
183
192
  args[1].should match(/this\\, here\\, is a test/)
184
- end
193
+ end.and_return code: 200, result: 1
185
194
  subject.execute
186
195
  end
187
196
  end
188
197
 
189
198
  it 'should send a complete event when MRCPSynth completes' do
190
- async_proxy = mock_call.async
191
- def async_proxy.send_agi_action(*args, &block)
192
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
193
- end
199
+ mock_call.should_receive(:execute_agi_command).and_return code: 200, result: 1
194
200
  subject.execute
195
201
  original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
196
202
  end
197
203
 
204
+ context "when we get a RubyAMI Error" do
205
+ it "should send an error complete event" do
206
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
207
+ mock_call.should_receive(:execute_agi_command).and_raise error
208
+ subject.execute
209
+ complete_reason = original_command.complete_event(0.1).reason
210
+ complete_reason.should be_a Punchblock::Event::Complete::Error
211
+ complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
212
+ end
213
+ end
214
+
198
215
  describe 'ssml' do
199
216
  context 'unset' do
200
217
  let(:command_opts) { { :ssml => nil } }
@@ -360,11 +377,11 @@ module Punchblock
360
377
  [:asterisk, nil].each do |media_engine|
361
378
  context "with a media engine of #{media_engine.inspect}" do
362
379
  def expect_playback(filename = audio_filename)
363
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Playback', filename
380
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', filename).and_return code: 200
364
381
  end
365
382
 
366
383
  def expect_playback_noanswer
367
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Playback', audio_filename + ',noanswer'
384
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename + ',noanswer').and_return code: 200
368
385
  end
369
386
 
370
387
  let(:audio_filename) { 'http://foo.com/bar.mp3' }
@@ -413,13 +430,22 @@ module Punchblock
413
430
  def mock_call.answered?
414
431
  true
415
432
  end
416
- async_proxy = mock_call.async
417
- def async_proxy.send_agi_action(*args, &block)
418
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
419
- end
433
+ expect_playback
420
434
  subject.execute
421
435
  original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
422
436
  end
437
+
438
+ context "when we get a RubyAMI Error" do
439
+ it "should send an error complete event" do
440
+ expect_answered
441
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
442
+ mock_call.should_receive(:execute_agi_command).and_raise error
443
+ subject.execute
444
+ complete_reason = original_command.complete_event(0.1).reason
445
+ complete_reason.should be_a Punchblock::Event::Complete::Error
446
+ complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
447
+ end
448
+ end
423
449
  end
424
450
 
425
451
  context 'with a single text node without spaces' do
@@ -438,14 +464,23 @@ module Punchblock
438
464
 
439
465
  it 'should send a complete event when the file finishes playback' do
440
466
  expect_answered
441
- async_proxy = mock_call.async
442
- def async_proxy.send_agi_action(*args, &block)
443
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
444
- end
467
+ expect_playback
445
468
  subject.execute
446
469
  original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
447
470
  end
448
471
 
472
+ context "when we get a RubyAMI Error" do
473
+ it "should send an error complete event" do
474
+ expect_answered
475
+ error = RubyAMI::Error.new.tap { |e| e.message = 'FooBar' }
476
+ mock_call.should_receive(:execute_agi_command).and_raise error
477
+ subject.execute
478
+ complete_reason = original_command.complete_event(0.1).reason
479
+ complete_reason.should be_a Punchblock::Event::Complete::Error
480
+ complete_reason.details.should == "Terminated due to AMI error 'FooBar'"
481
+ end
482
+ end
483
+
449
484
  context "with early media playback" do
450
485
  it "should play the file with Playback" do
451
486
  expect_answered false
@@ -507,10 +542,7 @@ module Punchblock
507
542
 
508
543
  it 'should send a complete event after the final file has finished playback' do
509
544
  expect_answered
510
- async_proxy = mock_call.async
511
- def async_proxy.send_agi_action(*args, &block)
512
- block.call Punchblock::Component::Asterisk::AGI::Command::Complete::Success.new(:code => 200, :result => 1)
513
- end
545
+ expect_playback [audio_filename1, audio_filename2].join('&')
514
546
  latch = CountDownLatch.new 1
515
547
  original_command.should_receive(:add_event).once.with do |e|
516
548
  e.reason.should be_a Punchblock::Component::Output::Complete::Success
@@ -660,11 +692,10 @@ module Punchblock
660
692
 
661
693
  describe 'interrupt_on' do
662
694
  def ami_event_for_dtmf(digit, position)
663
- RubyAMI::Event.new('DTMF').tap do |e|
664
- e['Digit'] = digit.to_s
665
- e['Start'] = position == :start ? 'Yes' : 'No'
666
- e['End'] = position == :end ? 'Yes' : 'No'
667
- end
695
+ RubyAMI::Event.new 'DTMF',
696
+ 'Digit' => digit.to_s,
697
+ 'Start' => position == :start ? 'Yes' : 'No',
698
+ 'End' => position == :end ? 'Yes' : 'No'
668
699
  end
669
700
 
670
701
  def send_ami_events_for_dtmf(digit)
@@ -675,11 +706,10 @@ module Punchblock
675
706
  let(:reason) { original_command.complete_event(5).reason }
676
707
  let(:channel) { "SIP/1234-00000000" }
677
708
  let :ami_event do
678
- RubyAMI::Event.new('AsyncAGI').tap do |e|
679
- e['SubEvent'] = "Start"
680
- e['Channel'] = channel
681
- e['Env'] = "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
682
- end
709
+ RubyAMI::Event.new 'AsyncAGI',
710
+ 'SubEvent' => "Start",
711
+ 'Channel' => channel,
712
+ 'Env' => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
683
713
  end
684
714
 
685
715
  context "set to nil" do
@@ -699,7 +729,8 @@ module Punchblock
699
729
 
700
730
  before do
701
731
  expect_answered
702
- expect_playback
732
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
733
+ subject.wrapped_object.should_receive(:send_success).and_return nil
703
734
  end
704
735
 
705
736
  context "when a DTMF digit is received" do
@@ -729,7 +760,8 @@ module Punchblock
729
760
 
730
761
  before do
731
762
  expect_answered
732
- expect_playback
763
+ mock_call.should_receive(:execute_agi_command).once.with('EXEC Playback', audio_filename)
764
+ subject.wrapped_object.should_receive(:send_success).and_return nil
733
765
  end
734
766
 
735
767
  context "when a DTMF digit is received" do
@@ -787,7 +819,7 @@ module Punchblock
787
819
 
788
820
  it "should use the media renderer set and not the platform default" do
789
821
  expect_answered
790
- mock_call.async.should_receive(:send_agi_action).once.with 'EXEC Playback', audio_filename
822
+ mock_call.should_receive(:execute_agi_command).once.with 'EXEC Playback', audio_filename
791
823
  subject.execute
792
824
  end
793
825
  end
@@ -809,11 +841,10 @@ module Punchblock
809
841
  let(:reason) { original_command.complete_event(5).reason }
810
842
  let(:channel) { "SIP/1234-00000000" }
811
843
  let :ami_event do
812
- RubyAMI::Event.new('AsyncAGI').tap do |e|
813
- e['SubEvent'] = "Start"
814
- e['Channel'] = channel
815
- e['Env'] = "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
816
- end
844
+ RubyAMI::Event.new 'AsyncAGI',
845
+ 'SubEvent' => "Start",
846
+ 'Channel' => channel,
847
+ 'Env' => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
817
848
  end
818
849
 
819
850
  before do