punchblock 1.9.4 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|