punchblock 1.9.4 → 2.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.markdown +6 -0
- data/Rakefile +16 -0
- data/benchmarks/ami_event_name_comparison.rb +14 -0
- data/benchmarks/channel.rb +27 -0
- data/lib/punchblock/client.rb +2 -6
- data/lib/punchblock/command/accept.rb +3 -24
- data/lib/punchblock/command/answer.rb +3 -24
- data/lib/punchblock/command/dial.rb +24 -76
- data/lib/punchblock/command/hangup.rb +3 -19
- data/lib/punchblock/command/join.rb +21 -70
- data/lib/punchblock/command/mute.rb +3 -3
- data/lib/punchblock/command/redirect.rb +6 -39
- data/lib/punchblock/command/reject.rb +14 -54
- data/lib/punchblock/command/unjoin.rb +8 -40
- data/lib/punchblock/command/unmute.rb +3 -3
- data/lib/punchblock/command_node.rb +0 -17
- data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
- data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
- data/lib/punchblock/component/component_node.rb +1 -1
- data/lib/punchblock/component/input.rb +89 -268
- data/lib/punchblock/component/output.rb +106 -154
- data/lib/punchblock/component/prompt.rb +51 -0
- data/lib/punchblock/component/record.rb +41 -130
- data/lib/punchblock/component.rb +1 -0
- data/lib/punchblock/connection/asterisk.rb +31 -4
- data/lib/punchblock/connection/xmpp.rb +6 -14
- data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
- data/lib/punchblock/event/active_speaker.rb +2 -10
- data/lib/punchblock/event/answered.rb +3 -3
- data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
- data/lib/punchblock/event/complete.rb +26 -48
- data/lib/punchblock/event/dtmf.rb +3 -13
- data/lib/punchblock/event/end.rb +10 -11
- data/lib/punchblock/event/joined.rb +5 -25
- data/lib/punchblock/event/offer.rb +4 -25
- data/lib/punchblock/event/ringing.rb +3 -3
- data/lib/punchblock/event/unjoined.rb +5 -25
- data/lib/punchblock/event.rb +0 -10
- data/lib/punchblock/has_headers.rb +20 -26
- data/lib/punchblock/rayo_node.rb +46 -23
- data/lib/punchblock/ref.rb +39 -18
- data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
- data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
- data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
- data/lib/punchblock/translator/asterisk/call.rb +60 -39
- data/lib/punchblock/translator/asterisk/channel.rb +41 -0
- data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
- data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
- data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
- data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
- data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
- data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
- data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
- data/lib/punchblock/translator/asterisk/component.rb +6 -5
- data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
- data/lib/punchblock/translator/asterisk.rb +24 -28
- data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
- data/lib/punchblock/translator/freeswitch/call.rb +15 -14
- data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
- data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
- data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
- data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
- data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
- data/lib/punchblock/translator/freeswitch/component.rb +2 -5
- data/lib/punchblock/translator/freeswitch.rb +2 -2
- data/lib/punchblock/translator/input_component.rb +33 -13
- data/lib/punchblock/uri_list.rb +21 -0
- data/lib/punchblock/version.rb +1 -1
- data/lib/punchblock.rb +4 -3
- data/punchblock.gemspec +7 -3
- data/spec/punchblock/client/component_registry_spec.rb +1 -1
- data/spec/punchblock/client_spec.rb +10 -26
- data/spec/punchblock/command/accept_spec.rb +41 -7
- data/spec/punchblock/command/answer_spec.rb +51 -7
- data/spec/punchblock/command/dial_spec.rb +56 -14
- data/spec/punchblock/command/hangup_spec.rb +41 -7
- data/spec/punchblock/command/join_spec.rb +53 -11
- data/spec/punchblock/command/mute_spec.rb +19 -4
- data/spec/punchblock/command/redirect_spec.rb +40 -10
- data/spec/punchblock/command/reject_spec.rb +43 -11
- data/spec/punchblock/command/unjoin_spec.rb +40 -9
- data/spec/punchblock/command/unmute_spec.rb +19 -4
- data/spec/punchblock/command_node_spec.rb +0 -4
- data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
- data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
- data/spec/punchblock/component/component_node_spec.rb +3 -5
- data/spec/punchblock/component/input_spec.rb +194 -61
- data/spec/punchblock/component/output_spec.rb +194 -62
- data/spec/punchblock/component/prompt_spec.rb +132 -0
- data/spec/punchblock/component/record_spec.rb +70 -32
- data/spec/punchblock/connection/asterisk_spec.rb +17 -3
- data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
- data/spec/punchblock/connection/xmpp_spec.rb +20 -38
- data/spec/punchblock/event/answered_spec.rb +12 -10
- data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
- data/spec/punchblock/event/complete_spec.rb +15 -19
- data/spec/punchblock/event/dtmf_spec.rb +5 -6
- data/spec/punchblock/event/end_spec.rb +20 -10
- data/spec/punchblock/event/joined_spec.rb +8 -7
- data/spec/punchblock/event/offer_spec.rb +41 -12
- data/spec/punchblock/event/ringing_spec.rb +12 -10
- data/spec/punchblock/event/started_speaking_spec.rb +5 -6
- data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
- data/spec/punchblock/event/unjoined_spec.rb +7 -7
- data/spec/punchblock/ref_spec.rb +86 -9
- data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
- data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
- data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
- data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
- data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
- data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
- data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
- data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
- data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
- data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
- data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
- data/spec/punchblock/translator/asterisk_spec.rb +20 -24
- data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
- data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
- data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
- data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
- data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
- data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
- data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
- data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
- data/spec/punchblock/uri_list_spec.rb +49 -0
- data/spec/punchblock_spec.rb +11 -1
- data/spec/spec_helper.rb +7 -11
- data/spec/support/mock_connection_with_event_handler.rb +1 -1
- metadata +104 -24
- data/lib/punchblock/header.rb +0 -9
- data/lib/punchblock/key_value_pair_node.rb +0 -51
- data/spec/punchblock/header_spec.rb +0 -11
|
@@ -11,7 +11,7 @@ module Punchblock
|
|
|
11
11
|
include HasMockCallbackConnection
|
|
12
12
|
|
|
13
13
|
let(:channel) { 'SIP/foo' }
|
|
14
|
-
let(:ami_client) {
|
|
14
|
+
let(:ami_client) { double('AMI Client').as_null_object }
|
|
15
15
|
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
|
|
16
16
|
let(:mock_call) { Punchblock::Translator::Asterisk::Call.new channel, translator, ami_client, connection }
|
|
17
17
|
let(:component_id) { Punchblock.new_uuid }
|
|
@@ -56,7 +56,7 @@ module Punchblock
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
let :expected_response do
|
|
59
|
-
Ref.new :
|
|
59
|
+
Ref.new uri: component_id
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
let :response do
|
|
@@ -67,20 +67,42 @@ module Punchblock
|
|
|
67
67
|
it 'should send the component node a ref with the action ID' do
|
|
68
68
|
ami_client.should_receive(:send_action).once.and_return response
|
|
69
69
|
subject.execute
|
|
70
|
-
original_command.response(1).should
|
|
70
|
+
original_command.response(1).should == expected_response
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
context 'with an error' do
|
|
74
|
+
let(:message) { 'Action failed' }
|
|
74
75
|
let :response do
|
|
75
|
-
RubyAMI::Error.new.tap { |e| e.message =
|
|
76
|
+
RubyAMI::Error.new.tap { |e| e.message = message }
|
|
76
77
|
end
|
|
77
78
|
|
|
79
|
+
before { ami_client.should_receive(:send_action).once.and_raise response }
|
|
80
|
+
|
|
78
81
|
it 'should send the component node false' do
|
|
79
|
-
ami_client.should_receive(:send_action).once.and_raise response
|
|
80
82
|
subject.execute
|
|
81
83
|
original_command.response(1).should be_false
|
|
82
84
|
subject.should_not be_alive
|
|
83
85
|
end
|
|
86
|
+
|
|
87
|
+
context "which is 'No such channel'" do
|
|
88
|
+
let(:message) { 'No such channel' }
|
|
89
|
+
|
|
90
|
+
it "should return an :item_not_found error for the command" do
|
|
91
|
+
subject.execute
|
|
92
|
+
original_command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id)
|
|
93
|
+
subject.should_not be_alive
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context "which is 'Channel SIP/nosuchchannel does not exist.'" do
|
|
98
|
+
let(:message) { 'Channel SIP/nosuchchannel does not exist.' }
|
|
99
|
+
|
|
100
|
+
it "should return an :item_not_found error for the command" do
|
|
101
|
+
subject.execute
|
|
102
|
+
original_command.response(0.5).should be == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id)
|
|
103
|
+
subject.should_not be_alive
|
|
104
|
+
end
|
|
105
|
+
end
|
|
84
106
|
end
|
|
85
107
|
end
|
|
86
108
|
|
|
@@ -142,6 +164,7 @@ module Punchblock
|
|
|
142
164
|
|
|
143
165
|
it 'should send an end (hangup) event to the translator' do
|
|
144
166
|
expected_end_event = Punchblock::Event::End.new reason: :hangup,
|
|
167
|
+
platform_code: 16,
|
|
145
168
|
target_call_id: mock_call.id
|
|
146
169
|
|
|
147
170
|
translator.should_receive(:handle_pb_event).once.with kind_of(Punchblock::Event::Complete)
|
|
@@ -10,7 +10,7 @@ module Punchblock
|
|
|
10
10
|
describe AMIAction do
|
|
11
11
|
include HasMockCallbackConnection
|
|
12
12
|
|
|
13
|
-
let(:ami_client) {
|
|
13
|
+
let(:ami_client) { double('AMI Client').as_null_object }
|
|
14
14
|
let(:mock_translator) { Punchblock::Translator::Asterisk.new ami_client, connection }
|
|
15
15
|
|
|
16
16
|
let :original_command do
|
|
@@ -27,7 +27,7 @@ module Punchblock
|
|
|
27
27
|
let(:component_id) { Punchblock.new_uuid }
|
|
28
28
|
|
|
29
29
|
let :expected_response do
|
|
30
|
-
Ref.new :
|
|
30
|
+
Ref.new uri: component_id
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
before { stub_uuids component_id }
|
|
@@ -49,8 +49,10 @@ module Punchblock
|
|
|
49
49
|
'Status' => '-1'
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
before { response.text_body = 'Some text body' }
|
|
53
|
+
|
|
52
54
|
let :expected_complete_reason do
|
|
53
|
-
Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :
|
|
55
|
+
Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new message: 'Channel status will follow', text_body: 'Some text body', headers: {'Exten' => "idonno", 'Context' => "default", 'Hint' => "", 'Status' => "-1"}
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
context 'for a non-causal action' do
|
|
@@ -88,19 +90,19 @@ module Punchblock
|
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
let :event_node do
|
|
91
|
-
Punchblock::Event::Asterisk::AMI::Event.new :
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
Punchblock::Event::Asterisk::AMI::Event.new name: 'CoreShowChannel', component_id: subject.id, headers: {
|
|
94
|
+
'Channel' => 'SIP/127.0.0.1-00000013',
|
|
95
|
+
'UniqueID' => '1287686437.19',
|
|
96
|
+
'Context' => 'adhearsion',
|
|
97
|
+
'Extension' => '23432',
|
|
98
|
+
'Priority' => '2',
|
|
99
|
+
'ChannelState' => '6',
|
|
100
|
+
'ChannelStateDesc' => 'Up'
|
|
99
101
|
}
|
|
100
102
|
end
|
|
101
103
|
|
|
102
104
|
let :expected_complete_reason do
|
|
103
|
-
Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new :
|
|
105
|
+
Punchblock::Component::Asterisk::AMI::Action::Complete::Success.new message: 'Channel status will follow', text_body: 'Some text body', headers: {'Exten' => "idonno", 'Context' => "default", 'Hint' => "", 'Status' => "-1", 'EventList' => 'Complete', 'ListItems' => '3'}
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
before { ami_client.should_receive(:send_action).once.and_return response }
|
|
@@ -130,7 +132,7 @@ module Punchblock
|
|
|
130
132
|
end
|
|
131
133
|
|
|
132
134
|
let :expected_complete_reason do
|
|
133
|
-
Punchblock::Event::Complete::Error.new :
|
|
135
|
+
Punchblock::Event::Complete::Error.new details: 'Action failed'
|
|
134
136
|
end
|
|
135
137
|
|
|
136
138
|
it 'should send a complete event to the component node' do
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Punchblock
|
|
6
|
+
module Translator
|
|
7
|
+
class Asterisk
|
|
8
|
+
module Component
|
|
9
|
+
describe ComposedPrompt do
|
|
10
|
+
class MockConnection
|
|
11
|
+
attr_reader :events
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@events = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def handle_event(event)
|
|
18
|
+
@events << event
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
let(:connection) { MockConnection.new }
|
|
23
|
+
let(:media_engine) { nil }
|
|
24
|
+
let(:ami_client) { double('AMI') }
|
|
25
|
+
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
|
|
26
|
+
let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
|
|
27
|
+
|
|
28
|
+
let :ssml_doc do
|
|
29
|
+
RubySpeech::SSML.draw do
|
|
30
|
+
audio src: 'http://foo.com/bar.mp3'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
let :dtmf_grammar do
|
|
35
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digit' do
|
|
36
|
+
rule id: 'digit' do
|
|
37
|
+
one_of do
|
|
38
|
+
item { '1' }
|
|
39
|
+
item { '2' }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
let :output_command_options do
|
|
46
|
+
{ render_document: {value: ssml_doc} }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
let :input_command_options do
|
|
50
|
+
{
|
|
51
|
+
mode: :dtmf,
|
|
52
|
+
grammar: {value: dtmf_grammar}
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
let(:command_options) { {} }
|
|
57
|
+
|
|
58
|
+
let :output_command do
|
|
59
|
+
Punchblock::Component::Output.new output_command_options
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
let :input_command do
|
|
63
|
+
Punchblock::Component::Input.new input_command_options
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
let :original_command do
|
|
67
|
+
Punchblock::Component::Prompt.new output_command, input_command, command_options
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subject { described_class.new original_command, call }
|
|
71
|
+
|
|
72
|
+
let(:playbackstatus) { 'SUCCESS' }
|
|
73
|
+
|
|
74
|
+
before do
|
|
75
|
+
call.stub answered?: true, execute_agi_command: true
|
|
76
|
+
call.stub(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus
|
|
77
|
+
original_command.request!
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def ami_event_for_dtmf(digit, position)
|
|
81
|
+
RubyAMI::Event.new 'DTMF',
|
|
82
|
+
'Digit' => digit.to_s,
|
|
83
|
+
'Start' => position == :start ? 'Yes' : 'No',
|
|
84
|
+
'End' => position == :end ? 'Yes' : 'No'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def send_ami_events_for_dtmf(digit)
|
|
88
|
+
call.process_ami_event ami_event_for_dtmf(digit, :start)
|
|
89
|
+
call.process_ami_event ami_event_for_dtmf(digit, :end)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#execute' do
|
|
93
|
+
context '#barge_in' do
|
|
94
|
+
context 'true' do
|
|
95
|
+
it "should execute an output component on the call and return a ref" do
|
|
96
|
+
call.should_receive(:execute_agi_command).once.with('EXEC Playback', 'http://foo.com/bar.mp3')
|
|
97
|
+
subject.execute
|
|
98
|
+
original_command.response(0.1).should be_a Ref
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context "if output fails to start" do
|
|
102
|
+
it "should return the error returned by output"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
context "receiving dtmf during output" do
|
|
106
|
+
it "should stop the output"
|
|
107
|
+
|
|
108
|
+
it "should contribute to the input result"
|
|
109
|
+
|
|
110
|
+
it "should return a match complete event"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "when not receiving any DTMF input at all" do
|
|
114
|
+
it "should not start the initial timer until output completes"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context 'false' do
|
|
119
|
+
it "should execute an output component on the call" do
|
|
120
|
+
call.should_receive(:execute_agi_command).once.with('EXEC Playback', 'http://foo.com/bar.mp3')
|
|
121
|
+
subject.execute
|
|
122
|
+
original_command.response(0.1).should be_a Ref
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context "if output fails to start" do
|
|
126
|
+
let(:output_response) { ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.' }
|
|
127
|
+
|
|
128
|
+
let :ssml_doc do
|
|
129
|
+
RubySpeech::SSML.draw do
|
|
130
|
+
string "Foo Bar"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "should return the error returned by output" do
|
|
135
|
+
subject.execute
|
|
136
|
+
original_command.response(0.1).should == output_response
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context "receiving dtmf during output" do
|
|
141
|
+
it "should not stop the output"
|
|
142
|
+
|
|
143
|
+
it "should not contribute to the input result"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context "receiving matching dtmf after output completes" do
|
|
147
|
+
let :expected_nlsml do
|
|
148
|
+
RubySpeech::NLSML.draw do
|
|
149
|
+
interpretation confidence: 1 do
|
|
150
|
+
instance "dtmf-1"
|
|
151
|
+
input "1", mode: :dtmf
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
let :expected_event do
|
|
157
|
+
Punchblock::Event::Complete.new reason: expected_reason,
|
|
158
|
+
component_id: subject.id,
|
|
159
|
+
target_call_id: call.id
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
let :expected_reason do
|
|
163
|
+
Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "should return a match complete event" do
|
|
167
|
+
expected_event
|
|
168
|
+
subject.execute
|
|
169
|
+
original_command.response(0.1).should be_a Ref
|
|
170
|
+
send_ami_events_for_dtmf 1
|
|
171
|
+
|
|
172
|
+
connection.events.should include(expected_event)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "when not receiving any DTMF input at all" do
|
|
177
|
+
it "should not start the initial timer until output completes"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
describe "#execute_command" do
|
|
184
|
+
context "with a command it does not understand" do
|
|
185
|
+
let(:command) { Punchblock::Component::Output::Pause.new }
|
|
186
|
+
|
|
187
|
+
before { command.request! }
|
|
188
|
+
it "returns a ProtocolError response" do
|
|
189
|
+
subject.execute_command command
|
|
190
|
+
command.response(0.1).should be_a ProtocolError
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
context "with a Stop command" do
|
|
195
|
+
let(:command) { Punchblock::Component::Stop.new }
|
|
196
|
+
let(:channel) { "SIP/1234-00000000" }
|
|
197
|
+
let :ami_event do
|
|
198
|
+
RubyAMI::Event.new 'AsyncAGI',
|
|
199
|
+
'SubEvent' => "Start",
|
|
200
|
+
'Channel' => channel,
|
|
201
|
+
'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"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
before do
|
|
205
|
+
command.request!
|
|
206
|
+
original_command.execute!
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "sets the command response to true" do
|
|
210
|
+
call.async.should_receive(:redirect_back).once
|
|
211
|
+
subject.execute_command command
|
|
212
|
+
command.response(0.1).should be == true
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "sends the correct complete event" do
|
|
216
|
+
expected_reason = Punchblock::Event::Complete::Stop.new
|
|
217
|
+
expected_event = Punchblock::Event::Complete.new reason: expected_reason,
|
|
218
|
+
component_id: subject.id,
|
|
219
|
+
target_call_id: call.id
|
|
220
|
+
|
|
221
|
+
call.async.should_receive(:redirect_back)
|
|
222
|
+
subject.execute_command command
|
|
223
|
+
original_command.should_not be_complete
|
|
224
|
+
call.process_ami_event ami_event
|
|
225
|
+
|
|
226
|
+
sleep 0.2
|
|
227
|
+
|
|
228
|
+
connection.events.should include(expected_event)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -10,7 +10,7 @@ module Punchblock
|
|
|
10
10
|
include HasMockCallbackConnection
|
|
11
11
|
|
|
12
12
|
let(:media_engine) { nil }
|
|
13
|
-
let(:ami_client) {
|
|
13
|
+
let(:ami_client) { double('AMI') }
|
|
14
14
|
let(:translator) { Punchblock::Translator::Asterisk.new ami_client, connection, media_engine }
|
|
15
15
|
let(:call) { Punchblock::Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
|
|
16
16
|
let(:original_command_options) { {} }
|
|
@@ -79,13 +79,17 @@ module Punchblock
|
|
|
79
79
|
send_ami_events_for_dtmf 2
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
let :expected_nlsml do
|
|
83
|
+
RubySpeech::NLSML.draw do
|
|
84
|
+
interpretation confidence: 1 do
|
|
85
|
+
instance "dtmf-1 dtmf-2"
|
|
86
|
+
input "12", mode: :dtmf
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
82
91
|
let :expected_event do
|
|
83
|
-
Punchblock::Component::Input::Complete::
|
|
84
|
-
:confidence => 1,
|
|
85
|
-
:utterance => '12',
|
|
86
|
-
:interpretation => 'dtmf-1 dtmf-2',
|
|
87
|
-
:component_id => subject.id,
|
|
88
|
-
:target_call_id => call.id
|
|
92
|
+
Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
it "should send a success complete event with the relevant data" do
|
|
@@ -105,8 +109,7 @@ module Punchblock
|
|
|
105
109
|
end
|
|
106
110
|
|
|
107
111
|
let :expected_event do
|
|
108
|
-
Punchblock::Component::Input::Complete::NoMatch.new
|
|
109
|
-
:target_call_id => call.id
|
|
112
|
+
Punchblock::Component::Input::Complete::NoMatch.new
|
|
110
113
|
end
|
|
111
114
|
|
|
112
115
|
it "should send a nomatch complete event" do
|
|
@@ -124,6 +127,15 @@ module Punchblock
|
|
|
124
127
|
original_command.response(0.1).should be == error
|
|
125
128
|
end
|
|
126
129
|
end
|
|
130
|
+
|
|
131
|
+
context 'with multiple grammars' do
|
|
132
|
+
let(:original_command_opts) { { :grammars => [{:value => grammar}, {:value => grammar}] } }
|
|
133
|
+
it "should return an error and not execute any actions" do
|
|
134
|
+
subject.execute
|
|
135
|
+
error = ProtocolError.new.setup 'option error', 'Only a single grammar is supported.'
|
|
136
|
+
original_command.response(0.1).should be == error
|
|
137
|
+
end
|
|
138
|
+
end
|
|
127
139
|
end
|
|
128
140
|
|
|
129
141
|
describe 'mode' do
|
|
@@ -145,8 +157,8 @@ module Punchblock
|
|
|
145
157
|
end
|
|
146
158
|
end
|
|
147
159
|
|
|
148
|
-
context '
|
|
149
|
-
let(:original_command_opts) { { :mode => :
|
|
160
|
+
context 'voice' do
|
|
161
|
+
let(:original_command_opts) { { :mode => :voice } }
|
|
150
162
|
it "should return an error and not execute any actions" do
|
|
151
163
|
subject.execute
|
|
152
164
|
error = ProtocolError.new.setup 'option error', 'A mode value other than DTMF is unsupported.'
|
|
@@ -156,7 +168,85 @@ module Punchblock
|
|
|
156
168
|
end
|
|
157
169
|
|
|
158
170
|
describe 'terminator' do
|
|
159
|
-
|
|
171
|
+
context 'set' do
|
|
172
|
+
let(:original_command_opts) { { terminator: '#' } }
|
|
173
|
+
|
|
174
|
+
before do
|
|
175
|
+
subject.execute
|
|
176
|
+
expected_event
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
let :grammar do
|
|
180
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
|
181
|
+
rule id: 'digits' do
|
|
182
|
+
item repeat: '2-5' do
|
|
183
|
+
one_of do
|
|
184
|
+
0.upto(9) { |d| item { d.to_s } }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
let :expected_nlsml do
|
|
192
|
+
RubySpeech::NLSML.draw do
|
|
193
|
+
interpretation confidence: 1 do
|
|
194
|
+
instance "dtmf-1 dtmf-2"
|
|
195
|
+
input "12", mode: :dtmf
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
let :expected_event do
|
|
201
|
+
Punchblock::Component::Input::Complete::Match.new nlsml: expected_nlsml
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
context "when encountered with a match" do
|
|
205
|
+
before do
|
|
206
|
+
send_ami_events_for_dtmf 1
|
|
207
|
+
send_ami_events_for_dtmf 2
|
|
208
|
+
send_ami_events_for_dtmf '#'
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "should send a match complete event with the relevant data" do
|
|
212
|
+
reason.should be == expected_event
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it "should not process further dtmf events" do
|
|
216
|
+
subject.async.should_receive(:process_dtmf).never
|
|
217
|
+
send_ami_events_for_dtmf 3
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context "when encountered with a NoMatch" do
|
|
222
|
+
before do
|
|
223
|
+
send_ami_events_for_dtmf '#'
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
let :expected_event do
|
|
227
|
+
Punchblock::Component::Input::Complete::NoMatch.new
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it "should send a nomatch complete event with the relevant data" do
|
|
231
|
+
reason.should be == expected_event
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
context "when encountered with a PotentialMatch" do
|
|
236
|
+
before do
|
|
237
|
+
send_ami_events_for_dtmf 1
|
|
238
|
+
send_ami_events_for_dtmf '#'
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
let :expected_event do
|
|
242
|
+
Punchblock::Component::Input::Complete::NoMatch.new
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it "should send a nomatch complete event with the relevant data" do
|
|
246
|
+
reason.should be == expected_event
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
160
250
|
end
|
|
161
251
|
|
|
162
252
|
describe 'recognizer' do
|
|
@@ -172,7 +262,7 @@ module Punchblock
|
|
|
172
262
|
send_ami_events_for_dtmf 1
|
|
173
263
|
sleep 1.5
|
|
174
264
|
send_ami_events_for_dtmf 2
|
|
175
|
-
reason.should be_a Punchblock::Component::Input::Complete::
|
|
265
|
+
reason.should be_a Punchblock::Component::Input::Complete::Match
|
|
176
266
|
end
|
|
177
267
|
|
|
178
268
|
it "should cause a NoInput complete event to be sent after the timeout" do
|
|
@@ -223,7 +313,7 @@ module Punchblock
|
|
|
223
313
|
send_ami_events_for_dtmf 1
|
|
224
314
|
sleep 0.5
|
|
225
315
|
send_ami_events_for_dtmf 2
|
|
226
|
-
reason.should be_a Punchblock::Component::Input::Complete::
|
|
316
|
+
reason.should be_a Punchblock::Component::Input::Complete::Match
|
|
227
317
|
end
|
|
228
318
|
|
|
229
319
|
it "should cause a NoMatch complete event to be sent after the timeout" do
|
|
@@ -234,6 +324,73 @@ module Punchblock
|
|
|
234
324
|
send_ami_events_for_dtmf 2
|
|
235
325
|
reason.should be_a Punchblock::Component::Input::Complete::NoMatch
|
|
236
326
|
end
|
|
327
|
+
|
|
328
|
+
context "with a trailing range repeat" do
|
|
329
|
+
let :grammar do
|
|
330
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
|
331
|
+
rule id: 'digits', scope: 'public' do
|
|
332
|
+
item repeat: '2-5' do
|
|
333
|
+
'1'
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context "when the buffer potentially matches the grammar" do
|
|
340
|
+
it "should cause a NoMatch complete event to be sent after the timeout" do
|
|
341
|
+
subject.execute
|
|
342
|
+
sleep 1.5
|
|
343
|
+
send_ami_events_for_dtmf 1
|
|
344
|
+
sleep 1.5
|
|
345
|
+
reason.should be_a Punchblock::Component::Input::Complete::NoMatch
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
context "when the buffer matches the grammar" do
|
|
350
|
+
let :expected_nlsml do
|
|
351
|
+
RubySpeech::NLSML.draw do
|
|
352
|
+
interpretation confidence: 1 do
|
|
353
|
+
instance "dtmf-1 dtmf-1"
|
|
354
|
+
input '11', mode: :dtmf
|
|
355
|
+
end
|
|
356
|
+
end.root
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
it "should fire a match on timeout" do
|
|
360
|
+
subject.execute
|
|
361
|
+
sleep 1.5
|
|
362
|
+
send_ami_events_for_dtmf 1
|
|
363
|
+
sleep 0.5
|
|
364
|
+
send_ami_events_for_dtmf 1
|
|
365
|
+
sleep 1.5
|
|
366
|
+
reason.should be_a Punchblock::Component::Input::Complete::Match
|
|
367
|
+
reason.nlsml.should == expected_nlsml
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
context "on the first keypress" do
|
|
371
|
+
let :grammar do
|
|
372
|
+
RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits' do
|
|
373
|
+
rule id: 'digits', scope: 'public' do
|
|
374
|
+
item repeat: '1-5' do
|
|
375
|
+
'1'
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it "should fire a match on timeout" do
|
|
382
|
+
subject.execute
|
|
383
|
+
sleep 1.5
|
|
384
|
+
send_ami_events_for_dtmf 1
|
|
385
|
+
sleep 0.5
|
|
386
|
+
send_ami_events_for_dtmf 1
|
|
387
|
+
sleep 1.5
|
|
388
|
+
reason.should be_a Punchblock::Component::Input::Complete::Match
|
|
389
|
+
reason.nlsml.should == expected_nlsml
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
237
394
|
end
|
|
238
395
|
|
|
239
396
|
context '-1' do
|