adhearsion 2.0.1 → 2.1.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/.travis.yml +4 -3
- data/CHANGELOG.md +30 -0
- data/README.markdown +1 -0
- data/adhearsion.gemspec +3 -4
- data/bin/ahn +0 -20
- data/features/cli_create.feature +1 -1
- data/features/cli_restart.feature +25 -1
- data/features/cli_start.feature +0 -2
- data/features/plugin_generator.feature +66 -15
- data/features/support/env.rb +0 -13
- data/lib/adhearsion.rb +26 -6
- data/lib/adhearsion/call.rb +42 -7
- data/lib/adhearsion/call_controller.rb +5 -2
- data/lib/adhearsion/call_controller/dial.rb +92 -50
- data/lib/adhearsion/call_controller/input.rb +19 -6
- data/lib/adhearsion/call_controller/menu_dsl/menu.rb +4 -0
- data/lib/adhearsion/call_controller/output.rb +143 -161
- data/lib/adhearsion/call_controller/output/abstract_player.rb +30 -0
- data/lib/adhearsion/call_controller/output/async_player.rb +26 -0
- data/lib/adhearsion/call_controller/output/formatter.rb +81 -0
- data/lib/adhearsion/call_controller/output/player.rb +25 -0
- data/lib/adhearsion/call_controller/record.rb +19 -2
- data/lib/adhearsion/events.rb +3 -0
- data/lib/adhearsion/foundation.rb +12 -6
- data/lib/adhearsion/foundation/exception_handler.rb +8 -6
- data/lib/adhearsion/generators/app/templates/README.md +13 -0
- data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +7 -1
- data/lib/adhearsion/generators/plugin/plugin_generator.rb +1 -0
- data/lib/adhearsion/generators/plugin/templates/plugin-template.gemspec.tt +3 -7
- data/lib/adhearsion/generators/plugin/templates/spec/spec_helper.rb.tt +0 -1
- data/lib/adhearsion/outbound_call.rb +15 -5
- data/lib/adhearsion/punchblock_plugin.rb +13 -2
- data/lib/adhearsion/punchblock_plugin/initializer.rb +13 -12
- data/lib/adhearsion/router.rb +43 -2
- data/lib/adhearsion/router/evented_route.rb +15 -0
- data/lib/adhearsion/router/openended_route.rb +16 -0
- data/lib/adhearsion/router/route.rb +31 -13
- data/lib/adhearsion/router/unaccepting_route.rb +11 -0
- data/lib/adhearsion/version.rb +1 -1
- data/pre-commit +14 -1
- data/spec/adhearsion/call_controller/dial_spec.rb +105 -10
- data/spec/adhearsion/call_controller/input_spec.rb +19 -21
- data/spec/adhearsion/call_controller/output/async_player_spec.rb +67 -0
- data/spec/adhearsion/call_controller/output/formatter_spec.rb +90 -0
- data/spec/adhearsion/call_controller/output/player_spec.rb +65 -0
- data/spec/adhearsion/call_controller/output_spec.rb +436 -190
- data/spec/adhearsion/call_controller/record_spec.rb +49 -6
- data/spec/adhearsion/call_controller_spec.rb +10 -2
- data/spec/adhearsion/call_spec.rb +138 -0
- data/spec/adhearsion/calls_spec.rb +1 -1
- data/spec/adhearsion/outbound_call_spec.rb +48 -8
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +34 -23
- data/spec/adhearsion/router/evented_route_spec.rb +34 -0
- data/spec/adhearsion/router/openended_route_spec.rb +61 -0
- data/spec/adhearsion/router/route_spec.rb +26 -4
- data/spec/adhearsion/router/unaccepting_route_spec.rb +72 -0
- data/spec/adhearsion/router_spec.rb +107 -2
- data/spec/adhearsion_spec.rb +19 -0
- data/spec/capture_warnings.rb +28 -21
- data/spec/spec_helper.rb +2 -3
- data/spec/support/call_controller_test_helpers.rb +31 -30
- metadata +32 -29
data/lib/adhearsion/router.rb
CHANGED
@@ -4,7 +4,12 @@ module Adhearsion
|
|
4
4
|
class Router
|
5
5
|
extend ActiveSupport::Autoload
|
6
6
|
|
7
|
+
autoload :EventedRoute
|
8
|
+
autoload :OpenendedRoute
|
7
9
|
autoload :Route
|
10
|
+
autoload :UnacceptingRoute
|
11
|
+
|
12
|
+
NoMatchError = Class.new Adhearsion::Error
|
8
13
|
|
9
14
|
attr_reader :routes
|
10
15
|
|
@@ -24,9 +29,45 @@ module Adhearsion
|
|
24
29
|
end
|
25
30
|
|
26
31
|
def handle(call)
|
27
|
-
|
32
|
+
raise NoMatchError unless route = match(call)
|
28
33
|
logger.info "Call #{call.id} selected route \"#{route.name}\" (#{route.target})"
|
29
|
-
route.
|
34
|
+
route.dispatch call
|
35
|
+
rescue NoMatchError
|
36
|
+
logger.warn "Call #{call.id} could not find a matching route. Rejecting."
|
37
|
+
call.reject :error
|
38
|
+
end
|
39
|
+
|
40
|
+
module Filters
|
41
|
+
def evented(&block)
|
42
|
+
filtered_routes EventedRoute, &block
|
43
|
+
end
|
44
|
+
|
45
|
+
def unaccepting(&block)
|
46
|
+
filtered_routes UnacceptingRoute, &block
|
47
|
+
end
|
48
|
+
|
49
|
+
def openended(&block)
|
50
|
+
filtered_routes OpenendedRoute, &block
|
51
|
+
end
|
52
|
+
|
53
|
+
def filtered_routes(mixin, &block)
|
54
|
+
FilteredRouter.new(self, mixin).instance_exec(&block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
include Filters
|
59
|
+
|
60
|
+
class FilteredRouter < SimpleDelegator
|
61
|
+
include Filters
|
62
|
+
|
63
|
+
def initialize(delegate, mixin)
|
64
|
+
super delegate
|
65
|
+
@mixin = mixin
|
66
|
+
end
|
67
|
+
|
68
|
+
def route(*args, &block)
|
69
|
+
super.tap { |r| r.extend @mixin }
|
70
|
+
end
|
30
71
|
end
|
31
72
|
end
|
32
73
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'has_guarded_handlers'
|
4
|
+
|
3
5
|
module Adhearsion
|
4
6
|
class Router
|
5
7
|
class Route
|
@@ -21,22 +23,38 @@ module Adhearsion
|
|
21
23
|
!guarded? guards, call
|
22
24
|
end
|
23
25
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
26
|
+
def dispatch(call, callback = nil)
|
27
|
+
controller = if target.respond_to?(:call)
|
28
|
+
CallController.new call, &target
|
29
|
+
else
|
30
|
+
target.new call
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
call.accept if accepting?
|
34
|
+
|
35
|
+
call.execute_controller controller, lambda { |call_actor|
|
36
|
+
begin
|
37
|
+
if call_actor[:ahn_prevent_hangup]
|
38
|
+
logger.info "Call routing completed, keeping the call alive at controller/router request."
|
39
|
+
else
|
34
40
|
call_actor.hangup
|
35
|
-
rescue Call::Hangup
|
36
41
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
rescue Call::Hangup
|
43
|
+
end
|
44
|
+
callback.call if callback
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def evented?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
def accepting?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def openended?
|
57
|
+
false
|
40
58
|
end
|
41
59
|
|
42
60
|
def inspect
|
data/lib/adhearsion/version.rb
CHANGED
data/pre-commit
CHANGED
@@ -1,2 +1,15 @@
|
|
1
|
-
#!/bin/
|
1
|
+
#!/bin/bash
|
2
2
|
rake encodeify
|
3
|
+
|
4
|
+
# avoid committing :focus on rspec examples
|
5
|
+
focus=', :focus'
|
6
|
+
result=$(git grep -n -a "$focus" -- */**/*_spec.rb)
|
7
|
+
|
8
|
+
if [ "$result" != '' ]; then
|
9
|
+
echo ""
|
10
|
+
echo "Refusing to commit a :focus tag in specs: "
|
11
|
+
echo ""
|
12
|
+
echo $result
|
13
|
+
echo ""
|
14
|
+
exit 1
|
15
|
+
fi
|
@@ -51,6 +51,16 @@ module Adhearsion
|
|
51
51
|
dial_thread.join.should be_true
|
52
52
|
end
|
53
53
|
|
54
|
+
let(:options) { { :foo => :bar } }
|
55
|
+
|
56
|
+
def dial_in_thread
|
57
|
+
Thread.new do
|
58
|
+
status = subject.dial to, options
|
59
|
+
latch.countdown!
|
60
|
+
status
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
54
64
|
describe "without a block" do
|
55
65
|
before do
|
56
66
|
flexmock(other_mock_call).should_receive(:dial).once.with(to, options)
|
@@ -58,16 +68,6 @@ module Adhearsion
|
|
58
68
|
flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
|
59
69
|
end
|
60
70
|
|
61
|
-
let(:options) { { :foo => :bar } }
|
62
|
-
|
63
|
-
def dial_in_thread
|
64
|
-
Thread.new do
|
65
|
-
status = subject.dial to, options
|
66
|
-
latch.countdown!
|
67
|
-
status
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
71
|
it "blocks the original controller until the new call ends" do
|
72
72
|
dial_in_thread
|
73
73
|
|
@@ -166,6 +166,21 @@ module Adhearsion
|
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
+
describe "when the caller has already hung up" do
|
170
|
+
before do
|
171
|
+
call << mock_end
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should raise Call::Hangup" do
|
175
|
+
expect { subject.dial to, options }.to raise_error(Call::Hangup)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should not make any outbound calls" do
|
179
|
+
flexmock(OutboundCall).should_receive(:new).never
|
180
|
+
expect { subject.dial to, options }.to raise_error
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
169
184
|
describe "with multiple third parties specified" do
|
170
185
|
let(:options) { {} }
|
171
186
|
let(:other_options) { options }
|
@@ -374,6 +389,86 @@ module Adhearsion
|
|
374
389
|
status.result.should be == :timeout
|
375
390
|
end
|
376
391
|
end
|
392
|
+
|
393
|
+
describe "with a confirmation controller" do
|
394
|
+
let(:confirmation_controller) do
|
395
|
+
latch = confirmation_latch
|
396
|
+
Class.new(Adhearsion::CallController) do
|
397
|
+
@@confirmation_latch = latch
|
398
|
+
|
399
|
+
def run
|
400
|
+
@@confirmation_latch.countdown!
|
401
|
+
call['confirm'] || hangup
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
let(:confirmation_latch) { CountDownLatch.new 1 }
|
407
|
+
|
408
|
+
let(:options) { {:confirm => confirmation_controller} }
|
409
|
+
|
410
|
+
before do
|
411
|
+
flexmock(other_mock_call).should_receive(:dial).once
|
412
|
+
flexmock(OutboundCall).should_receive(:new).and_return other_mock_call
|
413
|
+
end
|
414
|
+
|
415
|
+
context "when an outbound call is answered" do
|
416
|
+
it "should execute the specified confirmation controller" do
|
417
|
+
flexmock(other_mock_call).should_receive(:hangup).twice.and_return do
|
418
|
+
other_mock_call << mock_end
|
419
|
+
end
|
420
|
+
other_mock_call['confirm'] = false
|
421
|
+
|
422
|
+
dial_in_thread
|
423
|
+
|
424
|
+
latch.wait(0.1).should be_false
|
425
|
+
|
426
|
+
other_mock_call << mock_answered
|
427
|
+
|
428
|
+
confirmation_latch.wait(1).should be_true
|
429
|
+
latch.wait(2).should be_true
|
430
|
+
end
|
431
|
+
|
432
|
+
it "should join the calls if the call is still active after execution of the call controller" do
|
433
|
+
flexmock(other_mock_call).should_receive(:hangup).once
|
434
|
+
other_mock_call['confirm'] = true
|
435
|
+
flexmock(other_mock_call).should_receive(:join).once.with(call)
|
436
|
+
|
437
|
+
t = dial_in_thread
|
438
|
+
|
439
|
+
latch.wait(1).should be_false
|
440
|
+
|
441
|
+
other_mock_call << mock_answered
|
442
|
+
other_mock_call << mock_end
|
443
|
+
|
444
|
+
latch.wait(1).should be_true
|
445
|
+
|
446
|
+
t.join
|
447
|
+
status = t.value
|
448
|
+
status.result.should be == :answer
|
449
|
+
end
|
450
|
+
|
451
|
+
it "should not join the calls if the call is not active after execution of the call controller" do
|
452
|
+
flexmock(other_mock_call).should_receive(:hangup).twice.and_return do
|
453
|
+
other_mock_call << mock_end
|
454
|
+
end
|
455
|
+
other_mock_call['confirm'] = false
|
456
|
+
flexmock(other_mock_call).should_receive(:join).never.with(call)
|
457
|
+
|
458
|
+
t = dial_in_thread
|
459
|
+
|
460
|
+
latch.wait(1).should be_false
|
461
|
+
|
462
|
+
other_mock_call << mock_answered
|
463
|
+
|
464
|
+
latch.wait(1).should be_true
|
465
|
+
|
466
|
+
t.join
|
467
|
+
status = t.value
|
468
|
+
status.result.should be == :unconfirmed
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
377
472
|
end#describe #dial
|
378
473
|
end
|
379
474
|
end
|
@@ -164,7 +164,7 @@ module Adhearsion
|
|
164
164
|
|
165
165
|
it "executes failure hook and returns :failure if menu fails" do
|
166
166
|
menu_instance.should_receive(:should_continue?).and_return(false)
|
167
|
-
menu_instance.should_receive(:execute_failure_hook)
|
167
|
+
menu_instance.should_receive(:execute_failure_hook).once
|
168
168
|
result = subject.menu sound_files
|
169
169
|
result.menu.should be menu_instance
|
170
170
|
result.response.should be response
|
@@ -173,8 +173,8 @@ module Adhearsion
|
|
173
173
|
it "executes invalid hook if input is invalid" do
|
174
174
|
menu_instance.should_receive(:should_continue?).twice.and_return(true)
|
175
175
|
menu_instance.should_receive(:continue).and_return(result_invalid, result_done)
|
176
|
-
menu_instance.should_receive(:execute_invalid_hook)
|
177
|
-
menu_instance.should_receive(:restart!)
|
176
|
+
menu_instance.should_receive(:execute_invalid_hook).once
|
177
|
+
menu_instance.should_receive(:restart!).once
|
178
178
|
result = subject.menu sound_files
|
179
179
|
result.menu.should be menu_instance
|
180
180
|
result.response.should be response
|
@@ -184,8 +184,8 @@ module Adhearsion
|
|
184
184
|
menu_instance.should_receive(:should_continue?).twice.and_return(true)
|
185
185
|
menu_instance.should_receive(:continue).and_return(result_get_another_or_timeout, result_done)
|
186
186
|
subject.should_receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
|
187
|
-
menu_instance.should_receive(:execute_timeout_hook)
|
188
|
-
menu_instance.should_receive(:restart!)
|
187
|
+
menu_instance.should_receive(:execute_timeout_hook).once
|
188
|
+
menu_instance.should_receive(:restart!).once
|
189
189
|
subject.menu sound_files
|
190
190
|
end
|
191
191
|
|
@@ -193,7 +193,7 @@ module Adhearsion
|
|
193
193
|
menu_instance.should_receive(:should_continue?).twice.and_return(true)
|
194
194
|
menu_instance.should_receive(:continue).and_return(result_get_another_or_timeout, result_done)
|
195
195
|
subject.should_receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return("1")
|
196
|
-
menu_instance.should_receive(:<<).with("1")
|
196
|
+
menu_instance.should_receive(:<<).with("1").once
|
197
197
|
subject.menu sound_files
|
198
198
|
end
|
199
199
|
|
@@ -201,14 +201,14 @@ module Adhearsion
|
|
201
201
|
menu_instance.should_receive(:should_continue?).and_return(true)
|
202
202
|
menu_instance.should_receive(:continue).and_return(result_get_another_or_finish)
|
203
203
|
subject.should_receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
|
204
|
-
subject.should_receive(:jump_to).with(:match_object, :extension => :new_extension)
|
204
|
+
subject.should_receive(:jump_to).with(:match_object, :extension => :new_extension).once
|
205
205
|
subject.menu sound_files
|
206
206
|
end
|
207
207
|
|
208
208
|
it "jumps to payload when result is found" do
|
209
209
|
menu_instance.should_receive(:should_continue?).and_return(true)
|
210
210
|
menu_instance.should_receive(:continue).and_return(result_found)
|
211
|
-
subject.should_receive(:jump_to).with(:match_object, :extension => :new_extension)
|
211
|
+
subject.should_receive(:jump_to).with(:match_object, :extension => :new_extension).once
|
212
212
|
result = subject.menu sound_files
|
213
213
|
result.menu.should be menu_instance
|
214
214
|
result.response.should be response
|
@@ -238,11 +238,8 @@ module Adhearsion
|
|
238
238
|
let(:result_get_another_or_finish) { MenuDSL::Menu::MenuGetAnotherDigitOrFinish.new(:match_object, :new_extension) }
|
239
239
|
let(:result_found) { MenuDSL::Menu::MenuResultFound.new(:match_object, :new_extension) }
|
240
240
|
|
241
|
-
let(:status) { :foo }
|
242
|
-
let(:response) { '1234' }
|
243
|
-
|
244
241
|
before do
|
245
|
-
flexmock menu_instance
|
242
|
+
flexmock menu_instance
|
246
243
|
flexmock(MenuDSL::Menu).should_receive(:new).and_return(menu_instance)
|
247
244
|
end
|
248
245
|
|
@@ -250,35 +247,36 @@ module Adhearsion
|
|
250
247
|
menu_instance.should_receive(:continue).and_return(result_done)
|
251
248
|
result = subject.ask sound_files
|
252
249
|
result.menu.should be menu_instance
|
253
|
-
result.response.should
|
250
|
+
result.response.should == ''
|
254
251
|
end
|
255
252
|
|
256
253
|
it "exits the function if MenuTerminated" do
|
257
254
|
menu_instance.should_receive(:continue).and_return(result_terminated)
|
258
255
|
result = subject.ask sound_files
|
259
256
|
result.menu.should be menu_instance
|
260
|
-
result.response.should
|
257
|
+
result.response.should == ''
|
261
258
|
end
|
262
259
|
|
263
260
|
it "exits the function if MenuLimitReached" do
|
264
261
|
menu_instance.should_receive(:continue).and_return(result_limit_reached)
|
265
262
|
result = subject.ask sound_files
|
266
263
|
result.menu.should be menu_instance
|
267
|
-
result.response.should
|
264
|
+
result.response.should == ''
|
268
265
|
end
|
269
266
|
|
270
267
|
it "plays audio, then executes timeout hook if input times out" do
|
271
268
|
menu_instance.should_receive(:continue).and_return(result_get_another_or_timeout, result_done)
|
272
|
-
subject.should_receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
|
273
|
-
|
274
|
-
menu_instance
|
275
|
-
|
269
|
+
subject.should_receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return(nil)
|
270
|
+
result = subject.ask sound_files
|
271
|
+
result.menu.should be menu_instance
|
272
|
+
result.response.should be == ''
|
273
|
+
result.status.should be :timeout
|
276
274
|
end
|
277
275
|
|
278
276
|
it "plays audio, then adds digit to digit buffer if input is received" do
|
279
277
|
menu_instance.should_receive(:continue).and_return(result_get_another_or_timeout, result_done)
|
280
|
-
subject.should_receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return("1")
|
281
|
-
menu_instance.should_receive(:<<).with("1")
|
278
|
+
subject.should_receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return("1")
|
279
|
+
menu_instance.should_receive(:<<).with("1").once
|
282
280
|
subject.ask sound_files
|
283
281
|
end
|
284
282
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
module Adhearsion
|
6
|
+
class CallController
|
7
|
+
module Output
|
8
|
+
describe AsyncPlayer do
|
9
|
+
include CallControllerTestHelpers
|
10
|
+
|
11
|
+
let(:controller) { flexmock new_controller }
|
12
|
+
|
13
|
+
subject { AsyncPlayer.new controller }
|
14
|
+
|
15
|
+
describe "#output" do
|
16
|
+
let(:content) { RubySpeech::SSML.draw { string "BOO" } }
|
17
|
+
|
18
|
+
it "should execute an output component with the provided SSML content" do
|
19
|
+
component = Punchblock::Component::Output.new :ssml => content
|
20
|
+
expect_message_waiting_for_response component
|
21
|
+
subject.output content
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should allow extra options to be passed to the output component" do
|
25
|
+
component = Punchblock::Component::Output.new :ssml => content, :start_paused => true
|
26
|
+
expect_message_waiting_for_response component
|
27
|
+
subject.output content, :start_paused => true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the component" do
|
31
|
+
component = Punchblock::Component::Output.new :ssml => content
|
32
|
+
expect_message_waiting_for_response component
|
33
|
+
subject.output(content).should be_a Punchblock::Component::Output
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises a PlaybackError if the component fails to start" do
|
37
|
+
expect_message_waiting_for_response Punchblock::Component::Output.new(:ssml => content), Punchblock::ProtocolError
|
38
|
+
lambda { subject.output content }.should raise_error(PlaybackError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "logs the complete event if it is an error" do
|
42
|
+
response = Punchblock::Event::Complete.new
|
43
|
+
response.reason = Punchblock::Event::Complete::Error.new
|
44
|
+
component = Punchblock::Component::Output.new(:ssml => content)
|
45
|
+
flexmock subject, :new_output => component
|
46
|
+
expect_message_waiting_for_response component
|
47
|
+
flexmock(controller.logger).should_receive(:error).once
|
48
|
+
subject.output content
|
49
|
+
component.request!
|
50
|
+
component.execute!
|
51
|
+
component.trigger_event_handler response
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#play_ssml" do
|
56
|
+
let(:ssml) { RubySpeech::SSML.draw { string "BOO" } }
|
57
|
+
|
58
|
+
it 'executes an Output with the correct ssml' do
|
59
|
+
component = Punchblock::Component::Output.new :ssml => ssml.to_s
|
60
|
+
expect_message_waiting_for_response component
|
61
|
+
subject.play_ssml ssml
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|