punchblock 1.3.0 → 1.4.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.
- data/CHANGELOG.md +5 -0
- data/lib/punchblock.rb +1 -1
- data/lib/punchblock/connection.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +0 -1
- data/lib/punchblock/connection/freeswitch.rb +49 -0
- data/lib/punchblock/event/offer.rb +1 -1
- data/lib/punchblock/translator.rb +5 -0
- data/lib/punchblock/translator/asterisk.rb +16 -28
- data/lib/punchblock/translator/asterisk/call.rb +4 -21
- data/lib/punchblock/translator/asterisk/component.rb +0 -5
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +0 -3
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +0 -1
- data/lib/punchblock/translator/asterisk/component/input.rb +7 -97
- data/lib/punchblock/translator/asterisk/component/output.rb +0 -4
- data/lib/punchblock/translator/asterisk/component/record.rb +0 -2
- data/lib/punchblock/translator/freeswitch.rb +153 -0
- data/lib/punchblock/translator/freeswitch/call.rb +265 -0
- data/lib/punchblock/translator/freeswitch/component.rb +92 -0
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +57 -0
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +17 -0
- data/lib/punchblock/translator/freeswitch/component/input.rb +29 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +56 -0
- data/lib/punchblock/translator/freeswitch/component/record.rb +79 -0
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +26 -0
- data/lib/punchblock/translator/input_component.rb +108 -0
- data/lib/punchblock/version.rb +1 -1
- data/punchblock.gemspec +3 -2
- data/spec/punchblock/connection/freeswitch_spec.rb +90 -0
- data/spec/punchblock/translator/asterisk/call_spec.rb +23 -2
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +3 -3
- data/spec/punchblock/translator/asterisk_spec.rb +1 -1
- data/spec/punchblock/translator/freeswitch/call_spec.rb +922 -0
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +279 -0
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +312 -0
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +369 -0
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +373 -0
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +285 -0
- data/spec/punchblock/translator/freeswitch/component_spec.rb +118 -0
- data/spec/punchblock/translator/freeswitch_spec.rb +597 -0
- data/spec/punchblock_spec.rb +11 -0
- data/spec/spec_helper.rb +1 -0
- metadata +52 -7
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Freeswitch
|
|
8
|
+
module Component
|
|
9
|
+
describe TTSOutput do
|
|
10
|
+
let(:connection) do
|
|
11
|
+
mock_connection_with_event_handler do |event|
|
|
12
|
+
original_command.add_event event
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
let(:media_engine) { :flite }
|
|
16
|
+
let(:default_voice) { :hal }
|
|
17
|
+
let(:translator) { Punchblock::Translator::Freeswitch.new connection }
|
|
18
|
+
let(:mock_call) { Punchblock::Translator::Freeswitch::Call.new 'foo', translator }
|
|
19
|
+
|
|
20
|
+
let :original_command do
|
|
21
|
+
Punchblock::Component::Output.new command_options
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let :ssml_doc do
|
|
25
|
+
RubySpeech::SSML.draw do
|
|
26
|
+
say_as(:interpret_as => :cardinal) { 'FOO' }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let :command_options do
|
|
31
|
+
{ :ssml => ssml_doc }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def execute
|
|
35
|
+
subject.execute media_engine, default_voice
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
subject { described_class.new original_command, mock_call }
|
|
39
|
+
|
|
40
|
+
describe '#execute' do
|
|
41
|
+
before { original_command.request! }
|
|
42
|
+
def expect_playback(voice = default_voice)
|
|
43
|
+
subject.wrapped_object.expects(:application).once.with :speak, "#{media_engine}|#{voice}|#{ssml_doc}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
let :ssml_doc do
|
|
47
|
+
RubySpeech::SSML.draw do
|
|
48
|
+
audio :src => 'http://foo.com/bar.mp3'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
let(:command_opts) { {} }
|
|
53
|
+
|
|
54
|
+
let :command_options do
|
|
55
|
+
{ :ssml => ssml_doc }.merge(command_opts)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
let :original_command do
|
|
59
|
+
Punchblock::Component::Output.new command_options
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe 'ssml' do
|
|
63
|
+
context 'unset' do
|
|
64
|
+
let(:command_opts) { { :ssml => nil } }
|
|
65
|
+
it "should return an error and not execute any actions" do
|
|
66
|
+
execute
|
|
67
|
+
error = ProtocolError.new.setup 'option error', 'An SSML document is required.'
|
|
68
|
+
original_command.response(0.1).should be == error
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context 'with an SSML node' do
|
|
73
|
+
it 'should speak the document using the speak application' do
|
|
74
|
+
expect_playback
|
|
75
|
+
execute
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'should send a complete event when the speak finishes' do
|
|
79
|
+
expect_playback.yields true
|
|
80
|
+
execute
|
|
81
|
+
subject.handle_es_event RubyFS::Event.new(nil, :event_name => "CHANNEL_EXECUTE_COMPLETE")
|
|
82
|
+
original_command.complete_event(0.1).reason.should be_a Punchblock::Component::Output::Complete::Success
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe 'start-offset' do
|
|
88
|
+
context 'unset' do
|
|
89
|
+
let(:command_opts) { { :start_offset => nil } }
|
|
90
|
+
it 'should not pass any options to Playback' do
|
|
91
|
+
expect_playback
|
|
92
|
+
execute
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context 'set' do
|
|
97
|
+
let(:command_opts) { { :start_offset => 10 } }
|
|
98
|
+
it "should return an error and not execute any actions" do
|
|
99
|
+
execute
|
|
100
|
+
error = ProtocolError.new.setup 'option error', 'A start_offset value is unsupported.'
|
|
101
|
+
original_command.response(0.1).should be == error
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe 'start-paused' do
|
|
107
|
+
context 'false' do
|
|
108
|
+
let(:command_opts) { { :start_paused => false } }
|
|
109
|
+
it 'should not pass any options to Playback' do
|
|
110
|
+
expect_playback
|
|
111
|
+
execute
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context 'true' do
|
|
116
|
+
let(:command_opts) { { :start_paused => true } }
|
|
117
|
+
it "should return an error and not execute any actions" do
|
|
118
|
+
execute
|
|
119
|
+
error = ProtocolError.new.setup 'option error', 'A start_paused value is unsupported.'
|
|
120
|
+
original_command.response(0.1).should be == error
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe 'repeat-interval' do
|
|
126
|
+
context 'unset' do
|
|
127
|
+
let(:command_opts) { { :repeat_interval => nil } }
|
|
128
|
+
it 'should not pass any options to Playback' do
|
|
129
|
+
expect_playback
|
|
130
|
+
execute
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
context 'set' do
|
|
135
|
+
let(:command_opts) { { :repeat_interval => 10 } }
|
|
136
|
+
it "should return an error and not execute any actions" do
|
|
137
|
+
execute
|
|
138
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_interval value is unsupported.'
|
|
139
|
+
original_command.response(0.1).should be == error
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
describe 'repeat-times' do
|
|
145
|
+
context 'unset' do
|
|
146
|
+
let(:command_opts) { { :repeat_times => nil } }
|
|
147
|
+
it 'should not pass any options to Playback' do
|
|
148
|
+
expect_playback
|
|
149
|
+
execute
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
context 'set' do
|
|
154
|
+
let(:command_opts) { { :repeat_times => 2 } }
|
|
155
|
+
it "should return an error and not execute any actions" do
|
|
156
|
+
execute
|
|
157
|
+
error = ProtocolError.new.setup 'option error', 'A repeat_times value is unsupported.'
|
|
158
|
+
original_command.response(0.1).should be == error
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
describe 'max-time' do
|
|
164
|
+
context 'unset' do
|
|
165
|
+
let(:command_opts) { { :max_time => nil } }
|
|
166
|
+
it 'should not pass any options to Playback' do
|
|
167
|
+
expect_playback
|
|
168
|
+
execute
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context 'set' do
|
|
173
|
+
let(:command_opts) { { :max_time => 30 } }
|
|
174
|
+
it "should return an error and not execute any actions" do
|
|
175
|
+
execute
|
|
176
|
+
error = ProtocolError.new.setup 'option error', 'A max_time value is unsupported.'
|
|
177
|
+
original_command.response(0.1).should be == error
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe 'voice' do
|
|
183
|
+
context 'unset' do
|
|
184
|
+
let(:command_opts) { { :voice => nil } }
|
|
185
|
+
it 'should use the default voice' do
|
|
186
|
+
expect_playback
|
|
187
|
+
execute
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context 'set' do
|
|
192
|
+
let(:command_opts) { { :voice => 'alison' } }
|
|
193
|
+
it "should execute speak with the specified voice" do
|
|
194
|
+
expect_playback 'alison'
|
|
195
|
+
execute
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
describe 'interrupt_on' do
|
|
201
|
+
context "set to nil" do
|
|
202
|
+
let(:command_opts) { { :interrupt_on => nil } }
|
|
203
|
+
it "should not pass any digits to Playback" do
|
|
204
|
+
expect_playback
|
|
205
|
+
execute
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "set to :any" do
|
|
210
|
+
let(:command_opts) { { :interrupt_on => :any } }
|
|
211
|
+
it "should return an error and not execute any actions" do
|
|
212
|
+
execute
|
|
213
|
+
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of any is unsupported.'
|
|
214
|
+
original_command.response(0.1).should be == error
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
context "set to :dtmf" do
|
|
219
|
+
let(:command_opts) { { :interrupt_on => :dtmf } }
|
|
220
|
+
it "should return an error and not execute any actions" do
|
|
221
|
+
execute
|
|
222
|
+
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of dtmf is unsupported.'
|
|
223
|
+
original_command.response(0.1).should be == error
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context "set to :speech" do
|
|
228
|
+
let(:command_opts) { { :interrupt_on => :speech } }
|
|
229
|
+
it "should return an error and not execute any actions" do
|
|
230
|
+
execute
|
|
231
|
+
error = ProtocolError.new.setup 'option error', 'An interrupt-on value of speech is unsupported.'
|
|
232
|
+
original_command.response(0.1).should be == error
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
describe "#execute_command" do
|
|
239
|
+
context "with a command it does not understand" do
|
|
240
|
+
let(:command) { Punchblock::Component::Output::Pause.new }
|
|
241
|
+
|
|
242
|
+
before { command.request! }
|
|
243
|
+
|
|
244
|
+
it "returns a ProtocolError response" do
|
|
245
|
+
subject.execute_command command
|
|
246
|
+
command.response(0.1).should be_a ProtocolError
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
context "with a Stop command" do
|
|
251
|
+
let(:command) { Punchblock::Component::Stop.new }
|
|
252
|
+
let(:reason) { original_command.complete_event(5).reason }
|
|
253
|
+
|
|
254
|
+
before do
|
|
255
|
+
command.request!
|
|
256
|
+
original_command.request!
|
|
257
|
+
original_command.execute!
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it "sets the command response to true" do
|
|
261
|
+
subject.wrapped_object.expects(:application)
|
|
262
|
+
subject.execute_command command
|
|
263
|
+
command.response(0.1).should be == true
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "sends the correct complete event" do
|
|
267
|
+
subject.wrapped_object.expects(:application)
|
|
268
|
+
original_command.should_not be_complete
|
|
269
|
+
subject.execute_command command
|
|
270
|
+
reason.should be_a Punchblock::Event::Complete::Stop
|
|
271
|
+
original_command.should be_complete
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it "breaks the current dialplan application" do
|
|
275
|
+
subject.wrapped_object.expects(:application).once.with 'break'
|
|
276
|
+
subject.execute_command command
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Freeswitch
|
|
8
|
+
describe Component do
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Component
|
|
13
|
+
describe Component do
|
|
14
|
+
let(:connection) { Punchblock::Connection::Freeswitch.new }
|
|
15
|
+
let(:translator) { connection.translator }
|
|
16
|
+
let(:call) { Punchblock::Translator::Freeswitch::Call.new 'foo', translator }
|
|
17
|
+
let(:command) { Punchblock::Component::Input.new }
|
|
18
|
+
|
|
19
|
+
subject { Component.new command, call }
|
|
20
|
+
|
|
21
|
+
before { command.request! }
|
|
22
|
+
|
|
23
|
+
describe '#handle_es_event' do
|
|
24
|
+
context 'with a handler registered for a matching event' do
|
|
25
|
+
let :es_event do
|
|
26
|
+
RubyFS::Event.new nil, :event_name => 'CHANNEL_EXECUTE'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
let(:response) { mock 'Response' }
|
|
30
|
+
|
|
31
|
+
it 'should execute the handler' do
|
|
32
|
+
response.expects(:call).once.with es_event
|
|
33
|
+
subject.register_handler :es, :event_name => 'CHANNEL_EXECUTE' do |event|
|
|
34
|
+
response.call event
|
|
35
|
+
end
|
|
36
|
+
subject.handle_es_event es_event
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#send_event" do
|
|
42
|
+
before { command.execute! }
|
|
43
|
+
|
|
44
|
+
let :event do
|
|
45
|
+
Punchblock::Event::Complete.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
let :expected_event do
|
|
49
|
+
Punchblock::Event::Complete.new.tap do |e|
|
|
50
|
+
e.target_call_id = call.id
|
|
51
|
+
e.component_id = subject.id
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should send the event to the connection" do
|
|
56
|
+
connection.expects(:handle_event).once.with expected_event
|
|
57
|
+
subject.send_event event
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#send_complete_event" do
|
|
62
|
+
before { command.execute! }
|
|
63
|
+
|
|
64
|
+
let(:reason) { Punchblock::Event::Complete::Stop.new }
|
|
65
|
+
let :expected_event do
|
|
66
|
+
Punchblock::Event::Complete.new.tap do |c|
|
|
67
|
+
c.reason = Punchblock::Event::Complete::Stop.new
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should send a complete event with the specified reason" do
|
|
72
|
+
subject.wrapped_object.expects(:send_event).once.with expected_event
|
|
73
|
+
subject.send_complete_event reason
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should cause the actor to be shut down" do
|
|
77
|
+
subject.wrapped_object.stubs(:send_event).returns true
|
|
78
|
+
subject.send_complete_event reason
|
|
79
|
+
sleep 0.2
|
|
80
|
+
subject.should_not be_alive
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#call_ended" do
|
|
85
|
+
it "should send a complete event with the call hangup reason" do
|
|
86
|
+
subject.wrapped_object.expects(:send_complete_event).once.with Punchblock::Event::Complete::Hangup.new
|
|
87
|
+
subject.call_ended
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "#application" do
|
|
92
|
+
it "should execute a FS application on the current call" do
|
|
93
|
+
call.expects(:application).once.with('appname', "%[punchblock_component_id=#{subject.id}]options")
|
|
94
|
+
subject.application 'appname', 'options'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#execute_command' do
|
|
99
|
+
before do
|
|
100
|
+
component_command.request!
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'with a command we do not understand' do
|
|
104
|
+
let :component_command do
|
|
105
|
+
Punchblock::Component::Stop.new :component_id => subject.id
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'sends an error in response to the command' do
|
|
109
|
+
subject.execute_command component_command
|
|
110
|
+
component_command.response.should be == ProtocolError.new.setup('command-not-acceptable', "Did not understand command for component #{subject.id}", call.id, subject.id)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
describe Freeswitch do
|
|
8
|
+
let(:connection) { mock 'Connection::Freeswitch' }
|
|
9
|
+
let(:media_engine) { :flite }
|
|
10
|
+
let(:default_voice) { :hal }
|
|
11
|
+
|
|
12
|
+
let(:translator) { described_class.new connection, media_engine, default_voice }
|
|
13
|
+
let(:stream) { mock 'RubyFS::Stream' }
|
|
14
|
+
|
|
15
|
+
before { connection.expects(:stream).times(0..1).returns stream }
|
|
16
|
+
|
|
17
|
+
subject { translator }
|
|
18
|
+
|
|
19
|
+
its(:connection) { should be connection }
|
|
20
|
+
its(:stream) { should be stream }
|
|
21
|
+
|
|
22
|
+
describe '#terminate' do
|
|
23
|
+
it "terminates all calls" do
|
|
24
|
+
call = described_class::Call.new 'foo', subject
|
|
25
|
+
subject.register_call call
|
|
26
|
+
subject.terminate
|
|
27
|
+
call.should_not be_alive
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#execute_command' do
|
|
32
|
+
describe 'with a call command' do
|
|
33
|
+
let(:command) { Command::Answer.new }
|
|
34
|
+
let(:call_id) { 'abc123' }
|
|
35
|
+
|
|
36
|
+
it 'executes the call command' do
|
|
37
|
+
subject.wrapped_object.expects(:execute_call_command).with do |c|
|
|
38
|
+
c.should be command
|
|
39
|
+
c.target_call_id.should be == call_id
|
|
40
|
+
end
|
|
41
|
+
subject.execute_command command, :call_id => call_id
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe 'with a global component command' do
|
|
46
|
+
let(:command) { Component::Stop.new }
|
|
47
|
+
let(:component_id) { '123abc' }
|
|
48
|
+
|
|
49
|
+
it 'executes the component command' do
|
|
50
|
+
subject.wrapped_object.expects(:execute_component_command).with do |c|
|
|
51
|
+
c.should be command
|
|
52
|
+
c.component_id.should be == component_id
|
|
53
|
+
end
|
|
54
|
+
subject.execute_command command, :component_id => component_id
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe 'with a global command' do
|
|
59
|
+
let(:command) { Command::Dial.new }
|
|
60
|
+
|
|
61
|
+
it 'executes the command directly' do
|
|
62
|
+
subject.wrapped_object.expects(:execute_global_command).with command
|
|
63
|
+
subject.execute_command command
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#register_call' do
|
|
69
|
+
let(:call_id) { 'abc123' }
|
|
70
|
+
let(:call) { described_class::Call.new call_id, subject }
|
|
71
|
+
|
|
72
|
+
before do
|
|
73
|
+
subject.register_call call
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'should make the call accessible by ID' do
|
|
77
|
+
subject.call_with_id(call_id).should be call
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#deregister_call' do
|
|
82
|
+
let(:call_id) { 'abc123' }
|
|
83
|
+
let(:call) { described_class::Call.new call_id, subject }
|
|
84
|
+
|
|
85
|
+
before do
|
|
86
|
+
subject.register_call call
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'should make the call inaccessible by ID' do
|
|
90
|
+
subject.call_with_id(call_id).should be call
|
|
91
|
+
subject.deregister_call call
|
|
92
|
+
subject.call_with_id(call_id).should be_nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe '#register_component' do
|
|
97
|
+
let(:component_id) { 'abc123' }
|
|
98
|
+
let(:component) { mock 'Foo', :id => component_id }
|
|
99
|
+
|
|
100
|
+
it 'should make the component accessible by ID' do
|
|
101
|
+
subject.register_component component
|
|
102
|
+
subject.component_with_id(component_id).should be component
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
describe '#execute_call_command' do
|
|
107
|
+
let(:call_id) { 'abc123' }
|
|
108
|
+
let(:command) { Command::Answer.new.tap { |c| c.target_call_id = call_id } }
|
|
109
|
+
|
|
110
|
+
context "with a known call ID" do
|
|
111
|
+
let(:call) { described_class::Call.new 'SIP/foo', subject }
|
|
112
|
+
|
|
113
|
+
before do
|
|
114
|
+
command.request!
|
|
115
|
+
call.stubs(:id).returns call_id
|
|
116
|
+
subject.register_call call
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'sends the command to the call for execution' do
|
|
120
|
+
call.expects(:execute_command!).once.with command
|
|
121
|
+
subject.execute_call_command command
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
let :end_error_event do
|
|
126
|
+
Punchblock::Event::End.new.tap do |e|
|
|
127
|
+
e.target_call_id = call_id
|
|
128
|
+
e.reason = :error
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context "for an outgoing call which began executing but crashed" do
|
|
133
|
+
let(:dial_command) { Command::Dial.new :to => 'SIP/1234', :from => 'abc123' }
|
|
134
|
+
|
|
135
|
+
let(:call_id) { dial_command.response.id }
|
|
136
|
+
|
|
137
|
+
before do
|
|
138
|
+
stream.stub_everything
|
|
139
|
+
subject.execute_command dial_command
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'sends an error in response to the command' do
|
|
143
|
+
call = subject.call_with_id call_id
|
|
144
|
+
|
|
145
|
+
call.wrapped_object.define_singleton_method(:oops) do
|
|
146
|
+
raise 'Woops, I died'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
connection.expects(:handle_event).once.with end_error_event
|
|
150
|
+
|
|
151
|
+
lambda { call.oops }.should raise_error(/Woops, I died/)
|
|
152
|
+
sleep 0.1
|
|
153
|
+
call.should_not be_alive
|
|
154
|
+
subject.call_with_id(call_id).should be_nil
|
|
155
|
+
|
|
156
|
+
command.request!
|
|
157
|
+
subject.execute_call_command command
|
|
158
|
+
command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "for an incoming call which began executing but crashed" do
|
|
163
|
+
let :es_event do
|
|
164
|
+
RubyFS::Event.new nil, :event_name => 'CHANNEL_PARK', :unique_id => 'abc123'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
let(:call) { subject.call_with_id('abc123') }
|
|
168
|
+
let(:call_id) { call.id }
|
|
169
|
+
|
|
170
|
+
before do
|
|
171
|
+
connection.expects(:handle_event).at_least(1)
|
|
172
|
+
subject.handle_es_event es_event
|
|
173
|
+
call_id
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'sends an error in response to the command' do
|
|
177
|
+
call.wrapped_object.define_singleton_method(:oops) do
|
|
178
|
+
raise 'Woops, I died'
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
connection.expects(:handle_event).once.with end_error_event
|
|
182
|
+
|
|
183
|
+
lambda { call.oops }.should raise_error(/Woops, I died/)
|
|
184
|
+
sleep 0.1
|
|
185
|
+
call.should_not be_alive
|
|
186
|
+
subject.call_with_id(call_id).should be_nil
|
|
187
|
+
|
|
188
|
+
command.request!
|
|
189
|
+
subject.execute_call_command command
|
|
190
|
+
command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "with an unknown call ID" do
|
|
195
|
+
it 'sends an error in response to the command' do
|
|
196
|
+
command.request!
|
|
197
|
+
subject.execute_call_command command
|
|
198
|
+
command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id, nil)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe '#execute_component_command' do
|
|
204
|
+
let(:component_id) { '123abc' }
|
|
205
|
+
let(:component) { mock 'Translator::Freeswitch::Component', :id => component_id }
|
|
206
|
+
|
|
207
|
+
let(:command) { Component::Stop.new.tap { |c| c.component_id = component_id } }
|
|
208
|
+
|
|
209
|
+
before do
|
|
210
|
+
command.request!
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
context 'with a known component ID' do
|
|
214
|
+
before do
|
|
215
|
+
subject.register_component component
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'sends the command to the component for execution' do
|
|
219
|
+
component.expects(:execute_command!).once.with command
|
|
220
|
+
subject.execute_component_command command
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
context "with an unknown component ID" do
|
|
225
|
+
it 'sends an error in response to the command' do
|
|
226
|
+
subject.execute_component_command command
|
|
227
|
+
command.response.should be == ProtocolError.new.setup(:item_not_found, "Could not find a component with ID #{component_id}", nil, component_id)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
describe '#execute_global_command' do
|
|
233
|
+
context 'with a Dial' do
|
|
234
|
+
let :command do
|
|
235
|
+
Command::Dial.new :to => '1234', :from => 'abc123'
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
let(:id) { Punchblock.new_uuid }
|
|
239
|
+
|
|
240
|
+
before do
|
|
241
|
+
id
|
|
242
|
+
Punchblock.expects(:new_uuid).once.returns id
|
|
243
|
+
command.request!
|
|
244
|
+
stream.stub_everything
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it 'should be able to look up the call by ID' do
|
|
248
|
+
subject.execute_global_command command
|
|
249
|
+
call = subject.call_with_id id
|
|
250
|
+
call.should be_a Freeswitch::Call
|
|
251
|
+
call.translator.should be subject
|
|
252
|
+
call.stream.should be stream
|
|
253
|
+
call.media_engine.should be media_engine
|
|
254
|
+
call.default_voice.should be default_voice
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it 'should instruct the call to send a dial' do
|
|
258
|
+
mock_call = stub_everything 'Freeswitch::Call'
|
|
259
|
+
Freeswitch::Call.expects(:new_link).once.returns mock_call
|
|
260
|
+
mock_call.expects(:dial!).once.with command
|
|
261
|
+
subject.execute_global_command command
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
context "with a command we don't understand" do
|
|
266
|
+
let :command do
|
|
267
|
+
Command::Answer.new
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'sends an error in response to the command' do
|
|
271
|
+
subject.execute_command command
|
|
272
|
+
command.response.should be == ProtocolError.new.setup('command-not-acceptable', "Did not understand command")
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
describe '#handle_pb_event' do
|
|
278
|
+
it 'should forward the event to the connection' do
|
|
279
|
+
event = mock 'Punchblock::Event'
|
|
280
|
+
subject.connection.expects(:handle_event).once.with event
|
|
281
|
+
subject.handle_pb_event event
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
describe '#handle_es_event' do
|
|
286
|
+
before { subject.wrapped_object.stubs :handle_pb_event }
|
|
287
|
+
|
|
288
|
+
let(:unique_id) { "3f0e1e18-c056-11e1-b099-fffeda3ce54f" }
|
|
289
|
+
|
|
290
|
+
let :es_env do
|
|
291
|
+
{
|
|
292
|
+
:variable_direction => "inbound",
|
|
293
|
+
:variable_uuid => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
294
|
+
:variable_session_id => "1",
|
|
295
|
+
:variable_sip_local_network_addr => "109.148.160.137",
|
|
296
|
+
:variable_sip_network_ip => "192.168.1.74",
|
|
297
|
+
:variable_sip_network_port => "59253",
|
|
298
|
+
:variable_sip_received_ip => "192.168.1.74",
|
|
299
|
+
:variable_sip_received_port => "59253",
|
|
300
|
+
:variable_sip_via_protocol => "udp",
|
|
301
|
+
:variable_sip_authorized => "true",
|
|
302
|
+
:variable_sip_number_alias => "1000",
|
|
303
|
+
:variable_sip_auth_username => "1000",
|
|
304
|
+
:variable_sip_auth_realm => "127.0.0.1",
|
|
305
|
+
:variable_number_alias => "1000",
|
|
306
|
+
:variable_user_name => "1000",
|
|
307
|
+
:variable_domain_name => "127.0.0.1",
|
|
308
|
+
:variable_record_stereo => "true",
|
|
309
|
+
:variable_default_gateway => "example.com",
|
|
310
|
+
:variable_default_areacode => "918",
|
|
311
|
+
:variable_transfer_fallback_extension => "operator",
|
|
312
|
+
:variable_toll_allow => "domestic,international,local",
|
|
313
|
+
:variable_accountcode => "1000",
|
|
314
|
+
:variable_user_context => "default",
|
|
315
|
+
:variable_effective_caller_id_name => "Extension 1000",
|
|
316
|
+
:variable_effective_caller_id_number => "1000",
|
|
317
|
+
:variable_outbound_caller_id_name => "FreeSWITCH",
|
|
318
|
+
:variable_outbound_caller_id_number => "0000000000",
|
|
319
|
+
:variable_callgroup => "techsupport",
|
|
320
|
+
:variable_sip_from_user => "1000",
|
|
321
|
+
:variable_sip_from_uri => "1000@127.0.0.1",
|
|
322
|
+
:variable_sip_from_host => "127.0.0.1",
|
|
323
|
+
:variable_sip_from_user_stripped => "1000",
|
|
324
|
+
:variable_sip_from_tag => "1248111553",
|
|
325
|
+
:variable_sofia_profile_name => "internal",
|
|
326
|
+
:variable_sip_full_via => "SIP/2.0/UDP 192.168.1.74:59253;rport=59253;branch=z9hG4bK2021947958",
|
|
327
|
+
:variable_sip_full_from => "<sip:1000@127.0.0.1>;tag=1248111553",
|
|
328
|
+
:variable_sip_full_to => "<sip:10@127.0.0.1>",
|
|
329
|
+
:variable_sip_req_user => "10",
|
|
330
|
+
:variable_sip_req_uri => "10@127.0.0.1",
|
|
331
|
+
:variable_sip_req_host => "127.0.0.1",
|
|
332
|
+
:variable_sip_to_user => "10",
|
|
333
|
+
:variable_sip_to_uri => "10@127.0.0.1",
|
|
334
|
+
:variable_sip_to_host => "127.0.0.1",
|
|
335
|
+
:variable_sip_contact_user => "1000",
|
|
336
|
+
:variable_sip_contact_port => "59253",
|
|
337
|
+
:variable_sip_contact_uri => "1000@192.168.1.74:59253",
|
|
338
|
+
:variable_sip_contact_host => "192.168.1.74",
|
|
339
|
+
:variable_channel_name => "sofia/internal/1000@127.0.0.1",
|
|
340
|
+
:variable_sip_call_id => "1251435211@127.0.0.1",
|
|
341
|
+
:variable_sip_user_agent => "YATE/4.1.0",
|
|
342
|
+
:variable_sip_via_host => "192.168.1.74",
|
|
343
|
+
:variable_sip_via_port => "59253",
|
|
344
|
+
:variable_sip_via_rport => "59253",
|
|
345
|
+
:variable_max_forwards => "20",
|
|
346
|
+
:variable_presence_id => "1000@127.0.0.1",
|
|
347
|
+
:variable_switch_r_sdp => "v=0\r\no=yate 1340801245 1340801245 IN IP4 172.20.10.3\r\ns=SIP Call\r\nc=IN IP4 172.20.10.3\r\nt=0 0\r\nm=audio 25048 RTP/AVP 0 8 11 98 97 102 103 104 105 106 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:11 L16/8000\r\na=rtpmap:98 iLBC/8000\r\na=fmtp:98 mode=20\r\na=rtpmap:97 iLBC/8000\r\na=fmtp:97 mode=30\r\na=rtpmap:102 SPEEX/8000\r\na=rtpmap:103 SPEEX/16000\r\na=rtpmap:104 SPEEX/32000\r\na=rtpmap:105 iSAC/16000\r\na=rtpmap:106 iSAC/32000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:30\r\n",
|
|
348
|
+
:variable_remote_media_ip => "172.20.10.3",
|
|
349
|
+
:variable_remote_media_port => "25048",
|
|
350
|
+
:variable_sip_audio_recv_pt => "0",
|
|
351
|
+
:variable_sip_use_codec_name => "PCMU",
|
|
352
|
+
:variable_sip_use_codec_rate => "8000",
|
|
353
|
+
:variable_sip_use_codec_ptime => "30",
|
|
354
|
+
:variable_read_codec => "PCMU",
|
|
355
|
+
:variable_read_rate => "8000",
|
|
356
|
+
:variable_write_codec => "PCMU",
|
|
357
|
+
:variable_write_rate => "8000",
|
|
358
|
+
:variable_endpoint_disposition => "RECEIVED",
|
|
359
|
+
:variable_call_uuid => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
360
|
+
:variable_open => "true",
|
|
361
|
+
:variable_rfc2822_date => "Wed, 27 Jun 2012 13:47:25 +0100",
|
|
362
|
+
:variable_export_vars => "RFC2822_DATE",
|
|
363
|
+
:variable_current_application => "park"
|
|
364
|
+
}
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
let :es_content do
|
|
368
|
+
{
|
|
369
|
+
:event_name => "CHANNEL_PARK",
|
|
370
|
+
:core_uuid => "2ad09a34-c056-11e1-b095-fffeda3ce54f",
|
|
371
|
+
:freeswitch_hostname => "blmbp.home",
|
|
372
|
+
:freeswitch_switchname => "blmbp.home",
|
|
373
|
+
:freeswitch_ipv4 => "192.168.1.74",
|
|
374
|
+
:freeswitch_ipv6 => "%3A%3A1",
|
|
375
|
+
:event_date_local => "2012-06-27%2013%3A47%3A25",
|
|
376
|
+
:event_date_gmt => "Wed,%2027%20Jun%202012%2012%3A47%3A25%20GMT",
|
|
377
|
+
:event_date_timestamp => "1340801245553845",
|
|
378
|
+
:event_calling_file => "switch_ivr.c",
|
|
379
|
+
:event_calling_function => "switch_ivr_park",
|
|
380
|
+
:event_calling_line_number => "879",
|
|
381
|
+
:event_sequence => "485",
|
|
382
|
+
:channel_state => "CS_EXECUTE",
|
|
383
|
+
:channel_call_state => "RINGING",
|
|
384
|
+
:channel_state_number => "4",
|
|
385
|
+
:channel_name => "sofia/internal/1000%40127.0.0.1",
|
|
386
|
+
:unique_id => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
387
|
+
:call_direction => "inbound",
|
|
388
|
+
:presence_call_direction => "inbound",
|
|
389
|
+
:channel_hit_dialplan => "true",
|
|
390
|
+
:channel_presence_id => "1000%40127.0.0.1",
|
|
391
|
+
:channel_call_uuid => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
392
|
+
:answer_state => "ringing",
|
|
393
|
+
:channel_read_codec_name => "PCMU",
|
|
394
|
+
:channel_read_codec_rate => "8000",
|
|
395
|
+
:channel_read_codec_bit_rate => "64000",
|
|
396
|
+
:channel_write_codec_name => "PCMU",
|
|
397
|
+
:channel_write_codec_rate => "8000",
|
|
398
|
+
:channel_write_codec_bit_rate => "64000",
|
|
399
|
+
:caller_direction => "inbound",
|
|
400
|
+
:caller_username => "1000",
|
|
401
|
+
:caller_dialplan => "XML",
|
|
402
|
+
:caller_caller_id_name => "1000",
|
|
403
|
+
:caller_caller_id_number => "1000",
|
|
404
|
+
:caller_network_addr => "192.168.1.74",
|
|
405
|
+
:caller_ani => "1000",
|
|
406
|
+
:caller_destination_number => "10",
|
|
407
|
+
:caller_unique_id => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
408
|
+
:caller_source => "mod_sofia",
|
|
409
|
+
:caller_context => "default",
|
|
410
|
+
:caller_channel_name => "sofia/internal/1000%40127.0.0.1",
|
|
411
|
+
:caller_profile_index => "1",
|
|
412
|
+
:caller_profile_created_time => "1340801245532983",
|
|
413
|
+
:caller_channel_created_time => "1340801245532983",
|
|
414
|
+
:caller_channel_answered_time => "0",
|
|
415
|
+
:caller_channel_progress_time => "0",
|
|
416
|
+
:caller_channel_progress_media_time => "0",
|
|
417
|
+
:caller_channel_hangup_time => "0",
|
|
418
|
+
:caller_channel_transfer_time => "0",
|
|
419
|
+
:caller_screen_bit => "true",
|
|
420
|
+
:caller_privacy_hide_name => "false",
|
|
421
|
+
:caller_privacy_hide_number => "false"
|
|
422
|
+
}.merge es_env
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
let :es_event do
|
|
426
|
+
RubyFS::Event.new nil, es_content
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it 'should be able to look up the call by ID' do
|
|
430
|
+
subject.handle_es_event es_event
|
|
431
|
+
call = subject.call_with_id unique_id
|
|
432
|
+
call.should be_a Freeswitch::Call
|
|
433
|
+
call.translator.should be subject
|
|
434
|
+
call.stream.should be stream
|
|
435
|
+
call.media_engine.should be media_engine
|
|
436
|
+
call.default_voice.should be default_voice
|
|
437
|
+
call.es_env.should be == {
|
|
438
|
+
:variable_direction => "inbound",
|
|
439
|
+
:variable_uuid => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
440
|
+
:variable_session_id => "1",
|
|
441
|
+
:variable_sip_local_network_addr => "109.148.160.137",
|
|
442
|
+
:variable_sip_network_ip => "192.168.1.74",
|
|
443
|
+
:variable_sip_network_port => "59253",
|
|
444
|
+
:variable_sip_received_ip => "192.168.1.74",
|
|
445
|
+
:variable_sip_received_port => "59253",
|
|
446
|
+
:variable_sip_via_protocol => "udp",
|
|
447
|
+
:variable_sip_authorized => "true",
|
|
448
|
+
:variable_sip_number_alias => "1000",
|
|
449
|
+
:variable_sip_auth_username => "1000",
|
|
450
|
+
:variable_sip_auth_realm => "127.0.0.1",
|
|
451
|
+
:variable_number_alias => "1000",
|
|
452
|
+
:variable_user_name => "1000",
|
|
453
|
+
:variable_domain_name => "127.0.0.1",
|
|
454
|
+
:variable_record_stereo => "true",
|
|
455
|
+
:variable_default_gateway => "example.com",
|
|
456
|
+
:variable_default_areacode => "918",
|
|
457
|
+
:variable_transfer_fallback_extension => "operator",
|
|
458
|
+
:variable_toll_allow => "domestic,international,local",
|
|
459
|
+
:variable_accountcode => "1000",
|
|
460
|
+
:variable_user_context => "default",
|
|
461
|
+
:variable_effective_caller_id_name => "Extension 1000",
|
|
462
|
+
:variable_effective_caller_id_number => "1000",
|
|
463
|
+
:variable_outbound_caller_id_name => "FreeSWITCH",
|
|
464
|
+
:variable_outbound_caller_id_number => "0000000000",
|
|
465
|
+
:variable_callgroup => "techsupport",
|
|
466
|
+
:variable_sip_from_user => "1000",
|
|
467
|
+
:variable_sip_from_uri => "1000@127.0.0.1",
|
|
468
|
+
:variable_sip_from_host => "127.0.0.1",
|
|
469
|
+
:variable_sip_from_user_stripped => "1000",
|
|
470
|
+
:variable_sip_from_tag => "1248111553",
|
|
471
|
+
:variable_sofia_profile_name => "internal",
|
|
472
|
+
:variable_sip_full_via => "SIP/2.0/UDP 192.168.1.74:59253;rport=59253;branch=z9hG4bK2021947958",
|
|
473
|
+
:variable_sip_full_from => "<sip:1000@127.0.0.1>;tag=1248111553",
|
|
474
|
+
:variable_sip_full_to => "<sip:10@127.0.0.1>",
|
|
475
|
+
:variable_sip_req_user => "10",
|
|
476
|
+
:variable_sip_req_uri => "10@127.0.0.1",
|
|
477
|
+
:variable_sip_req_host => "127.0.0.1",
|
|
478
|
+
:variable_sip_to_user => "10",
|
|
479
|
+
:variable_sip_to_uri => "10@127.0.0.1",
|
|
480
|
+
:variable_sip_to_host => "127.0.0.1",
|
|
481
|
+
:variable_sip_contact_user => "1000",
|
|
482
|
+
:variable_sip_contact_port => "59253",
|
|
483
|
+
:variable_sip_contact_uri => "1000@192.168.1.74:59253",
|
|
484
|
+
:variable_sip_contact_host => "192.168.1.74",
|
|
485
|
+
:variable_channel_name => "sofia/internal/1000@127.0.0.1",
|
|
486
|
+
:variable_sip_call_id => "1251435211@127.0.0.1",
|
|
487
|
+
:variable_sip_user_agent => "YATE/4.1.0",
|
|
488
|
+
:variable_sip_via_host => "192.168.1.74",
|
|
489
|
+
:variable_sip_via_port => "59253",
|
|
490
|
+
:variable_sip_via_rport => "59253",
|
|
491
|
+
:variable_max_forwards => "20",
|
|
492
|
+
:variable_presence_id => "1000@127.0.0.1",
|
|
493
|
+
:variable_switch_r_sdp => "v=0\r\no=yate 1340801245 1340801245 IN IP4 172.20.10.3\r\ns=SIP Call\r\nc=IN IP4 172.20.10.3\r\nt=0 0\r\nm=audio 25048 RTP/AVP 0 8 11 98 97 102 103 104 105 106 101\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:11 L16/8000\r\na=rtpmap:98 iLBC/8000\r\na=fmtp:98 mode=20\r\na=rtpmap:97 iLBC/8000\r\na=fmtp:97 mode=30\r\na=rtpmap:102 SPEEX/8000\r\na=rtpmap:103 SPEEX/16000\r\na=rtpmap:104 SPEEX/32000\r\na=rtpmap:105 iSAC/16000\r\na=rtpmap:106 iSAC/32000\r\na=rtpmap:101 telephone-event/8000\r\na=ptime:30\r\n",
|
|
494
|
+
:variable_remote_media_ip => "172.20.10.3",
|
|
495
|
+
:variable_remote_media_port => "25048",
|
|
496
|
+
:variable_sip_audio_recv_pt => "0",
|
|
497
|
+
:variable_sip_use_codec_name => "PCMU",
|
|
498
|
+
:variable_sip_use_codec_rate => "8000",
|
|
499
|
+
:variable_sip_use_codec_ptime => "30",
|
|
500
|
+
:variable_read_codec => "PCMU",
|
|
501
|
+
:variable_read_rate => "8000",
|
|
502
|
+
:variable_write_codec => "PCMU",
|
|
503
|
+
:variable_write_rate => "8000",
|
|
504
|
+
:variable_endpoint_disposition => "RECEIVED",
|
|
505
|
+
:variable_call_uuid => "3f0e1e18-c056-11e1-b099-fffeda3ce54f",
|
|
506
|
+
:variable_open => "true",
|
|
507
|
+
:variable_rfc2822_date => "Wed, 27 Jun 2012 13:47:25 +0100",
|
|
508
|
+
:variable_export_vars => "RFC2822_DATE",
|
|
509
|
+
:variable_current_application => "park"
|
|
510
|
+
}
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
describe "with a RubyFS::Stream::Connected" do
|
|
514
|
+
let(:es_event) { RubyFS::Stream::Connected.new }
|
|
515
|
+
|
|
516
|
+
it "should send a Punchblock::Connection::Connected event" do
|
|
517
|
+
subject.wrapped_object.expects(:handle_pb_event).once.with(Punchblock::Connection::Connected.new)
|
|
518
|
+
subject.handle_es_event es_event
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
describe "with a RubyFS::Stream::Disconnected" do
|
|
523
|
+
let(:es_event) { RubyFS::Stream::Disconnected.new }
|
|
524
|
+
|
|
525
|
+
it "should not raise an error" do
|
|
526
|
+
subject.handle_es_event es_event
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
describe 'with a CHANNEL_PARK event' do
|
|
531
|
+
it 'should instruct the call to send an offer' do
|
|
532
|
+
mock_call = stub_everything 'Freeswitch::Call'
|
|
533
|
+
Freeswitch::Call.expects(:new).once.returns mock_call
|
|
534
|
+
subject.wrapped_object.expects(:link)
|
|
535
|
+
mock_call.expects(:send_offer!).once
|
|
536
|
+
subject.handle_es_event es_event
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
context 'if a call already exists for a matching ID' do
|
|
540
|
+
let(:call) { Freeswitch::Call.new unique_id, subject }
|
|
541
|
+
|
|
542
|
+
before do
|
|
543
|
+
subject.register_call call
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
it "should not create a new call" do
|
|
547
|
+
Freeswitch::Call.expects(:new).never
|
|
548
|
+
subject.handle_es_event es_event
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
describe 'with an event with an Other-Leg-Unique-ID value' do
|
|
554
|
+
let(:call_a) { Freeswitch::Call.new Punchblock.new_uuid, subject }
|
|
555
|
+
let(:call_b) { Freeswitch::Call.new Punchblock.new_uuid, subject }
|
|
556
|
+
|
|
557
|
+
before do
|
|
558
|
+
subject.register_call call_a
|
|
559
|
+
subject.register_call call_b
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
let :es_event do
|
|
563
|
+
RubyFS::Event.new nil, {
|
|
564
|
+
:unique_id => call_a.id,
|
|
565
|
+
:other_leg_unique_id => call_b.id
|
|
566
|
+
}
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
it "is delivered to the bridging leg" do
|
|
570
|
+
call_a.expects(:handle_es_event!).once.with es_event
|
|
571
|
+
subject.handle_es_event es_event
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
it "is delivered to the other leg" do
|
|
575
|
+
call_b.expects(:handle_es_event!).once.with es_event
|
|
576
|
+
subject.handle_es_event es_event
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
describe 'with an ES event for a known ID' do
|
|
581
|
+
let :call do
|
|
582
|
+
Freeswitch::Call.new unique_id, subject
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
before do
|
|
586
|
+
subject.register_call call
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
it 'sends the ES event to the call' do
|
|
590
|
+
call.expects(:handle_es_event!).once.with es_event
|
|
591
|
+
subject.handle_es_event es_event
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|