adhearsion 1.0.1 → 1.0.2
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/.gitignore +10 -0
- data/CHANGELOG +8 -0
- data/README.markdown +33 -0
- data/Rakefile +28 -68
- data/adhearsion.gemspec +19 -133
- data/app_generators/ahn/templates/Gemfile +0 -4
- data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +4 -16
- data/lib/adhearsion/cli.rb +17 -0
- data/lib/adhearsion/component_manager/component_tester.rb +1 -3
- data/lib/adhearsion/component_manager/spec_framework.rb +4 -10
- data/lib/adhearsion/foundation/object.rb +10 -0
- data/lib/adhearsion/version.rb +1 -1
- data/spec/ahn_command_spec.rb +284 -0
- data/spec/component_manager_spec.rb +292 -0
- data/spec/constants_spec.rb +8 -0
- data/spec/drb_spec.rb +65 -0
- data/spec/fixtures/dialplan.rb +3 -0
- data/spec/foundation/event_socket_spec.rb +168 -0
- data/spec/host_definitions_spec.rb +79 -0
- data/spec/initialization_spec.rb +163 -0
- data/spec/initializer/configuration_spec.rb +270 -0
- data/spec/initializer/loading_spec.rb +149 -0
- data/spec/initializer/paths_spec.rb +74 -0
- data/spec/logging_spec.rb +86 -0
- data/spec/relationship_properties_spec.rb +54 -0
- data/spec/silence.rb +10 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/voip/asterisk/agi_server_spec.rb +473 -0
- data/spec/voip/asterisk/ami/ami_spec.rb +549 -0
- data/spec/voip/asterisk/ami/lexer/ami_fixtures.yml +30 -0
- data/spec/voip/asterisk/ami/lexer/lexer_story +291 -0
- data/spec/voip/asterisk/ami/lexer/lexer_story.rb +241 -0
- data/spec/voip/asterisk/ami/lexer/story_helper.rb +124 -0
- data/spec/voip/asterisk/ami/old_tests.rb +204 -0
- data/spec/voip/asterisk/ami/super_manager/super_manager_story +25 -0
- data/spec/voip/asterisk/ami/super_manager/super_manager_story.rb +15 -0
- data/spec/voip/asterisk/ami/super_manager/super_manager_story_helper.rb +5 -0
- data/spec/voip/asterisk/commands_spec.rb +2179 -0
- data/spec/voip/asterisk/config_file_generators/agents_spec.rb +251 -0
- data/spec/voip/asterisk/config_file_generators/queues_spec.rb +323 -0
- data/spec/voip/asterisk/config_file_generators/voicemail_spec.rb +306 -0
- data/spec/voip/asterisk/config_manager_spec.rb +127 -0
- data/spec/voip/asterisk/menu_command/calculated_match_spec.rb +109 -0
- data/spec/voip/asterisk/menu_command/matchers_spec.rb +97 -0
- data/spec/voip/call_routing_spec.rb +125 -0
- data/spec/voip/dialplan_manager_spec.rb +468 -0
- data/spec/voip/dsl/dialing_dsl_spec.rb +270 -0
- data/spec/voip/dsl/dispatcher_spec.rb +82 -0
- data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
- data/spec/voip/dsl/parser_spec.rb +69 -0
- data/spec/voip/freeswitch/basic_connection_manager_spec.rb +39 -0
- data/spec/voip/freeswitch/inbound_connection_manager_spec.rb +39 -0
- data/spec/voip/freeswitch/oes_server_spec.rb +9 -0
- data/spec/voip/numerical_string_spec.rb +61 -0
- data/spec/voip/phone_number_spec.rb +45 -0
- data/theatre-spec/dsl_examples/dynamic_stomp.rb +7 -0
- data/theatre-spec/dsl_examples/simple_before_call.rb +7 -0
- data/theatre-spec/dsl_spec.rb +43 -0
- data/theatre-spec/invocation_spec.rb +167 -0
- data/theatre-spec/namespace_spec.rb +125 -0
- data/theatre-spec/spec_helper.rb +37 -0
- data/theatre-spec/spec_helper_spec.rb +28 -0
- data/theatre-spec/theatre_class_spec.rb +150 -0
- metadata +171 -34
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec', "= 1.1.4"
|
3
|
+
require 'spec/story'
|
4
|
+
require File.dirname(__FILE__) + "/../../../../../lib/adhearsion"
|
5
|
+
|
6
|
+
RAGEL_FILES = %w[lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb]
|
7
|
+
|
8
|
+
def regenerate_ragel
|
9
|
+
# Check Ragel version:
|
10
|
+
ragel_version_match = `ragel --version`.match(/(\d)\.(\d)+/)
|
11
|
+
abort "Could not get Ragel version! Is it installed? You must have at least version 6.3" unless ragel_version_match
|
12
|
+
big, small = ragel_version_match.captures.map { |n| n.to_i }
|
13
|
+
if big < 6 || (big == 6 && small < 3)
|
14
|
+
abort "Please upgrade Ragel! You're on version #{ragel_version_match[0]} and must be on 6.3 or later"
|
15
|
+
end
|
16
|
+
|
17
|
+
RAGEL_FILES.each do |ragel_file|
|
18
|
+
ruby_file = ragel_file.sub(".rl.rb", ".rb")
|
19
|
+
puts `ragel -n -R #{ragel_file} -o #{ruby_file} 2>&1`
|
20
|
+
raise "Failed generating code from Ragel file #{ragel_file}" if $?.to_i.nonzero?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir.chdir(File.dirname(__FILE__) + "/../../../../..") { regenerate_ragel }
|
25
|
+
|
26
|
+
require 'adhearsion/voip/asterisk/manager_interface/ami_lexer'
|
27
|
+
|
28
|
+
FIXTURES = YAML.load_file File.dirname(__FILE__) + "/ami_fixtures.yml"
|
29
|
+
|
30
|
+
def fixture(path, overrides={})
|
31
|
+
path_segments = path.split '/'
|
32
|
+
selected_event = path_segments.inject(FIXTURES.clone) do |hash, segment|
|
33
|
+
raise ArgumentError, path + " not found!" unless hash
|
34
|
+
hash[segment.to_sym]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Downcase all keys in the event and the overrides
|
38
|
+
selected_event = selected_event.inject({}) do |downcased_hash,(key,value)|
|
39
|
+
downcased_hash[key.to_s.downcase] = value
|
40
|
+
downcased_hash
|
41
|
+
end
|
42
|
+
overrides = overrides.inject({}) do |downcased_hash,(key,value)|
|
43
|
+
downcased_hash[key.to_s.downcase] = value
|
44
|
+
downcased_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
# Replace variables in the selected_event with any overrides, ignoring case of the key
|
48
|
+
keys_with_variables = selected_event.select { |(key, value)| value.kind_of?(Symbol) || value.kind_of?(Hash) }
|
49
|
+
|
50
|
+
keys_with_variables.each do |original_key, variable_type|
|
51
|
+
# Does an override an exist in the supplied list?
|
52
|
+
if overriden_pair = overrides.find { |(key, value)| key == original_key }
|
53
|
+
# We have an override! Let's replace the template value in the event with the overriden value
|
54
|
+
selected_event[original_key] = overriden_pair.last
|
55
|
+
else
|
56
|
+
# Based on the type, let's generate a placeholder.
|
57
|
+
selected_event[original_key] = case variable_type
|
58
|
+
when :string
|
59
|
+
rand(100000).to_s
|
60
|
+
when Hash
|
61
|
+
if variable_type.has_key? "one_of"
|
62
|
+
# Choose a random possibility
|
63
|
+
possibilities = variable_type['one_of']
|
64
|
+
possibilities[rand(possibilities.size)]
|
65
|
+
else
|
66
|
+
raise "Unrecognized Hash fixture property! ##{variable_type.keys.to_sentence}"
|
67
|
+
end
|
68
|
+
else
|
69
|
+
raise "Unrecognized fixture variable type #{variable_type}!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
hash_to_stanza(selected_event).tap do |event|
|
75
|
+
selected_event.each_pair do |key, value|
|
76
|
+
event.meta_def(key) { value }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def hash_to_stanza(hash)
|
82
|
+
ordered_hash = hash.to_a
|
83
|
+
starter = hash.find { |(key, value)| key.strip =~ /^(Response|Action)$/i }
|
84
|
+
ordered_hash.unshift ordered_hash.delete(starter) if starter
|
85
|
+
ordered_hash.inject(String.new) do |stanza,(key, value)|
|
86
|
+
stanza + "#{key}: #{value}\r\n"
|
87
|
+
end + "\r\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_newlines(string)
|
91
|
+
# HOLY FUCK THIS IS UGLY
|
92
|
+
tmp_replacement = random_string
|
93
|
+
string.gsub("\r\n", tmp_replacement).
|
94
|
+
gsub("\n", "\r\n").
|
95
|
+
gsub(tmp_replacement, "\r\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
def random_string
|
99
|
+
(rand(1_000_000_000_000) + 1_000_000_000).to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def follows_body_text(name)
|
103
|
+
case name
|
104
|
+
when "ragel_description"
|
105
|
+
"Ragel is a software development tool that allows user actions to
|
106
|
+
be embedded into the transitions of a regular expression's corresponding state machine,
|
107
|
+
eliminating the need to switch from the regular expression engine and user code execution
|
108
|
+
environment and back again."
|
109
|
+
when "with_colon_after_first_line"
|
110
|
+
"Host Username Refresh State Reg.Time \r\nlax.teliax.net:5060 jicksta 105 Registered Tue, 11 Nov 2008 02:29:55"
|
111
|
+
when "show_channels_from_wayne"
|
112
|
+
"Channel Location State Application(Data)\r\n0 active channels\r\n0 active calls"
|
113
|
+
when "empty_string"
|
114
|
+
""
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
def syntax_error_data(name)
|
120
|
+
case name
|
121
|
+
when "immediate_packet_with_colon"
|
122
|
+
"!IJ@MHY:!&@B*!B @ ! @^! @ !@ !\r!@ ! @ !@ ! !!m, \n\\n\n"
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
=begin
|
2
|
+
describe "Connecting via AMI" do
|
3
|
+
it "should raise an exception if the password was invalid" do
|
4
|
+
host, port = "localhost", 5038
|
5
|
+
ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "bad_password", "localhost", :port => port
|
6
|
+
|
7
|
+
ami_server = AmiServer.new
|
8
|
+
flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
|
9
|
+
flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
|
10
|
+
|
11
|
+
the_following_code do
|
12
|
+
ami.connect!
|
13
|
+
end.should raise_error Adhearsion::VoIP::Asterisk::AMI::AuthenticationFailedException
|
14
|
+
ami.disconnect!
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should discover its own permissions and make them available as connection attributes"
|
18
|
+
|
19
|
+
it "should find the Asterisk version when connecting" do
|
20
|
+
host, port = "localhost", 5038
|
21
|
+
ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port
|
22
|
+
|
23
|
+
ami_server = AmiServer.new
|
24
|
+
flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
|
25
|
+
flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
|
26
|
+
|
27
|
+
ami.connect!
|
28
|
+
ami.version.should == "1.0"
|
29
|
+
ami.disconnect!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "The AMI command interface" do
|
34
|
+
|
35
|
+
before do
|
36
|
+
host, port = "localhost", 5038
|
37
|
+
@ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", host, :port => port, :events => false
|
38
|
+
|
39
|
+
ami_server = AmiServer.new
|
40
|
+
flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
|
41
|
+
flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
|
42
|
+
|
43
|
+
@ami.connect!
|
44
|
+
end
|
45
|
+
|
46
|
+
after do
|
47
|
+
@ami.disconnect!
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should respond to an immediate command" do
|
51
|
+
resp = @ami.queues
|
52
|
+
resp[0][:raw].should be_a_kind_of String
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should respond to a follows command" do
|
56
|
+
resp = @ami.command :Command => "show channels"
|
57
|
+
resp[0][:raw].should be_a_kind_of String
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should respond to a DBGet for a non-existent key with an exception" do
|
61
|
+
the_following_code do
|
62
|
+
resp = @ami.dbget :Family => "none", :Key => "somekey"
|
63
|
+
end.should raise_error Adhearsion::VoIP::Asterisk::AMI::ActionError
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should respond to a DBGet for a key with an event" do
|
67
|
+
resp = @ami.dbput :Family => "none", :Key => "somekey", :Val => 5
|
68
|
+
resp = @ami.dbget :Family => "none", :Key => "somekey"
|
69
|
+
resp[0]['Val'].should == "5"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should respond to a command that generates follows event(s)" do
|
73
|
+
resp = @ami.queuestatus
|
74
|
+
resp[0]['Queue'].should == "default"
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should show usage for an improper follows command" do
|
78
|
+
resp = @ami.command :Command => "meetme list"
|
79
|
+
resp[0][:raw].should be_a_kind_of String
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should respond to a synchronous originate"
|
83
|
+
it "should respond to an asynchronous originate"
|
84
|
+
|
85
|
+
it "should define events() as a private method to prevent turning events on or off" do
|
86
|
+
@ami.private_methods.include?("events").should be true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should raise an exception when Asterisk doesn't recognize a command" do
|
90
|
+
the_following_code {
|
91
|
+
@ami.this_command_does_not_exist_kthx
|
92
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::AMI::ActionError
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'AMI#originate' do
|
99
|
+
include AmiCommandTestHelper
|
100
|
+
it "should pass the arguments to execute_ami_command! with the options given" do
|
101
|
+
ami = new_ami_instance
|
102
|
+
options = { :channel => "ohai_lolz", :application => "Echo" }
|
103
|
+
flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options).once
|
104
|
+
ami.originate options
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should rename the :caller_id Hash key to :callerid" do
|
108
|
+
ami, caller_id = new_ami_instance, "Jay"
|
109
|
+
options = { :channel => "ohai_lolz", :application => "Echo"}
|
110
|
+
flexmock(ami).should_receive(:execute_ami_command!).with(:originate, options.merge(:callerid => caller_id)).once
|
111
|
+
ami.originate options.merge(:caller_id => caller_id)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'AMI#call_and_exec' do
|
117
|
+
include AmiCommandTestHelper
|
118
|
+
it "should execute originate properly with the minimum arguments" do
|
119
|
+
number, app = "12224446666", "Echo"
|
120
|
+
|
121
|
+
ami = flexmock new_ami_instance
|
122
|
+
ami.should_receive(:originate).once.with(:channel => number, :application => app).and_return true
|
123
|
+
ami.call_and_exec number, app
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'AMI#introduce' do
|
129
|
+
|
130
|
+
include AmiCommandTestHelper
|
131
|
+
|
132
|
+
it "should execute origiante properly (when :caller_id and :options aren't specified)" do
|
133
|
+
caller, callee, caller_id = "SIP/12224446666@trunk", "SIP/12224447777@trunk", "Jay Phillips"
|
134
|
+
|
135
|
+
correct_args = {:application => "Dial", :channel => caller, :data => callee, :caller_id => "Jay"}
|
136
|
+
ami = flexmock new_ami_instance
|
137
|
+
ami.should_receive(:originate).once.with(correct_args).and_return(true)
|
138
|
+
ami.introduce caller, callee, :caller_id => "Jay"
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "The manager proxy" do
|
144
|
+
before do
|
145
|
+
host, port = "localhost", 5038
|
146
|
+
@ami = Adhearsion::VoIP::Asterisk::AMI.new "admin", "password", "localhost", :port => port, :events => false
|
147
|
+
|
148
|
+
ami_server = AmiServer.new
|
149
|
+
flexmock(TCPSocket).should_receive(:new).once.with(host, port).and_return(ami_server)
|
150
|
+
flexmock(IO).should_receive(:select).at_least.once.with([ami_server], nil, nil, 1.0).and_return(true)
|
151
|
+
|
152
|
+
@ami.connect!
|
153
|
+
@door = DRb.start_service "druby://127.0.0.1:9050", Adhearsion::DrbDoor.instance
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should accept a command" do
|
157
|
+
client = DRbObject.new nil, DRb.uri
|
158
|
+
client.proxy.ping
|
159
|
+
end
|
160
|
+
|
161
|
+
after do
|
162
|
+
DRb.stop_service
|
163
|
+
@ami.disconnect!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "The command-sending interface" do
|
168
|
+
it "should raise an exception if permission was denied"
|
169
|
+
it "should allow variables to be specified as a Hash"
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "Sent arbitrary AMI commands" do
|
173
|
+
|
174
|
+
it "should allow a convenient way of parsing by event name"
|
175
|
+
it "should return Hash arguments"
|
176
|
+
|
177
|
+
it "should recognize its subclasses"
|
178
|
+
it "should send events to all of its subclasses"
|
179
|
+
it "should catch action names with method_missing() and format them properly"
|
180
|
+
|
181
|
+
it "should raise an exception if permission was denied"
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "AMI Packets" do
|
185
|
+
it "A Packet should not be an error" do
|
186
|
+
Adhearsion::VoIP::Asterisk::AMI::Packet.new.error?.should.be false
|
187
|
+
end
|
188
|
+
it "An ErrorPacket should be an error" do
|
189
|
+
Adhearsion::VoIP::Asterisk::AMI::ErrorPacket.new.error?.should.be true
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
BEGIN {
|
194
|
+
module AmiCommandTestHelper
|
195
|
+
def new_ami_instance
|
196
|
+
# TODO. mock everything out here
|
197
|
+
Adhearsion::VoIP::Asterisk::AMI.new("user","pass").tap do |ami|
|
198
|
+
flexmock(ami).should_receive(:connect!).and_return(true)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
}
|
204
|
+
=end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Story: Building and sending high-level AMI actions with SuperManager
|
2
|
+
|
3
|
+
As an Adhearsion user
|
4
|
+
I want higher-level, intuitive abstractions of the AMI protocol
|
5
|
+
So that I can control Asterisk better
|
6
|
+
|
7
|
+
TODO:
|
8
|
+
- Migrate to cucumber
|
9
|
+
|
10
|
+
Scenario: Sending a Ping
|
11
|
+
|
12
|
+
Given a new SuperManager
|
13
|
+
And a "Ping" action
|
14
|
+
|
15
|
+
When the data it sent
|
16
|
+
And the server responds with
|
17
|
+
|
18
|
+
Then the data sent should be well-formed
|
19
|
+
And the 1st action should have the action name "Ping"
|
20
|
+
And the 1st action should have an ActionID
|
21
|
+
And the 1st action should have exactly 2 headers
|
22
|
+
|
23
|
+
Scenario: An action with many
|
24
|
+
|
25
|
+
Scenario: A Channel object
|
@@ -0,0 +1,2179 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'adhearsion/voip/menu_state_machine/menu_class'
|
3
|
+
require 'adhearsion/voip/menu_state_machine/menu_builder'
|
4
|
+
|
5
|
+
module DialplanCommandTestHelpers
|
6
|
+
def self.included(test_case)
|
7
|
+
test_case.send(:attr_reader, :mock_call, :input, :output)
|
8
|
+
|
9
|
+
test_case.before do
|
10
|
+
Adhearsion::Configuration.configure { |config| config.enable_asterisk() }
|
11
|
+
|
12
|
+
@input = MockSocket.new
|
13
|
+
@output = MockSocket.new
|
14
|
+
@mock_call = Object.new
|
15
|
+
@mock_call.metaclass.send(:attr_reader, :call)
|
16
|
+
mock_call.extend(Adhearsion::VoIP::Asterisk::Commands)
|
17
|
+
flexmock(mock_call) do |call|
|
18
|
+
call.should_receive(:from_pbx).and_return(input)
|
19
|
+
call.should_receive(:to_pbx).and_return(output)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class MockSocket
|
25
|
+
|
26
|
+
def print(message)
|
27
|
+
messages << message
|
28
|
+
end
|
29
|
+
|
30
|
+
def puts(message)
|
31
|
+
messages << message.chomp + "\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
def read
|
35
|
+
messages.shift
|
36
|
+
end
|
37
|
+
|
38
|
+
def gets
|
39
|
+
read
|
40
|
+
end
|
41
|
+
|
42
|
+
def messages
|
43
|
+
@messages ||= []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def should_pass_control_to_a_context_that_throws(symbol, &block)
|
51
|
+
did_the_rescue_block_get_executed = false
|
52
|
+
begin
|
53
|
+
yield
|
54
|
+
rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe
|
55
|
+
did_the_rescue_block_get_executed = true
|
56
|
+
cpe.target.should throw_symbol symbol
|
57
|
+
rescue => e
|
58
|
+
did_the_rescue_block_get_executed = true
|
59
|
+
raise e
|
60
|
+
ensure
|
61
|
+
did_the_rescue_block_get_executed.should be true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def should_throw(sym=nil,&block)
|
66
|
+
block.should throw_symbol(*[sym].compact)
|
67
|
+
end
|
68
|
+
|
69
|
+
def mock_route_calculation_with(*definitions)
|
70
|
+
flexmock(Adhearsion::VoIP::DSL::DialingDSL).should_receive(:calculate_routes_for).and_return(definitions)
|
71
|
+
end
|
72
|
+
|
73
|
+
def pbx_should_have_been_sent(message)
|
74
|
+
output.gets.chomp.should == message
|
75
|
+
end
|
76
|
+
|
77
|
+
def pbx_should_respond_with(message)
|
78
|
+
input.print message
|
79
|
+
end
|
80
|
+
|
81
|
+
def pbx_should_respond_with_digits(string_of_digits)
|
82
|
+
pbx_should_respond_with "200 result=#{string_of_digits}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def pbx_should_respond_with_digits_and_timeout(string_of_digits)
|
86
|
+
pbx_should_respond_with "200 result=#{string_of_digits} (timeout)"
|
87
|
+
end
|
88
|
+
|
89
|
+
def pbx_should_respond_to_timeout(timeout)
|
90
|
+
pbx_should_respond_with "200 result=#{timeout}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def pbx_should_respond_with_value(value)
|
94
|
+
pbx_should_respond_with "200 result=1 (#{value})"
|
95
|
+
end
|
96
|
+
|
97
|
+
def pbx_should_respond_with_success(success_code = nil)
|
98
|
+
pbx_should_respond_with pbx_success_response(success_code)
|
99
|
+
end
|
100
|
+
alias does_not_read_data_back pbx_should_respond_with_success
|
101
|
+
|
102
|
+
def pbx_should_respond_with_failure(failure_code = nil)
|
103
|
+
pbx_should_respond_with(pbx_failure_response(failure_code))
|
104
|
+
end
|
105
|
+
|
106
|
+
def pbx_should_respond_with_successful_background_response(digit=0)
|
107
|
+
pbx_should_respond_with_success digit.kind_of?(String) ? digit[0] : digit
|
108
|
+
end
|
109
|
+
|
110
|
+
def pbx_should_respond_with_a_wait_for_digit_timeout
|
111
|
+
pbx_should_respond_with_successful_background_response 0
|
112
|
+
end
|
113
|
+
|
114
|
+
def pbx_success_response(success_code = nil)
|
115
|
+
"200 result=#{success_code || default_success_code}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def default_success_code
|
119
|
+
'1'
|
120
|
+
end
|
121
|
+
|
122
|
+
def pbx_failure_response(failure_code = nil)
|
123
|
+
"200 result=#{failure_code || default_failure_code}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def default_failure_code
|
127
|
+
'-1'
|
128
|
+
end
|
129
|
+
|
130
|
+
def output_stream_matches(pattern)
|
131
|
+
output.gets.should match pattern
|
132
|
+
end
|
133
|
+
|
134
|
+
module OutputStreamMatchers
|
135
|
+
def pbx_was_asked_to_play(*audio_files)
|
136
|
+
audio_files.each do |audio_file|
|
137
|
+
output_stream_matches(/playback "#{audio_file}"/)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def pbx_was_asked_to_play_number(number)
|
142
|
+
output_stream_matches(/saynumber "#{number}"/)
|
143
|
+
end
|
144
|
+
|
145
|
+
def pbx_was_asked_to_play_time(number)
|
146
|
+
output_stream_matches(/sayunixtime "#{number}"/)
|
147
|
+
end
|
148
|
+
|
149
|
+
def pbx_was_asked_to_execute(application, *options)
|
150
|
+
output_stream_matches(/exec saydigits "#{options.join('|')}"/i)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
include OutputStreamMatchers
|
154
|
+
|
155
|
+
def assert_success(response)
|
156
|
+
response.should == pbx_success_response
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
module MenuBuilderTestHelper
|
163
|
+
def builder_should_match_with_these_quantities_of_calculated_matches(checks)
|
164
|
+
checks.each do |check,hash|
|
165
|
+
hash.each_pair do |method_name,intended_quantity|
|
166
|
+
builder.calculate_matches_for(check).send(method_name).should == intended_quantity
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module MenuTestHelper
|
173
|
+
|
174
|
+
def pbx_should_send_digits(*digits)
|
175
|
+
digits.each do |digit|
|
176
|
+
digit = nil if digit == :timeout
|
177
|
+
mock_call.should_receive(:interruptible_play).once.and_return(digit)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
module ConfirmationManagerTestHelper
|
183
|
+
def encode_hash(hash)
|
184
|
+
Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument(hash)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe 'Asterisk VoIP Commands' do
|
189
|
+
include DialplanCommandTestHelpers
|
190
|
+
|
191
|
+
it "a call can write back to the PBX" do
|
192
|
+
message = 'oh hai'
|
193
|
+
mock_call.write message
|
194
|
+
pbx_should_have_been_sent message
|
195
|
+
end
|
196
|
+
end
|
197
|
+
describe 'hangup command' do
|
198
|
+
include DialplanCommandTestHelpers
|
199
|
+
|
200
|
+
it "hanging up a call succesfully writes HANGUP back to the PBX and a success resopnse is returned" do
|
201
|
+
pbx_should_respond_with_success
|
202
|
+
response = mock_call.hangup
|
203
|
+
pbx_should_have_been_sent 'HANGUP'
|
204
|
+
response.should == pbx_success_response
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'interruptible_play command' do
|
209
|
+
|
210
|
+
include DialplanCommandTestHelpers
|
211
|
+
|
212
|
+
it 'should return a string for the digit that was pressed' do
|
213
|
+
digits = %w{0 1 # * 9}.map{|c| c.ord}
|
214
|
+
file = "file_doesnt_matter"
|
215
|
+
digits.each { |digit| pbx_should_respond_with_success digit }
|
216
|
+
digits.map { |digit| mock_call.send(:interruptible_play, file) }.should == digits.map(&:chr)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should return nil if no digit was pressed" do
|
220
|
+
pbx_should_respond_with_success 0
|
221
|
+
mock_call.send(:interruptible_play, 'foobar').should be nil
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should play a series of files, stopping the series when a digit is played" do
|
225
|
+
stubbed_keypad_input = [0, 0, ?3.ord]
|
226
|
+
stubbed_keypad_input.each do |digit|
|
227
|
+
pbx_should_respond_with_success digit
|
228
|
+
end
|
229
|
+
|
230
|
+
files = (100..105).map(&:to_s)
|
231
|
+
mock_call.send(:interruptible_play, *files).should == '3'
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
describe 'wait_for_digit command' do
|
237
|
+
|
238
|
+
include DialplanCommandTestHelpers
|
239
|
+
|
240
|
+
it 'should return a string for the digit that was pressed' do
|
241
|
+
digits = %w{0 1 # * 9}.map{|c| c.ord}
|
242
|
+
digits.each { |digit| pbx_should_respond_with_success digit }
|
243
|
+
digits.map { |digit| mock_call.send(:wait_for_digit) }.should == digits.map(&:chr)
|
244
|
+
end
|
245
|
+
|
246
|
+
it "the timeout given must be converted to milliseconds" do
|
247
|
+
pbx_should_respond_with_success 0
|
248
|
+
mock_call.send(:wait_for_digit, 1)
|
249
|
+
output.messages.first.chomp.ends_with?('"1000"').should be true
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
describe 'answer' do
|
254
|
+
include DialplanCommandTestHelpers
|
255
|
+
|
256
|
+
it 'should send ANSWER over the AGI socket' do
|
257
|
+
does_not_read_data_back
|
258
|
+
mock_call.answer
|
259
|
+
pbx_should_have_been_sent 'ANSWER'
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
describe 'execute' do
|
265
|
+
include DialplanCommandTestHelpers
|
266
|
+
|
267
|
+
it 'execute writes exec and app name to the PBX' do
|
268
|
+
pbx_should_respond_with_success
|
269
|
+
assert_success mock_call.execute(:foo)
|
270
|
+
pbx_should_have_been_sent 'EXEC foo ""'
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'execute returns false if the command was not executed successfully by the PBX' do
|
274
|
+
pbx_should_respond_with_failure
|
275
|
+
mock_call.execute(:foo).should_not be true
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'execute can accept arguments after the app name which get translated into pipe-delimited arguments to the PBX' do
|
279
|
+
pbx_should_respond_with_success
|
280
|
+
mock_call.execute :foo, 'bar', 'baz', 'hi'
|
281
|
+
pbx_should_have_been_sent 'EXEC foo "bar"|"baz"|"hi"'
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should raise a Hangup exception when nil is returned when reading a command from Asterisk" do
|
285
|
+
flexmock(input).should_receive(:gets).once.and_return nil
|
286
|
+
the_following_code {
|
287
|
+
mock_call.execute :foo, "bar"
|
288
|
+
}.should raise_error Adhearsion::Hangup
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should raise a ArgumentError if given a null byte in the arguments" do
|
292
|
+
the_following_code {
|
293
|
+
mock_call.execute :foo, "bar\0"
|
294
|
+
}.should raise_error ArgumentError
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
describe 'play command' do
|
300
|
+
include DialplanCommandTestHelpers
|
301
|
+
|
302
|
+
it 'passing a single string to play results in the playback application being executed with that file name on the PBX' do
|
303
|
+
pbx_should_respond_with_success
|
304
|
+
audio_file = "cents-per-minute"
|
305
|
+
mock_call.play audio_file
|
306
|
+
pbx_was_asked_to_play audio_file
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'multiple strings can be passed to play, causing multiple playback commands to be issued' do
|
310
|
+
2.times do
|
311
|
+
pbx_should_respond_with_success
|
312
|
+
end
|
313
|
+
audio_files = ["cents-per-minute", 'o-hai']
|
314
|
+
mock_call.play(*audio_files)
|
315
|
+
pbx_was_asked_to_play(*audio_files)
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'If a number is passed to play(), the saynumber application is executed with the number as an argument' do
|
319
|
+
pbx_should_respond_with_success
|
320
|
+
mock_call.play 123
|
321
|
+
pbx_was_asked_to_play_number(123)
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'if a string representation of a number is passed to play(), the saynumber application is executed with the number as an argument' do
|
325
|
+
pbx_should_respond_with_success
|
326
|
+
mock_call.play '123'
|
327
|
+
pbx_was_asked_to_play_number(123)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'If a Time is passed to play(), the SayUnixTime application will be executed with the time since the UNIX epoch in seconds as an argument' do
|
331
|
+
time = Time.parse("12/5/2000")
|
332
|
+
pbx_should_respond_with_success
|
333
|
+
mock_call.play time
|
334
|
+
pbx_was_asked_to_play_time(time.to_i)
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'If a Date is passed to play(), the SayUnixTime application will be executed with the date passed in' do
|
338
|
+
date = Date.parse('2011-01-23')
|
339
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",'BdY').and_return('200')
|
340
|
+
mock_call.play date
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'If a Date or Time is passed to play_time(), the SayUnixTime application will be executed with the date and format passed in' do
|
344
|
+
date, format = Date.parse('2011-01-23'), 'ABdY'
|
345
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return('200')
|
346
|
+
mock_call.play_time date, :format => format
|
347
|
+
|
348
|
+
time, format = Time.at(875121313), 'BdY \'digits/at\' IMp'
|
349
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",format).and_return('200')
|
350
|
+
mock_call.play_time time, :format => format
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'If a Time object is passed to play_time, the SayUnixTime application will be executed with the default parameters' do
|
354
|
+
time = Time.at(875121313)
|
355
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, "",'').and_return('200')
|
356
|
+
mock_call.play_time time
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'If an object other than Time, DateTime, or Date is passed to play_time false will be returned' do
|
360
|
+
non_time = 'blah'
|
361
|
+
mock_call.play_time(non_time).should be false
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'If an array containing a Date/DateTime/Time object and a hash is passed to play(), the SayUnixTime application will be executed with the object passed in with the specified format and timezone' do
|
365
|
+
date, format = Date.parse('2011-01-23'), 'ABdY'
|
366
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, date.to_time.to_i, "",format).and_return('200')
|
367
|
+
mock_call.play [date, {:format => format}]
|
368
|
+
|
369
|
+
time, timezone = Time.at(1295843084), 'US/Eastern'
|
370
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,'').and_return('200')
|
371
|
+
mock_call.play [time, {:timezone => timezone}]
|
372
|
+
|
373
|
+
time, timezone, format = Time.at(1295843084), 'US/Eastern', 'ABdY \'digits/at\' IMp'
|
374
|
+
mock_call.should_receive(:execute).once.with(:sayunixtime, time.to_i, timezone,format).and_return('200')
|
375
|
+
mock_call.play [time, {:timezone => timezone, :format => format}]
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'If a string matching dollars and (optionally) cents is passed to play(), a series of command will be executed to read the dollar amount', :ignore => true do
|
379
|
+
#TODO: I think we should not have this be part of play(). Too much functionality in one method. Too much overloading. When we want to support multiple
|
380
|
+
# currencies, it'll be completely unwieldy. I'd suggest play_currency as a separate method. - Chad
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe 'input command' do
|
385
|
+
|
386
|
+
include DialplanCommandTestHelpers
|
387
|
+
|
388
|
+
# pbx_should_respond_with_successful_background_response
|
389
|
+
# pbx_should_respond_with_a_wait_for_digit_timeout
|
390
|
+
|
391
|
+
it 'should raise an error when the number of digits expected is -1 (this is deprecated behavior)' do
|
392
|
+
the_following_code {
|
393
|
+
mock_call.input(-1)
|
394
|
+
}.should raise_error ArgumentError
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'input() calls wait_for_digit the specified number of times (when no sound files are given)' do
|
398
|
+
# mock_call.should_receive(:interruptible_play).never
|
399
|
+
mock_call.should_receive(:wait_for_digit).times(4).and_return('1', '2', '3', '4')
|
400
|
+
mock_call.input(4).should == '1234'
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'should execute wait_for_digit if no digit is pressed during interruptible_play' do
|
404
|
+
sound_files = %w[one two three]
|
405
|
+
mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
|
406
|
+
mock_call.should_receive(:wait_for_digit).once.and_throw :digit_request
|
407
|
+
should_throw(:digit_request) { mock_call.input(10, :play => sound_files) }
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should default the :accept_key to "#" when unlimited digits are to be collected' do
|
411
|
+
mock_call.should_receive(:wait_for_digit).times(2).and_return '*', '#'
|
412
|
+
mock_call.input.should == '*'
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'should raise an exception when unlimited digits are to be collected and :accept_key => false' do
|
416
|
+
flexstub(mock_call).should_receive(:read).and_return
|
417
|
+
the_following_code {
|
418
|
+
mock_call.input(:accept_key => false)
|
419
|
+
}.should raise_error ArgumentError
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'when :accept_key is false and input() is collecting a finite number of digits, it should allow all DTMFs' do
|
423
|
+
all_digits = %w[0 1 2 3 # * 4 5 6 7 8 9]
|
424
|
+
mock_call.should_receive(:wait_for_digit).times(all_digits.size).and_return(*all_digits)
|
425
|
+
the_following_code {
|
426
|
+
mock_call.input(all_digits.size, :accept_key => false)
|
427
|
+
}.should_not raise_error ArgumentError
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'passes wait_for_digit the :timeout option when one is given' do
|
431
|
+
mock_call.should_receive(:interruptible_play).never
|
432
|
+
mock_call.should_receive(:wait_for_digit).twice.and_return '1', '2'
|
433
|
+
mock_call.input(2, :timeout => 1.minute).should == '12'
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'executes interruptible_play() with all of the files given to :play' do
|
437
|
+
sound_files = %w[foo bar qaz]
|
438
|
+
mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return '#'
|
439
|
+
mock_call.should_receive(:wait_for_digit).once.and_return '*'
|
440
|
+
mock_call.input(2, :play => sound_files).should == '#*'
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'pressing the terminating key before any other digits returns an empty string' do
|
444
|
+
mock_call.should_receive(:wait_for_digit).once.and_return '*'
|
445
|
+
mock_call.input(:accept_key => '*').should == ''
|
446
|
+
end
|
447
|
+
|
448
|
+
it 'should execute wait_for_digit first if no sound files are given' do
|
449
|
+
mock_call.should_receive(:interruptible_play).never
|
450
|
+
mock_call.should_receive(:wait_for_digit).once.and_throw :digit_request
|
451
|
+
should_throw(:digit_request) { mock_call.input(1) }
|
452
|
+
end
|
453
|
+
|
454
|
+
it "Input timing out when digits are pressed returns only the collected digits" do
|
455
|
+
mock_call.should_receive(:wait_for_digit).twice.and_return '5', nil
|
456
|
+
mock_call.input(9, :timeout => 1.day).should == '5'
|
457
|
+
end
|
458
|
+
|
459
|
+
end
|
460
|
+
|
461
|
+
describe "The variable() command" do
|
462
|
+
|
463
|
+
include DialplanCommandTestHelpers
|
464
|
+
|
465
|
+
it "should call set_variable for every Hash-key argument given" do
|
466
|
+
args = [:ohai, "ur_home_erly"]
|
467
|
+
mock_call.should_receive(:set_variable).once.with(*args)
|
468
|
+
mock_call.variable Hash[*args]
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should call set_variable for every Hash-key argument given" do
|
472
|
+
many_args = { :a => :b, :c => :d, :e => :f, :g => :h}
|
473
|
+
mock_call.should_receive(:set_variable).times(many_args.size)
|
474
|
+
mock_call.variable many_args
|
475
|
+
end
|
476
|
+
|
477
|
+
it "should call get_variable for every String given" do
|
478
|
+
variables = ["foo", "bar", :qaz, :qwerty, :baz]
|
479
|
+
variables.each do |var|
|
480
|
+
mock_call.should_receive(:get_variable).once.with(var).and_return("X")
|
481
|
+
end
|
482
|
+
mock_call.variable(*variables)
|
483
|
+
end
|
484
|
+
|
485
|
+
it "should NOT return an Array when just one arg is given" do
|
486
|
+
mock_call.should_receive(:get_variable).once.and_return "lol"
|
487
|
+
mock_call.variable(:foo).should_not be_a_kind_of Array
|
488
|
+
end
|
489
|
+
|
490
|
+
it "should raise an ArgumentError when a Hash and normal args are given" do
|
491
|
+
the_following_code {
|
492
|
+
mock_call.variable 5,4,3,2,1, :foo => :bar
|
493
|
+
}.should raise_error ArgumentError
|
494
|
+
end
|
495
|
+
|
496
|
+
end
|
497
|
+
|
498
|
+
describe "the set_variable method" do
|
499
|
+
|
500
|
+
include DialplanCommandTestHelpers
|
501
|
+
|
502
|
+
it "variables and values are properly quoted" do
|
503
|
+
mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "foo" "i can \\" has ruby?"'
|
504
|
+
mock_call.set_variable 'foo', 'i can " has ruby?'
|
505
|
+
end
|
506
|
+
|
507
|
+
it "to_s() is effectively called on both the key and the value" do
|
508
|
+
mock_call.should_receive(:raw_response).once.with 'SET VARIABLE "QAZ" "QWERTY"'
|
509
|
+
mock_call.set_variable :QAZ, :QWERTY
|
510
|
+
end
|
511
|
+
|
512
|
+
end
|
513
|
+
|
514
|
+
describe "the sip_add_header method" do
|
515
|
+
include DialplanCommandTestHelpers
|
516
|
+
|
517
|
+
it "values are properly quoted" do
|
518
|
+
mock_call.should_receive(:raw_response).once.with 'EXEC SIPAddHeader "x-ahn-header: rubyrox"'
|
519
|
+
mock_call.sip_add_header "x-ahn-header", "rubyrox"
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
describe "the sip_get_header method" do
|
524
|
+
include DialplanCommandTestHelpers
|
525
|
+
|
526
|
+
it "values are properly quoted" do
|
527
|
+
mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
|
528
|
+
mock_call.sip_get_header "x-ahn-header"
|
529
|
+
end
|
530
|
+
|
531
|
+
it "values are properly quoted with aliased method" do
|
532
|
+
mock_call.should_receive(:raw_response).once.with 'GET VARIABLE "SIP_HEADER(x-ahn-header)"'
|
533
|
+
mock_call.sip_header "x-ahn-header"
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
describe 'the voicemail command' do
|
538
|
+
|
539
|
+
include DialplanCommandTestHelpers
|
540
|
+
|
541
|
+
it 'should not send the context name when none is given' do
|
542
|
+
mailbox_number = 123
|
543
|
+
mock_call.should_receive(:execute).once.with('voicemail', 123, '').and_throw :sent_voicemail!
|
544
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail 123 }
|
545
|
+
end
|
546
|
+
|
547
|
+
it 'should send the context name when one is given' do
|
548
|
+
mailbox_number, context_name = 333, 'doesntmatter'
|
549
|
+
mock_call.should_receive(:execute).once.with('voicemail', "#{mailbox_number}@#{context_name}", '').and_throw :sent_voicemail!
|
550
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail(context_name => mailbox_number) }
|
551
|
+
end
|
552
|
+
|
553
|
+
it 'should pass in the s option if :skip => true' do
|
554
|
+
mailbox_number = '012'
|
555
|
+
mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 's').and_throw :sent_voicemail!
|
556
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :skip => true) }
|
557
|
+
end
|
558
|
+
|
559
|
+
it 'should combine mailbox numbers with the context name given when both are given' do
|
560
|
+
does_not_read_data_back
|
561
|
+
context = "lolcats"
|
562
|
+
mailboxes = [1,2,3,4,5]
|
563
|
+
mailboxes_with_context = mailboxes.map { |mailbox| "#{mailbox}@#{context}"}
|
564
|
+
mock_call.should_receive(:execute).once.with('voicemail', mailboxes_with_context.join('&'), '')
|
565
|
+
mock_call.voicemail context => mailboxes
|
566
|
+
end
|
567
|
+
|
568
|
+
it 'should raise an argument error if the mailbox number is not numerical' do
|
569
|
+
the_following_code {
|
570
|
+
mock_call.voicemail :foo => "bar"
|
571
|
+
}.should raise_error ArgumentError
|
572
|
+
end
|
573
|
+
|
574
|
+
it 'should raise an argument error if too many arguments are supplied' do
|
575
|
+
the_following_code {
|
576
|
+
mock_call.voicemail "wtfisthisargument", :context_name => 123, :greeting => :busy
|
577
|
+
}.should raise_error ArgumentError
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'should raise an ArgumentError if multiple context names are given' do
|
581
|
+
the_following_code {
|
582
|
+
mock_call.voicemail :one => [1,2,3], :two => [11,22,33]
|
583
|
+
}.should raise_error ArgumentError
|
584
|
+
end
|
585
|
+
|
586
|
+
it "should raise an ArgumentError when the :greeting value isn't recognized" do
|
587
|
+
the_following_code {
|
588
|
+
mock_call.voicemail :context_name => 123, :greeting => :zomgz
|
589
|
+
}.should raise_error ArgumentError
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'should pass in the u option if :greeting => :unavailable' do
|
593
|
+
mailbox_number = '776'
|
594
|
+
mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail!
|
595
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) }
|
596
|
+
end
|
597
|
+
|
598
|
+
it 'should pass in both the skip and greeting options if both are supplied' do
|
599
|
+
mailbox_number = '4'
|
600
|
+
mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'u').and_throw :sent_voicemail!
|
601
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :unavailable) }
|
602
|
+
end
|
603
|
+
|
604
|
+
it 'should raise an ArgumentError if mailbox_number is blank?()' do
|
605
|
+
the_following_code {
|
606
|
+
mock_call.voicemail ''
|
607
|
+
}.should raise_error ArgumentError
|
608
|
+
|
609
|
+
the_following_code {
|
610
|
+
mock_call.voicemail nil
|
611
|
+
}.should raise_error ArgumentError
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'should pass in the b option if :gretting => :busy' do
|
615
|
+
mailbox_number = '1'
|
616
|
+
mock_call.should_receive(:execute).once.with('voicemail', mailbox_number, 'b').and_throw :sent_voicemail!
|
617
|
+
should_throw(:sent_voicemail!) { mock_call.voicemail(mailbox_number, :greeting => :busy) }
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'should return true if VMSTATUS == "SUCCESS"' do
|
621
|
+
mock_call.should_receive(:execute).once
|
622
|
+
mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "SUCCESS"
|
623
|
+
mock_call.voicemail(3).should be true
|
624
|
+
end
|
625
|
+
|
626
|
+
it 'should return false if VMSTATUS == "USEREXIT"' do
|
627
|
+
mock_call.should_receive(:execute).once
|
628
|
+
mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "USEREXIT"
|
629
|
+
mock_call.voicemail(2).should be false
|
630
|
+
end
|
631
|
+
|
632
|
+
it 'should return nil if VMSTATUS == "FAILED"' do
|
633
|
+
mock_call.should_receive(:execute).once
|
634
|
+
mock_call.should_receive(:variable).once.with('VMSTATUS').and_return "FAILED"
|
635
|
+
mock_call.voicemail(2).should be nil
|
636
|
+
end
|
637
|
+
|
638
|
+
end
|
639
|
+
|
640
|
+
describe 'The voicemail_main command' do
|
641
|
+
|
642
|
+
include DialplanCommandTestHelpers
|
643
|
+
|
644
|
+
#it "should not pass in the context or the delimiting @ sign if you don't supply one"
|
645
|
+
|
646
|
+
it "the :folder Hash key argument should wrap the value in a()" do
|
647
|
+
folder = "foobar"
|
648
|
+
mailbox = 81
|
649
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","a(#{folder})")
|
650
|
+
mock_call.voicemail_main :mailbox => mailbox, :folder => folder
|
651
|
+
end
|
652
|
+
|
653
|
+
it ':authenticate should pass in the "s" option if given false' do
|
654
|
+
mailbox = 333
|
655
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}","s")
|
656
|
+
mock_call.voicemail_main :mailbox => mailbox, :authenticate => false
|
657
|
+
end
|
658
|
+
|
659
|
+
it ':authenticate should pass in the s option if given false' do
|
660
|
+
mailbox = 55
|
661
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}")
|
662
|
+
mock_call.voicemail_main :mailbox => mailbox, :authenticate => true
|
663
|
+
end
|
664
|
+
|
665
|
+
it 'should not pass any flags only a mailbox is given' do
|
666
|
+
mailbox = "1"
|
667
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}")
|
668
|
+
mock_call.voicemail_main :mailbox => mailbox
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'when given no mailbox or context an empty string should be passed to execute as the first argument' do
|
672
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "", "s")
|
673
|
+
mock_call.voicemail_main :authenticate => false
|
674
|
+
end
|
675
|
+
|
676
|
+
it 'should properly concatenate the options when given multiple ones' do
|
677
|
+
folder = "ohai"
|
678
|
+
mailbox = 9999
|
679
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "#{mailbox}", "sa(#{folder})")
|
680
|
+
mock_call.voicemail_main :mailbox => mailbox, :authenticate => false, :folder => folder
|
681
|
+
end
|
682
|
+
|
683
|
+
it 'should not require any arguments' do
|
684
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain")
|
685
|
+
mock_call.voicemail_main
|
686
|
+
end
|
687
|
+
|
688
|
+
it 'should pass in the "@context_name" part in if a :context is given and no mailbox is given' do
|
689
|
+
context_name = "icanhascheezburger"
|
690
|
+
mock_call.should_receive(:execute).once.with("VoiceMailMain", "@#{context_name}")
|
691
|
+
mock_call.voicemail_main :context => context_name
|
692
|
+
end
|
693
|
+
|
694
|
+
it "should raise an exception if the folder has a space or malformed characters in it" do
|
695
|
+
["i has a space", "exclaim!", ",", ""].each do |bad_folder_name|
|
696
|
+
the_following_code {
|
697
|
+
mock_call.voicemail_main :mailbox => 123, :folder => bad_folder_name
|
698
|
+
}.should raise_error ArgumentError
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
end
|
703
|
+
|
704
|
+
describe 'the check_voicemail command' do
|
705
|
+
|
706
|
+
include DialplanCommandTestHelpers
|
707
|
+
|
708
|
+
it "should simply execute voicemail_main with no arguments after warning" do
|
709
|
+
flexmock(ahn_log.agi).should_receive(:warn).once.with(String)
|
710
|
+
mock_call.should_receive(:voicemail_main).once.and_return :mocked_out
|
711
|
+
mock_call.check_voicemail.should be :mocked_out
|
712
|
+
end
|
713
|
+
|
714
|
+
end
|
715
|
+
|
716
|
+
|
717
|
+
describe "The queue management abstractions" do
|
718
|
+
|
719
|
+
include DialplanCommandTestHelpers
|
720
|
+
|
721
|
+
it 'should not create separate objects for queues with basically the same name' do
|
722
|
+
mock_call.queue('foo').should be mock_call.queue('foo')
|
723
|
+
mock_call.queue('bar').should be mock_call.queue(:bar)
|
724
|
+
end
|
725
|
+
|
726
|
+
it "queue() should return an instance of QueueProxy" do
|
727
|
+
mock_call.queue("foobar").should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy
|
728
|
+
end
|
729
|
+
|
730
|
+
it "a QueueProxy should respond to join!(), members()" do
|
731
|
+
%w[join! agents].each do |method|
|
732
|
+
mock_call.queue('foobar').should respond_to(method)
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
it 'a QueueProxy should return a QueueAgentsListProxy when members() is called' do
|
737
|
+
mock_call.queue('foobar').agents.should be_a_kind_of(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueAgentsListProxy)
|
738
|
+
end
|
739
|
+
|
740
|
+
it 'join! should properly join a queue' do
|
741
|
+
mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '', '')
|
742
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "FULL"
|
743
|
+
mock_call.queue("foobaz").join!
|
744
|
+
end
|
745
|
+
|
746
|
+
it 'should return a symbol representing the result of joining the queue' do
|
747
|
+
does_not_read_data_back
|
748
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "TIMEOUT"
|
749
|
+
mock_call.queue('monkey').join!.should be :timeout
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'should return :completed after joining the queue and being connected' do
|
753
|
+
does_not_read_data_back
|
754
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return nil
|
755
|
+
mock_call.queue('monkey').join!.should be :completed
|
756
|
+
end
|
757
|
+
|
758
|
+
it 'should join a queue with a timeout properly' do
|
759
|
+
mock_call.should_receive(:execute).once.with("queue", "foobaz", "", '', '', '60', '')
|
760
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
761
|
+
mock_call.queue("foobaz").join! :timeout => 1.minute
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'should join a queue with an announcement file properly' do
|
765
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', 'custom_announcement_file_here', '', '')
|
766
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
767
|
+
mock_call.queue("roflcopter").join! :announce => 'custom_announcement_file_here'
|
768
|
+
end
|
769
|
+
|
770
|
+
it 'should join a queue with an agi script properly' do
|
771
|
+
mock_call.should_receive(:execute).once.with("queue", 'support', '', '', '', '','agi://localhost/queue_agi_test')
|
772
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINUNAVAIL"
|
773
|
+
mock_call.queue("support").join! :agi => 'agi://localhost/queue_agi_test'
|
774
|
+
end
|
775
|
+
|
776
|
+
it 'should join a queue with allow_transfer properly' do
|
777
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Tt", '', '', '', '')
|
778
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
779
|
+
mock_call.queue("roflcopter").join! :allow_transfer => :everyone
|
780
|
+
|
781
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "T", '', '', '', '')
|
782
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
783
|
+
mock_call.queue("roflcopter").join! :allow_transfer => :caller
|
784
|
+
|
785
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "t", '', '', '', '')
|
786
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
787
|
+
mock_call.queue("roflcopter").join! :allow_transfer => :agent
|
788
|
+
end
|
789
|
+
|
790
|
+
it 'should join a queue with allow_hangup properly' do
|
791
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "Hh", '', '', '', '')
|
792
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
793
|
+
mock_call.queue("roflcopter").join! :allow_hangup => :everyone
|
794
|
+
|
795
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "H", '', '', '', '')
|
796
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
797
|
+
mock_call.queue("roflcopter").join! :allow_hangup => :caller
|
798
|
+
|
799
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "h", '', '', '', '')
|
800
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
801
|
+
mock_call.queue("roflcopter").join! :allow_hangup => :agent
|
802
|
+
end
|
803
|
+
|
804
|
+
it 'should join a queue properly with the :play argument' do
|
805
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "r", '', '', '', '')
|
806
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
807
|
+
mock_call.queue("roflcopter").join! :play => :ringing
|
808
|
+
|
809
|
+
mock_call.should_receive(:execute).once.with("queue", "roflcopter", "", '', '', '', '')
|
810
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
811
|
+
mock_call.queue("roflcopter").join! :play => :music
|
812
|
+
end
|
813
|
+
|
814
|
+
it 'joining a queue with many options specified' do
|
815
|
+
mock_call.should_receive(:execute).once.with("queue", "q", "rtHh", '', '', '120', '')
|
816
|
+
mock_call.should_receive(:get_variable).once.with("QUEUESTATUS").and_return "JOINEMPTY"
|
817
|
+
mock_call.queue('q').join! :allow_transfer => :agent, :timeout => 2.minutes,
|
818
|
+
:play => :ringing, :allow_hangup => :everyone
|
819
|
+
end
|
820
|
+
|
821
|
+
it 'join!() should raise an ArgumentError when unrecognized Hash key arguments are given' do
|
822
|
+
the_following_code {
|
823
|
+
mock_call.queue('iwearmysunglassesatnight').join! :misspelled => true
|
824
|
+
}.should raise_error ArgumentError
|
825
|
+
end
|
826
|
+
|
827
|
+
it 'should fetch the members with the name given to queue()' do
|
828
|
+
mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(jay)").and_return 5
|
829
|
+
mock_call.queue('jay').agents.size.should be 5
|
830
|
+
end
|
831
|
+
|
832
|
+
it 'should not fetch a QUEUE_MEMBER_COUNT each time count() is called when caching is enabled' do
|
833
|
+
mock_call.should_receive(:variable).once.with("QUEUE_MEMBER_COUNT(sales)").and_return 0
|
834
|
+
10.times do
|
835
|
+
mock_call.queue('sales').agents(:cache => true).size
|
836
|
+
end
|
837
|
+
end
|
838
|
+
|
839
|
+
it 'should raise an argument error if the members() method receives an unrecognized symbol' do
|
840
|
+
the_following_code {
|
841
|
+
mock_call.queue('foobarz').agents(:cached => true) # common typo
|
842
|
+
}.should raise_error ArgumentError
|
843
|
+
end
|
844
|
+
|
845
|
+
it 'when fetching agents, it should properly split by the supported delimiters' do
|
846
|
+
queue_name = "doesnt_matter"
|
847
|
+
mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2')
|
848
|
+
mock_call.queue(queue_name).agents(:cache => true).to_a.size.should be 3
|
849
|
+
end
|
850
|
+
|
851
|
+
it 'when fetching agents, each array index should be an instance of AgentProxy' do
|
852
|
+
queue_name = 'doesnt_matter'
|
853
|
+
mock_call.should_receive(:get_variable).with("QUEUE_MEMBER_LIST(#{queue_name})").and_return('Agent/007,Agent/003,Zap/2')
|
854
|
+
agents = mock_call.queue(queue_name).agents(:cache => true).to_a
|
855
|
+
agents.size.should > 0
|
856
|
+
agents.each do |agent|
|
857
|
+
agent.should be_a_kind_of Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
861
|
+
it 'should properly retrieve metadata for an AgentProxy instance' do
|
862
|
+
agent_id, metadata_name = '22', 'status'
|
863
|
+
mock_env = flexmock "a mock ExecutionEnvironment"
|
864
|
+
mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter"
|
865
|
+
mock_env.should_receive(:variable).once.with("AGENT(#{agent_id}:#{metadata_name})")
|
866
|
+
agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{agent_id}", mock_queue)
|
867
|
+
agent.send(:agent_metadata, metadata_name)
|
868
|
+
end
|
869
|
+
|
870
|
+
it 'AgentProxy#logged_in? should return true if the "state" of an agent == LOGGEDIN' do
|
871
|
+
mock_env = flexmock "a mock ExecutionEnvironment"
|
872
|
+
mock_queue = flexmock "a queue that references our mock ExecutionEnvironment", :environment => mock_env, :name => "doesntmatter"
|
873
|
+
|
874
|
+
agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new('Agent/123', mock_queue)
|
875
|
+
flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDIN'
|
876
|
+
agent.logged_in?.should be true
|
877
|
+
|
878
|
+
flexmock(agent).should_receive(:agent_metadata).once.with('status').and_return 'LOGGEDOUT'
|
879
|
+
agent.logged_in?.should_not be true
|
880
|
+
end
|
881
|
+
|
882
|
+
it 'the AgentProxy should populate its own "id" property to the numerical ID of the "interface" with which it was constructed' do
|
883
|
+
mock_queue = flexmock :name => "doesntmatter"
|
884
|
+
id = '123'
|
885
|
+
|
886
|
+
agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new("Agent/#{id}", mock_queue)
|
887
|
+
agent.id.should == id
|
888
|
+
|
889
|
+
agent = Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy.new(id, mock_queue)
|
890
|
+
agent.id.should == id
|
891
|
+
end
|
892
|
+
|
893
|
+
it 'QueueAgentsListProxy#<<() should new the channel driver given as the argument to the system' do
|
894
|
+
queue_name, agent_channel = "metasyntacticvariablesftw", "Agent/123"
|
895
|
+
mock_call.should_receive('execute').once.with("AddQueueMember", queue_name, agent_channel, "", "", "", "")
|
896
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
897
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
898
|
+
mock_call.queue(queue_name).agents.new agent_channel
|
899
|
+
end
|
900
|
+
|
901
|
+
it 'when a queue agent is dynamically added and the queue does not exist, a QueueDoesNotExistError should be raised' do
|
902
|
+
does_not_read_data_back
|
903
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('NOSUCHQUEUE')
|
904
|
+
the_following_code {
|
905
|
+
mock_call.queue('this_should_not_exist').agents.new 'Agent/911'
|
906
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
|
907
|
+
end
|
908
|
+
|
909
|
+
it 'when a queue agent is dynamiaclly added and the adding was successful, an AgentProxy should be returned' do
|
910
|
+
mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("ADDED")
|
911
|
+
mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "")
|
912
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lalala)").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
913
|
+
return_value = mock_call.queue('lalala').agents.new "Agent/007"
|
914
|
+
return_value.kind_of?(Adhearsion::VoIP::Asterisk::Commands::QueueProxy::AgentProxy).should be true
|
915
|
+
end
|
916
|
+
|
917
|
+
it 'when a queue agent is dynamiaclly added and the adding was unsuccessful, a false should be returned' do
|
918
|
+
mock_call.should_receive(:get_variable).once.with("AQMSTATUS").and_return("MEMBERALREADY")
|
919
|
+
mock_call.should_receive(:execute).once.with("AddQueueMember", "lalala", "Agent/007", "", "", "", "")
|
920
|
+
return_value = mock_call.queue('lalala').agents.new "Agent/007"
|
921
|
+
return_value.should be false
|
922
|
+
end
|
923
|
+
|
924
|
+
it 'should raise an argument when an unrecognized key is given to add()' do
|
925
|
+
the_following_code {
|
926
|
+
mock_call.queue('q').agents.new :foo => "bar"
|
927
|
+
}.should raise_error ArgumentError
|
928
|
+
end
|
929
|
+
|
930
|
+
it 'should execute AddQueueMember with the penalty properly' do
|
931
|
+
queue_name = 'name_does_not_matter'
|
932
|
+
mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', 10, '', '','')
|
933
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
934
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
935
|
+
mock_call.queue(queue_name).agents.new 'Agent/007', :penalty => 10
|
936
|
+
end
|
937
|
+
|
938
|
+
it 'should execute AddQueueMember with the state_interface properly' do
|
939
|
+
queue_name = 'name_does_not_matter'
|
940
|
+
mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agent/007', '', '', '','SIP/2302')
|
941
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
942
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
943
|
+
mock_call.queue(queue_name).agents.new 'Agent/007', :state_interface => 'SIP/2302'
|
944
|
+
end
|
945
|
+
|
946
|
+
it 'should execute AddQueueMember properly when the name is given' do
|
947
|
+
queue_name, agent_name = 'name_does_not_matter', 'Jay Phillips'
|
948
|
+
mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, 'Agents/007', '', '', agent_name,'')
|
949
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
950
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
951
|
+
mock_call.queue(queue_name).agents.new 'Agents/007', :name => agent_name
|
952
|
+
end
|
953
|
+
|
954
|
+
it 'should execute AddQueueMember properly when the name, penalty, and interface is given' do
|
955
|
+
queue_name, agent_name, interface, penalty = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4
|
956
|
+
mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name,'')
|
957
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
958
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
959
|
+
mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty
|
960
|
+
end
|
961
|
+
|
962
|
+
it 'should execute AddQueueMember properly when the name, penalty, interface, and state_interface is given' do
|
963
|
+
queue_name, agent_name, interface, penalty, state_interface = 'name_does_not_matter', 'Jay Phillips', 'Agent/007', 4, 'SIP/2302'
|
964
|
+
mock_call.should_receive(:execute).once.with('AddQueueMember', queue_name, interface, penalty, '', agent_name, state_interface)
|
965
|
+
mock_call.should_receive(:get_variable).once.with('AQMSTATUS').and_return('ADDED')
|
966
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/007,SIP/2302,Local/2510@from-internal"
|
967
|
+
mock_call.queue(queue_name).agents.new interface, :name => agent_name, :penalty => penalty, :state_interface => state_interface
|
968
|
+
end
|
969
|
+
|
970
|
+
it 'should return a correct boolean for exists?()' do
|
971
|
+
mock_call.should_receive(:execute).once.with("RemoveQueueMember", "kablamm", "SIP/AdhearsionQueueExistenceCheck")
|
972
|
+
mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE"
|
973
|
+
mock_call.queue("kablamm").exists?.should be true
|
974
|
+
|
975
|
+
mock_call.should_receive(:execute).once.with("RemoveQueueMember", "monkey", "SIP/AdhearsionQueueExistenceCheck")
|
976
|
+
mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE"
|
977
|
+
mock_call.queue("monkey").exists?.should be false
|
978
|
+
end
|
979
|
+
|
980
|
+
it 'should pause an agent properly from a certain queue' do
|
981
|
+
does_not_read_data_back
|
982
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008"
|
983
|
+
mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED"
|
984
|
+
|
985
|
+
agents = mock_call.queue('lolcats').agents :cache => true
|
986
|
+
agents.last.pause!.should be true
|
987
|
+
end
|
988
|
+
|
989
|
+
it 'should pause an agent properly from a certain queue and return false when the agent did not exist' do
|
990
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(lolcats)").and_return "Agent/007,Agent/008"
|
991
|
+
mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "NOTFOUND"
|
992
|
+
mock_call.should_receive(:execute).once.with("PauseQueueMember", 'lolcats', "Agent/008")
|
993
|
+
|
994
|
+
agents = mock_call.queue('lolcats').agents :cache => true
|
995
|
+
agents.last.pause!.should be false
|
996
|
+
end
|
997
|
+
|
998
|
+
it 'should pause an agent globally properly' do
|
999
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(family)").and_return "Agent/Jay"
|
1000
|
+
mock_call.should_receive(:get_variable).once.with("PQMSTATUS").and_return "PAUSED"
|
1001
|
+
mock_call.should_receive(:execute).once.with("PauseQueueMember", nil, "Agent/Jay")
|
1002
|
+
|
1003
|
+
mock_call.queue('family').agents.first.pause! :everywhere => true
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
it 'should unpause an agent properly' do
|
1007
|
+
queue_name = "name with spaces"
|
1008
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(#{queue_name})").and_return "Agent/Jay"
|
1009
|
+
mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED"
|
1010
|
+
mock_call.should_receive(:execute).once.with("UnpauseQueueMember", queue_name, "Agent/Jay")
|
1011
|
+
|
1012
|
+
mock_call.queue(queue_name).agents.first.unpause!.should be true
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
it 'should unpause an agent globally properly' do
|
1016
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
|
1017
|
+
mock_call.should_receive(:get_variable).once.with("UPQMSTATUS").and_return "UNPAUSED"
|
1018
|
+
mock_call.should_receive(:execute).once.with("UnpauseQueueMember", nil, "Agent/Tom")
|
1019
|
+
|
1020
|
+
mock_call.queue('FOO').agents.first.unpause!(:everywhere => true).should be true
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
it 'waiting_count for a queue that does exist' do
|
1024
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_WAITING_COUNT(q)").and_return "50"
|
1025
|
+
flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return true
|
1026
|
+
mock_call.queue('q').waiting_count.should be 50
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
it 'waiting_count for a queue that does not exist' do
|
1030
|
+
the_following_code {
|
1031
|
+
flexmock(mock_call.queue('q')).should_receive(:exists?).once.and_return false
|
1032
|
+
mock_call.queue('q').waiting_count
|
1033
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
it 'empty? should call waiting_count' do
|
1037
|
+
queue = mock_call.queue 'testing_empty'
|
1038
|
+
flexmock(queue).should_receive(:waiting_count).once.and_return 0
|
1039
|
+
queue.empty?.should be true
|
1040
|
+
|
1041
|
+
queue = mock_call.queue 'testing_empty'
|
1042
|
+
flexmock(queue).should_receive(:waiting_count).once.and_return 99
|
1043
|
+
queue.empty?.should_not be true
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
it 'any? should call waiting_count' do
|
1047
|
+
queue = mock_call.queue 'testing_empty'
|
1048
|
+
flexmock(queue).should_receive(:waiting_count).once.and_return 0
|
1049
|
+
queue.any?.should be false
|
1050
|
+
|
1051
|
+
queue = mock_call.queue 'testing_empty'
|
1052
|
+
flexmock(queue).should_receive(:waiting_count).once.and_return 99
|
1053
|
+
queue.any?.should be true
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
it 'should remove an agent properly' do
|
1057
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
|
1058
|
+
mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom')
|
1059
|
+
mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "REMOVED"
|
1060
|
+
mock_call.queue('FOO').agents.first.remove!.should be true
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
it 'should remove an agent properly' do
|
1064
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(FOO)").and_return "Agent/Tom"
|
1065
|
+
mock_call.should_receive(:execute).once.with('RemoveQueueMember', 'FOO', 'Agent/Tom')
|
1066
|
+
mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOTINQUEUE"
|
1067
|
+
mock_call.queue('FOO').agents.first.remove!.should be false
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
it "should raise a QueueDoesNotExistError when removing an agent from a queue that doesn't exist" do
|
1071
|
+
mock_call.should_receive(:get_variable).once.with("QUEUE_MEMBER_LIST(cool_people)").and_return "Agent/ZeroCool"
|
1072
|
+
mock_call.should_receive(:execute).once.with("RemoveQueueMember", "cool_people", "Agent/ZeroCool")
|
1073
|
+
mock_call.should_receive(:get_variable).once.with("RQMSTATUS").and_return "NOSUCHQUEUE"
|
1074
|
+
the_following_code {
|
1075
|
+
mock_call.queue("cool_people").agents.first.remove!
|
1076
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::Commands::QueueProxy::QueueDoesNotExistError
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
it "should log an agent in properly with no agent id given" do
|
1080
|
+
mock_call.should_receive(:execute).once.with('AgentLogin', nil, 's')
|
1081
|
+
mock_call.queue('barrel_o_agents').agents.login!
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
it 'should remove "Agent/" before the agent ID given if necessary when logging an agent in' do
|
1085
|
+
mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's')
|
1086
|
+
mock_call.queue('barrel_o_agents').agents.login! 'Agent/007'
|
1087
|
+
|
1088
|
+
mock_call.should_receive(:execute).once.with('AgentLogin', '007', 's')
|
1089
|
+
mock_call.queue('barrel_o_agents').agents.login! '007'
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
it 'should add an agent silently properly' do
|
1093
|
+
mock_call.should_receive(:execute).once.with('AgentLogin', '007', '')
|
1094
|
+
mock_call.queue('barrel_o_agents').agents.login! 'Agent/007', :silent => false
|
1095
|
+
|
1096
|
+
mock_call.should_receive(:execute).once.with('AgentLogin', '008', 's')
|
1097
|
+
mock_call.queue('barrel_o_agents').agents.login! 'Agent/008', :silent => true
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
it 'logging an agent in should raise an ArgumentError is unrecognized arguments are given' do
|
1101
|
+
the_following_code {
|
1102
|
+
mock_call.queue('ohai').agents.login! 1,2,3,4,5
|
1103
|
+
}.should raise_error ArgumentError
|
1104
|
+
|
1105
|
+
the_following_code {
|
1106
|
+
mock_call.queue('lols').agents.login! 1337, :sssssilent => false
|
1107
|
+
}.should raise_error ArgumentError
|
1108
|
+
|
1109
|
+
the_following_code {
|
1110
|
+
mock_call.queue('qwerty').agents.login! 777, 6,5,4,3,2,1, :wee => :wee
|
1111
|
+
}.should raise_error ArgumentError
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
describe 'the menu() method' do
|
1117
|
+
|
1118
|
+
include DialplanCommandTestHelpers
|
1119
|
+
|
1120
|
+
it "should instantiate a new Menu object with only the Hash given as menu() options" do
|
1121
|
+
args = [1,2,3,4,5, {:timeout => 1.year, :tries => (1.0/0.0)}]
|
1122
|
+
|
1123
|
+
flexmock(Adhearsion::VoIP::Menu).should_receive(:new).once.
|
1124
|
+
with(args.last).and_throw(:instantiating_menu!)
|
1125
|
+
|
1126
|
+
should_throw(:instantiating_menu!) { mock_call.menu(*args) }
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
it "should jump to a context when a timeout is encountered and there is at least one exact match" do
|
1130
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1131
|
+
pbx_should_respond_with_successful_background_response ?4.ord
|
1132
|
+
pbx_should_respond_with_a_wait_for_digit_timeout
|
1133
|
+
|
1134
|
+
context_named_main = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :inside_main! }
|
1135
|
+
context_named_other = Adhearsion::DialPlan::DialplanContextProc.new(:other) { throw :inside_other! }
|
1136
|
+
flexmock(mock_call).should_receive(:main).once.and_return(context_named_main)
|
1137
|
+
flexmock(mock_call).should_receive(:other).never
|
1138
|
+
|
1139
|
+
should_pass_control_to_a_context_that_throws :inside_main! do
|
1140
|
+
mock_call.menu do |link|
|
1141
|
+
link.main 54
|
1142
|
+
link.other 543
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
it "when the 'extension' variable is changed, it should be an instance of PhoneNumber" do
|
1148
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1149
|
+
foobar_context = Adhearsion::DialPlan::DialplanContextProc.new(:foobar) { throw :foobar! }
|
1150
|
+
mock_call.should_receive(:foobar).once.and_return foobar_context
|
1151
|
+
should_pass_control_to_a_context_that_throws :foobar! do
|
1152
|
+
mock_call.menu do |link|
|
1153
|
+
link.foobar 5
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
5.should === mock_call.extension
|
1157
|
+
mock_call.extension.__real_string.should == "5"
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
describe 'the Menu class' do
|
1163
|
+
|
1164
|
+
include DialplanCommandTestHelpers
|
1165
|
+
|
1166
|
+
it "should yield a MenuBuilder when instantiated" do
|
1167
|
+
lambda {
|
1168
|
+
Adhearsion::VoIP::Menu.new do |block_argument|
|
1169
|
+
block_argument.should be_a_kind_of Adhearsion::VoIP::MenuBuilder
|
1170
|
+
throw :inside_block
|
1171
|
+
end
|
1172
|
+
}.should throw_symbol :inside_block
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
it "should invoke wait_for_digit instead of interruptible_play when no sound files are given" do
|
1176
|
+
mock_call.should_receive(:wait_for_digit).once.with(5).and_return '#'
|
1177
|
+
mock_call.menu { |link| link.does_not_match 3 }
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
it 'should invoke interruptible_play when sound files are given only for the first digit' do
|
1181
|
+
sound_files = %w[i like big butts and i cannot lie]
|
1182
|
+
timeout = 1337
|
1183
|
+
|
1184
|
+
mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
|
1185
|
+
mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil
|
1186
|
+
|
1187
|
+
mock_call.menu(sound_files, :timeout => timeout) { |link| link.qwerty 12345 }
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
it 'if the call to interruptible_play receives a timeout, it should execute wait_for_digit with the timeout given' do
|
1191
|
+
sound_files = %w[i like big butts and i cannot lie]
|
1192
|
+
timeout = 987
|
1193
|
+
|
1194
|
+
mock_call.should_receive(:interruptible_play).once.with(*sound_files).and_return nil
|
1195
|
+
mock_call.should_receive(:wait_for_digit).with(timeout).and_return
|
1196
|
+
|
1197
|
+
mock_call.menu(sound_files, :timeout => timeout) { |link| link.foobar 911 }
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
it "should work when no files are given to be played and a timeout is reached on the first digit" do
|
1201
|
+
timeout = 12
|
1202
|
+
[:on_premature_timeout, :on_failure].each do |usage_case|
|
1203
|
+
should_throw :got_here! do
|
1204
|
+
mock_call.should_receive(:wait_for_digit).once.with(timeout).and_return nil # Simulates timeout
|
1205
|
+
mock_call.menu :timeout => timeout do |link|
|
1206
|
+
link.foobar 0
|
1207
|
+
link.__send__(usage_case) { throw :got_here! }
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
it "should default the timeout to five seconds" do
|
1214
|
+
pbx_should_respond_with_successful_background_response ?2.ord
|
1215
|
+
pbx_should_respond_with_a_wait_for_digit_timeout
|
1216
|
+
|
1217
|
+
mock_call.should_receive(:wait_for_digit).once.with(5).and_return nil
|
1218
|
+
mock_call.menu { |link| link.foobar 22 }
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
it "when matches fail due to timeouts, the menu should repeat :tries times" do
|
1222
|
+
tries, times_timed_out = 10, 0
|
1223
|
+
|
1224
|
+
tries.times do
|
1225
|
+
pbx_should_respond_with_successful_background_response ?4.ord
|
1226
|
+
pbx_should_respond_with_successful_background_response ?0.ord
|
1227
|
+
pbx_should_respond_with_a_wait_for_digit_timeout
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
should_throw :inside_failure_callback do
|
1231
|
+
mock_call.menu :tries => tries do |link|
|
1232
|
+
link.pattern_longer_than_our_test_input 400
|
1233
|
+
link.on_premature_timeout { times_timed_out += 1 }
|
1234
|
+
link.on_invalid { raise "should never get here!" }
|
1235
|
+
link.on_failure { throw :inside_failure_callback }
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
times_timed_out.should be tries
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
it "when matches fail due to invalid input, the menu should repeat :tries times" do
|
1242
|
+
tries = 10
|
1243
|
+
times_invalid = 0
|
1244
|
+
|
1245
|
+
tries.times do
|
1246
|
+
pbx_should_respond_with_successful_background_response ?0.ord
|
1247
|
+
end
|
1248
|
+
|
1249
|
+
should_throw :inside_failure_callback do
|
1250
|
+
mock_call.menu :tries => tries do |link|
|
1251
|
+
link.be_leet 1337
|
1252
|
+
link.on_premature_timeout { raise "should never get here!" }
|
1253
|
+
link.on_invalid { times_invalid += 1 }
|
1254
|
+
link.on_failure { throw :inside_failure_callback }
|
1255
|
+
end
|
1256
|
+
end
|
1257
|
+
times_invalid.should be tries
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
it "invoke on_invalid callback when an invalid extension was entered" do
|
1261
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1262
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1263
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1264
|
+
should_throw :inside_invalid_callback do
|
1265
|
+
mock_call.menu do |link|
|
1266
|
+
link.onetwothree 123
|
1267
|
+
link.on_invalid { throw :inside_invalid_callback }
|
1268
|
+
end
|
1269
|
+
end
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
it "invoke on_premature_timeout when a timeout is encountered" do
|
1273
|
+
pbx_should_respond_with_successful_background_response ?9.ord
|
1274
|
+
pbx_should_respond_with_a_wait_for_digit_timeout
|
1275
|
+
|
1276
|
+
should_throw :inside_timeout do
|
1277
|
+
mock_call.menu :timeout => 1 do |link|
|
1278
|
+
link.something 999
|
1279
|
+
link.on_premature_timeout { throw :inside_timeout }
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
end
|
1283
|
+
|
1284
|
+
end
|
1285
|
+
|
1286
|
+
describe "the Menu class's high-level judgment" do
|
1287
|
+
|
1288
|
+
include DialplanCommandTestHelpers
|
1289
|
+
|
1290
|
+
it "should match things in ambiguous ranges properly" do
|
1291
|
+
pbx_should_respond_with_successful_background_response ?1.ord
|
1292
|
+
pbx_should_respond_with_successful_background_response ?1.ord
|
1293
|
+
pbx_should_respond_with_successful_background_response ?1.ord
|
1294
|
+
pbx_should_respond_with_a_wait_for_digit_timeout
|
1295
|
+
|
1296
|
+
main_context = Adhearsion::DialPlan::DialplanContextProc.new(:main) { throw :got_here! }
|
1297
|
+
mock_call.should_receive(:main).and_return main_context
|
1298
|
+
|
1299
|
+
should_pass_control_to_a_context_that_throws :got_here! do
|
1300
|
+
mock_call.menu do |link|
|
1301
|
+
link.blah 1
|
1302
|
+
link.main 11..11111
|
1303
|
+
end
|
1304
|
+
end
|
1305
|
+
111.should === mock_call.extension
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
it 'should match things in a range when there are many other non-matching patterns' do
|
1309
|
+
pbx_should_respond_with_successful_background_response ?9.ord
|
1310
|
+
pbx_should_respond_with_successful_background_response ?9.ord
|
1311
|
+
pbx_should_respond_with_successful_background_response ?5.ord
|
1312
|
+
|
1313
|
+
conferences_context = Adhearsion::DialPlan::DialplanContextProc.new(:conferences) { throw :got_here! }
|
1314
|
+
mock_call.should_receive(:conferences).and_return conferences_context
|
1315
|
+
|
1316
|
+
should_pass_control_to_a_context_that_throws :got_here! do
|
1317
|
+
mock_call.menu do |link|
|
1318
|
+
link.sales 1
|
1319
|
+
link.tech_support 2
|
1320
|
+
link.finance 3
|
1321
|
+
link.conferences 900..999
|
1322
|
+
end
|
1323
|
+
end
|
1324
|
+
end
|
1325
|
+
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
describe 'the MenuBuilder' do
|
1329
|
+
|
1330
|
+
include MenuBuilderTestHelper
|
1331
|
+
|
1332
|
+
attr_reader :builder
|
1333
|
+
before(:each) do
|
1334
|
+
@builder = Adhearsion::VoIP::MenuBuilder.new
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
it "should convert each pattern given to it into a MatchCalculator instance" do
|
1338
|
+
builder.tap do |link|
|
1339
|
+
link.foo 1,2,3
|
1340
|
+
link.bar "4", "5", 6
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
builder.weighted_match_calculators.size.should be 6
|
1344
|
+
builder.weighted_match_calculators.each do |match_calculator|
|
1345
|
+
match_calculator.should be_a_kind_of Adhearsion::VoIP::MatchCalculator
|
1346
|
+
end
|
1347
|
+
end
|
1348
|
+
|
1349
|
+
it "conflicting ranges" do
|
1350
|
+
builder.tap do |link|
|
1351
|
+
link.hundreds 100...200
|
1352
|
+
link.thousands 1_000...2_000
|
1353
|
+
link.tenthousands 10_000...20_000
|
1354
|
+
end
|
1355
|
+
|
1356
|
+
builder_should_match_with_these_quantities_of_calculated_matches \
|
1357
|
+
1 => { :exact_match_count => 0, :potential_match_count => 11100 },
|
1358
|
+
10 => { :exact_match_count => 0, :potential_match_count => 1110 },
|
1359
|
+
100 => { :exact_match_count => 1, :potential_match_count => 110 },
|
1360
|
+
1_000 => { :exact_match_count => 1, :potential_match_count => 10 },
|
1361
|
+
10_000 => { :exact_match_count => 1, :potential_match_count => 0 },
|
1362
|
+
100_000 => { :exact_match_count => 0, :potential_match_count => 0 }
|
1363
|
+
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
it 'a String query ran against multiple Numeric patterns and a range' do
|
1367
|
+
builder.tap do |link|
|
1368
|
+
link.sales 1
|
1369
|
+
link.tech_support 2
|
1370
|
+
link.finance 3
|
1371
|
+
link.conferences 900..999
|
1372
|
+
end
|
1373
|
+
match = builder.calculate_matches_for "995"
|
1374
|
+
require 'pp'
|
1375
|
+
# pp match
|
1376
|
+
match.potential_match?.should_not be true
|
1377
|
+
match.exact_match?.should be true
|
1378
|
+
match.actual_exact_matches.should == ["995"]
|
1379
|
+
end
|
1380
|
+
|
1381
|
+
it "multiple patterns given at once" do
|
1382
|
+
builder.tap do |link|
|
1383
|
+
link.multiple_patterns 1,2,3,4,5,6,7,8,9
|
1384
|
+
link.multiple_patterns 100..199, 200..299, 300..399, 400..499, 500..599,
|
1385
|
+
600..699, 700..799, 800..899, 900..999
|
1386
|
+
end
|
1387
|
+
1.upto 9 do |num|
|
1388
|
+
builder.calculate_matches_for(num).tap do |matches_of_num|
|
1389
|
+
matches_of_num.potential_match_count.should be 100
|
1390
|
+
matches_of_num.exact_match_count.should be 1
|
1391
|
+
end
|
1392
|
+
builder.calculate_matches_for((num * 100) + 5).tap do |matches_of_num|
|
1393
|
+
matches_of_num.potential_match_count.should be 0
|
1394
|
+
matches_of_num.exact_match_count.should be 1
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
it "numeric literals that don't match but ultimately would" do
|
1400
|
+
builder.tap do |link|
|
1401
|
+
link.nineninenine 999
|
1402
|
+
link.shouldnt_match 4444
|
1403
|
+
end
|
1404
|
+
builder.calculate_matches_for(9).potential_match_count.should be 1
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
it "three fixnums that obviously don't conflict" do
|
1408
|
+
builder.tap do |link|
|
1409
|
+
link.one 1
|
1410
|
+
link.two 2
|
1411
|
+
link.three 3
|
1412
|
+
end
|
1413
|
+
[[1,2,3,4,'#'], [1,1,1,0,0]].transpose.each do |(input,expected_matches)|
|
1414
|
+
matches = builder.calculate_matches_for input
|
1415
|
+
matches.exact_match_count.should be expected_matches
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
|
1419
|
+
it "numerical digits mixed with special digits" do
|
1420
|
+
builder.tap do |link|
|
1421
|
+
link.one '5*11#3'
|
1422
|
+
link.two '5***'
|
1423
|
+
link.three '###'
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
builder_should_match_with_these_quantities_of_calculated_matches \
|
1427
|
+
'5' => { :potential_match_count => 2, :exact_match_count => 0 },
|
1428
|
+
'*' => { :potential_match_count => 0, :exact_match_count => 0 },
|
1429
|
+
'5**' => { :potential_match_count => 1, :exact_match_count => 0 },
|
1430
|
+
'5*1' => { :potential_match_count => 1, :exact_match_count => 0 },
|
1431
|
+
'5*11#3' => { :potential_match_count => 0, :exact_match_count => 1 },
|
1432
|
+
'5*11#4' => { :potential_match_count => 0, :exact_match_count => 0 },
|
1433
|
+
'5***' => { :potential_match_count => 0, :exact_match_count => 1 },
|
1434
|
+
'###' => { :potential_match_count => 0, :exact_match_count => 1 },
|
1435
|
+
'##*' => { :potential_match_count => 0, :exact_match_count => 0 }
|
1436
|
+
|
1437
|
+
end
|
1438
|
+
|
1439
|
+
it 'a Fixnum exact match conflicting with a Range that would ultimately match' do
|
1440
|
+
builder.tap do |link|
|
1441
|
+
link.single_digit 1
|
1442
|
+
link.range 100..200
|
1443
|
+
end
|
1444
|
+
matches = builder.calculate_matches_for 1
|
1445
|
+
matches.potential_match_count.should be 100
|
1446
|
+
end
|
1447
|
+
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
describe 'say_digits command' do
|
1451
|
+
include DialplanCommandTestHelpers
|
1452
|
+
it 'Can execute the saydigits application using say_digits' do
|
1453
|
+
digits = "12345"
|
1454
|
+
pbx_should_respond_with_success
|
1455
|
+
mock_call.say_digits digits
|
1456
|
+
pbx_was_asked_to_execute "saydigits", digits
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
it 'Digits that include pound, star, and minus are considered valid' do
|
1460
|
+
digits = "1#2*3-4"
|
1461
|
+
mock_call.should_receive(:execute).once.with("saydigits", digits)
|
1462
|
+
mock_call.say_digits digits
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
it 'Cannot pass non-integers into say_digits. Will raise an ArgumentError' do
|
1466
|
+
the_following_code {
|
1467
|
+
mock_call.say_digits 'abc'
|
1468
|
+
}.should raise_error(ArgumentError)
|
1469
|
+
|
1470
|
+
the_following_code {
|
1471
|
+
mock_call.say_digits '1.20'
|
1472
|
+
}.should raise_error(ArgumentError)
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
it 'Digits that start with a 0 are considered valid and parsed properly' do
|
1476
|
+
digits = "0123"
|
1477
|
+
mock_call.should_receive(:execute).once.with("saydigits", digits)
|
1478
|
+
mock_call.say_digits digits
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
describe 'the enable_feature command' do
|
1484
|
+
|
1485
|
+
include DialplanCommandTestHelpers
|
1486
|
+
|
1487
|
+
it 'it should fetch the variable for DYNAMIC_FEATURES at first' do
|
1488
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_throw :got_variable
|
1489
|
+
should_throw :got_variable do
|
1490
|
+
mock_call.enable_feature :foobar
|
1491
|
+
end
|
1492
|
+
end
|
1493
|
+
|
1494
|
+
it 'should check Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS mapping for configuration setters' do
|
1495
|
+
feature_name = :attended_transfer
|
1496
|
+
|
1497
|
+
assertion = lambda do |arg|
|
1498
|
+
arg.should == :this_is_the_right_arg
|
1499
|
+
throw :inside_assertion!
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
# I had to do this ugly hack because of a bug in Flexmock which prevented me from mocking out Hash#[] :(
|
1503
|
+
old_hash_feature_extension = Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name]
|
1504
|
+
begin
|
1505
|
+
Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = assertion
|
1506
|
+
# </uglyhack>
|
1507
|
+
should_throw :inside_assertion! do
|
1508
|
+
mock_call.enable_feature(feature_name, :this_is_the_right_arg)
|
1509
|
+
end
|
1510
|
+
ensure
|
1511
|
+
Adhearsion::VoIP::Asterisk::Commands::DYNAMIC_FEATURE_EXTENSIONS[feature_name] = old_hash_feature_extension
|
1512
|
+
end
|
1513
|
+
end
|
1514
|
+
|
1515
|
+
it 'should separate enabled features with a "#"' do
|
1516
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return("one")
|
1517
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'one#bar')
|
1518
|
+
mock_call.enable_feature "bar"
|
1519
|
+
end
|
1520
|
+
|
1521
|
+
it 'should not add duplicate enabled dynamic features' do
|
1522
|
+
mock_call.should_receive(:variable).once.and_return('eins#zwei')
|
1523
|
+
mock_call.enable_feature "eins"
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
it 'should raise an ArgumentError if optional options are given when DYNAMIC_FEATURE_EXTENSIONS does not have a key for the feature name' do
|
1527
|
+
the_following_code {
|
1528
|
+
mock_call.enable_feature :this_is_not_recognized, :these_features => "are not going to be recognized"
|
1529
|
+
}.should raise_error ArgumentError
|
1530
|
+
end
|
1531
|
+
|
1532
|
+
it 'enabling :attended_transfer should actually enable the atxfer feature' do
|
1533
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return ''
|
1534
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer')
|
1535
|
+
mock_call.enable_feature :attended_transfer
|
1536
|
+
end
|
1537
|
+
|
1538
|
+
it 'the :context optional option when enabling :attended_transfer should set the TRANSFER_CONTEXT variable to the String supplied as a Hash value' do
|
1539
|
+
context_name = "direct_dial"
|
1540
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES").and_return ''
|
1541
|
+
mock_call.should_receive(:variable).once.with("DYNAMIC_FEATURES" => 'atxfer')
|
1542
|
+
mock_call.should_receive(:variable).once.with("TRANSFER_CONTEXT" => context_name)
|
1543
|
+
mock_call.enable_feature :attended_transfer, :context => context_name
|
1544
|
+
end
|
1545
|
+
|
1546
|
+
it 'enabling :attended_transfer should not add a duplicate if atxfer has been enabled, but it should still set the TRANSFER_CONTEXT variable' do
|
1547
|
+
context_name = 'blah'
|
1548
|
+
mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer'
|
1549
|
+
mock_call.should_receive(:variable).once.with('TRANSFER_CONTEXT' => context_name)
|
1550
|
+
mock_call.enable_feature :attended_transfer, :context => context_name
|
1551
|
+
end
|
1552
|
+
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
describe 'the disable_feature command' do
|
1556
|
+
|
1557
|
+
include DialplanCommandTestHelpers
|
1558
|
+
|
1559
|
+
it "should properly remove the feature from the DYNAMIC_FEATURES variable" do
|
1560
|
+
mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'foobar#qaz'
|
1561
|
+
mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES' => 'qaz')
|
1562
|
+
mock_call.disable_feature "foobar"
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
it "should not re-set the variable if the feature wasn't enabled in the first place" do
|
1566
|
+
mock_call.should_receive(:variable).once.with('DYNAMIC_FEATURES').and_return 'atxfer'
|
1567
|
+
mock_call.should_receive(:variable).never
|
1568
|
+
mock_call.disable_feature "jay"
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
describe 'jump_to command' do
|
1574
|
+
|
1575
|
+
include DialplanCommandTestHelpers
|
1576
|
+
|
1577
|
+
it 'when given a DialplanContextProc as the only argument, it should raise a ControlPassingException with that as the target' do
|
1578
|
+
# Having to do this ugly hack because I can't do anything with the exception once I set up an expectation with it normally.
|
1579
|
+
dialplan_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {}
|
1580
|
+
should_throw :finishing_the_rescue_block do
|
1581
|
+
begin
|
1582
|
+
mock_call.jump_to dialplan_context
|
1583
|
+
rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException => cpe
|
1584
|
+
cpe.target.should be dialplan_context
|
1585
|
+
throw :finishing_the_rescue_block
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
it 'when given a String, it should perform a lookup of the context name' do
|
1591
|
+
context_name = 'cool_context'
|
1592
|
+
mock_call.should_receive(context_name).once.and_throw :found_context!
|
1593
|
+
should_throw :found_context! do
|
1594
|
+
mock_call.jump_to context_name
|
1595
|
+
end
|
1596
|
+
end
|
1597
|
+
|
1598
|
+
it 'when given a Symbol, it should perform a lookup of the context name' do
|
1599
|
+
context_name = :cool_context
|
1600
|
+
mock_call.should_receive(context_name).once.and_throw :found_context!
|
1601
|
+
should_throw :found_context! do
|
1602
|
+
mock_call.jump_to context_name
|
1603
|
+
end
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
it "a clearly invalid context name should raise a ContextNotFoundException" do
|
1607
|
+
bad_context_name = ' ZOMGS this is A REALLY! STUPID context name . wtf were you thinking?!'
|
1608
|
+
the_following_code {
|
1609
|
+
mock_call.jump_to bad_context_name
|
1610
|
+
}.should raise_error Adhearsion::VoIP::DSL::Dialplan::ContextNotFoundException
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
it 'when given an :extension override, the new value should be boxed in a PhoneNumber' do
|
1614
|
+
my_context = Adhearsion::DialPlan::DialplanContextProc.new("my_context") {}
|
1615
|
+
begin
|
1616
|
+
mock_call.jump_to my_context, :extension => 1337
|
1617
|
+
rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException
|
1618
|
+
# Eating this exception
|
1619
|
+
end
|
1620
|
+
mock_call.extension.__real_num.should be 1337
|
1621
|
+
end
|
1622
|
+
|
1623
|
+
it 'other overrides should be simply metadef()d' do
|
1624
|
+
test_context = Adhearsion::DialPlan::DialplanContextProc.new("test_context") {}
|
1625
|
+
begin
|
1626
|
+
mock_call.jump_to test_context, :caller_id => 1_444_555_6666
|
1627
|
+
rescue Adhearsion::VoIP::DSL::Dialplan::ControlPassingException
|
1628
|
+
# Eating this exception
|
1629
|
+
end
|
1630
|
+
mock_call.caller_id.should == 1_444_555_6666
|
1631
|
+
end
|
1632
|
+
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
describe "get variable command" do
|
1636
|
+
include DialplanCommandTestHelpers
|
1637
|
+
|
1638
|
+
it "Getting a variable that isn't set returns nothing" do
|
1639
|
+
pbx_should_respond_with "200 result=0"
|
1640
|
+
mock_call.get_variable('OMGURFACE').should be nil
|
1641
|
+
end
|
1642
|
+
|
1643
|
+
it 'An empty variable should return an empty String' do
|
1644
|
+
pbx_should_respond_with_value ""
|
1645
|
+
mock_call.get_variable('kablamm').should == ""
|
1646
|
+
end
|
1647
|
+
|
1648
|
+
it "Getting a variable that is set returns its value" do
|
1649
|
+
unique_id = "1192470850.1"
|
1650
|
+
pbx_should_respond_with_value unique_id
|
1651
|
+
variable_value_returned = mock_call.get_variable('UNIQUEID')
|
1652
|
+
variable_value_returned.should == unique_id
|
1653
|
+
end
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
describe "duration_of command" do
|
1657
|
+
include DialplanCommandTestHelpers
|
1658
|
+
|
1659
|
+
it "Duration of must take a block" do
|
1660
|
+
the_following_code {
|
1661
|
+
mock_call.duration_of
|
1662
|
+
}.should raise_error(LocalJumpError)
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
it "Passed block to duration of is actually executed" do
|
1666
|
+
the_following_code {
|
1667
|
+
mock_call.duration_of {
|
1668
|
+
throw :inside_duration_of
|
1669
|
+
}
|
1670
|
+
}.should throw_symbol :inside_duration_of
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
it "Duration of block is returned" do
|
1674
|
+
start_time = Time.parse('9:25:00')
|
1675
|
+
end_time = Time.parse('9:25:05')
|
1676
|
+
expected_duration = end_time - start_time
|
1677
|
+
|
1678
|
+
flexmock(Time).should_receive(:now).twice.and_return(start_time, end_time)
|
1679
|
+
duration = mock_call.duration_of {
|
1680
|
+
# This doesn't matter
|
1681
|
+
}
|
1682
|
+
|
1683
|
+
duration.should == expected_duration
|
1684
|
+
end
|
1685
|
+
end
|
1686
|
+
|
1687
|
+
describe "Dial command" do
|
1688
|
+
include DialplanCommandTestHelpers
|
1689
|
+
|
1690
|
+
it "should set the caller id if the caller_id option is specified" do
|
1691
|
+
does_not_read_data_back
|
1692
|
+
mock_call.should_receive(:set_caller_id_number).once
|
1693
|
+
mock_call.dial 123, :caller_id => "1234678901"
|
1694
|
+
end
|
1695
|
+
|
1696
|
+
it 'should raise an exception when unknown hash key arguments are given to it' do
|
1697
|
+
the_following_code {
|
1698
|
+
mock_call.dial 123, :asjndfhasndfahsbdfbhasbdfhabsd => "asbdfhabshdfbajhshfbajsf"
|
1699
|
+
}.should raise_error ArgumentError
|
1700
|
+
end
|
1701
|
+
|
1702
|
+
it 'should set the caller ID name when given the :name hash key argument' do
|
1703
|
+
does_not_read_data_back
|
1704
|
+
name = "Jay Phillips"
|
1705
|
+
mock_call.should_receive(:set_caller_id_name).once.with(name)
|
1706
|
+
mock_call.dial "BlahBlahBlah", :name => name
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
it "should raise an exception when a non-numerical caller_id is specified" do
|
1710
|
+
the_following_code {
|
1711
|
+
mock_call.dial 911, :caller_id => "zomgz"
|
1712
|
+
}.should raise_error ArgumentError
|
1713
|
+
end
|
1714
|
+
|
1715
|
+
it 'should pass the value of the :confirm key to dial_macro_option_compiler()' do
|
1716
|
+
does_not_read_data_back
|
1717
|
+
value_of_confirm_key = {:play => "ohai", :timeout => 30}
|
1718
|
+
mock_call.should_receive(:dial_macro_option_compiler).once.with value_of_confirm_key
|
1719
|
+
mock_call.dial 123, :confirm => value_of_confirm_key
|
1720
|
+
end
|
1721
|
+
|
1722
|
+
it "should add the return value of dial_macro_option_compiler to the :options key's value given to the dial command" do
|
1723
|
+
channel = "SIP/1337"
|
1724
|
+
macro_arg = "THISSHOULDGETPASSEDTOASTERISK"
|
1725
|
+
timeout = 10
|
1726
|
+
options = 'hH'
|
1727
|
+
|
1728
|
+
mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg
|
1729
|
+
mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg)
|
1730
|
+
mock_call.dial channel, :for => timeout, :confirm => true, :options => options
|
1731
|
+
end
|
1732
|
+
|
1733
|
+
it 'should add the return value of dial_macro_option_compiler to the options field when NO :options are given' do
|
1734
|
+
channel = "SIP/1337"
|
1735
|
+
macro_arg = "THISSHOULDGETPASSEDTOASTERISK"
|
1736
|
+
timeout = 10
|
1737
|
+
options = 'hH'
|
1738
|
+
|
1739
|
+
mock_call.should_receive(:dial_macro_option_compiler).once.and_return macro_arg
|
1740
|
+
mock_call.should_receive(:execute).with('Dial', channel, timeout, options + macro_arg)
|
1741
|
+
mock_call.dial channel, :for => timeout, :confirm => true, :options => options
|
1742
|
+
end
|
1743
|
+
|
1744
|
+
end
|
1745
|
+
|
1746
|
+
describe "The Dial command's :confirm option setting builder" do
|
1747
|
+
|
1748
|
+
include DialplanCommandTestHelpers
|
1749
|
+
|
1750
|
+
attr_reader :formatter
|
1751
|
+
before :each do
|
1752
|
+
@formatter = mock_call.method :dial_macro_option_compiler
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
it 'should allow passing in the :confirm named argument with true' do
|
1756
|
+
the_following_code {
|
1757
|
+
formatter.call true
|
1758
|
+
}.should_not raise_error ArgumentError
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
it 'should separate :play options with "++"' do
|
1762
|
+
sound_files = *1..10
|
1763
|
+
formatter.call(:play => sound_files).should include sound_files.join('++')
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
it 'should raise an ArgumentError if an invalid Hash key is given' do
|
1767
|
+
the_following_code {
|
1768
|
+
formatter.call :this_symbol_is_not_valid => 123
|
1769
|
+
}.should raise_error ArgumentError
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
it "should raise an ArgumentError if the argument's class is not recognized" do
|
1773
|
+
the_following_code {
|
1774
|
+
formatter.call Time.now # Time is an example strange case
|
1775
|
+
}.should raise_error ArgumentError
|
1776
|
+
end
|
1777
|
+
|
1778
|
+
it 'should return the contents within a M() Dial argument' do
|
1779
|
+
formatter.call(true).should =~ /^M\(.+\)$/
|
1780
|
+
end
|
1781
|
+
|
1782
|
+
it 'should replace the default macro name when given the :macro options' do
|
1783
|
+
macro_name = "ivegotalovelybunchofcoconuts"
|
1784
|
+
formatter.call(:macro => macro_name).starts_with?("M(#{macro_name}").should be true
|
1785
|
+
end
|
1786
|
+
|
1787
|
+
it 'should allow a symbol macro name' do
|
1788
|
+
the_following_code {
|
1789
|
+
formatter.call(:macro => :foo)
|
1790
|
+
}.should_not raise_error ArgumentError
|
1791
|
+
end
|
1792
|
+
|
1793
|
+
it 'should only allow alphanumeric and underscores in the macro name' do
|
1794
|
+
bad_options = ["this has a space", "foo,bar", 'exists?', 'x^z', '', "!&@&*^!@"]
|
1795
|
+
bad_options.each do |bad_option|
|
1796
|
+
the_following_code {
|
1797
|
+
formatter.call(:macro => bad_option)
|
1798
|
+
}.should raise_error ArgumentError
|
1799
|
+
end
|
1800
|
+
end
|
1801
|
+
|
1802
|
+
it 'should confirm :timeout => :none to 0' do
|
1803
|
+
formatter.call(:timeout => :none).should include "timeout:0"
|
1804
|
+
end
|
1805
|
+
|
1806
|
+
it 'should separate the macro name and the arguments with a caret (^)' do
|
1807
|
+
formatter.call(:macro => "jay").should =~ /M\(jay\^.+/
|
1808
|
+
end
|
1809
|
+
|
1810
|
+
it 'should raise an ArgumentError if a caret existed anywhere in the resulting String' do
|
1811
|
+
# FIXME: Duplicate hash key
|
1812
|
+
bad_options = [{:play => "foo^bar", :key => "^", :play => ["hello-world", 'lol^cats']}]
|
1813
|
+
bad_options.each do |bad_option|
|
1814
|
+
the_following_code {
|
1815
|
+
formatter.call(bad_option)
|
1816
|
+
}.should raise_error ArgumentError
|
1817
|
+
end
|
1818
|
+
end
|
1819
|
+
|
1820
|
+
it 'should raise an ArgumentError if the :key is not [0-9#*]' do
|
1821
|
+
bad_options = %w[& A $ @ . )]
|
1822
|
+
bad_options.each do |bad_option|
|
1823
|
+
the_following_code {
|
1824
|
+
formatter.call :key => bad_option
|
1825
|
+
}.should raise_error ArgumentError
|
1826
|
+
end
|
1827
|
+
end
|
1828
|
+
|
1829
|
+
it 'should raise an ArgumentError if the key is longer than one digit' do
|
1830
|
+
the_following_code {
|
1831
|
+
formatter.call :key => "55"
|
1832
|
+
}.should raise_error ArgumentError
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
it 'should raise an ArgumentError if the timeout is not numeric and not :none' do
|
1836
|
+
bad_options = [:nonee, Time.now, method(:inspect)]
|
1837
|
+
bad_options.each do |bad_option|
|
1838
|
+
the_following_code {
|
1839
|
+
formatter.call bad_option
|
1840
|
+
}.should raise_error ArgumentError
|
1841
|
+
end
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
it 'should support passing a String argument as a timeout' do
|
1845
|
+
the_following_code {
|
1846
|
+
formatter.call :timeout => "123"
|
1847
|
+
}.should_not raise_error ArgumentError
|
1848
|
+
end
|
1849
|
+
|
1850
|
+
it 'should raise an ArgumentError if given a Float' do
|
1851
|
+
the_following_code {
|
1852
|
+
formatter.call :timeout => 100.0012
|
1853
|
+
}.should raise_error ArgumentError
|
1854
|
+
end
|
1855
|
+
|
1856
|
+
it 'should allow passing a ActiveSupport::Duration to :timeout' do
|
1857
|
+
the_following_code {
|
1858
|
+
formatter.call :timeout => 3.minutes
|
1859
|
+
}.should_not raise_error ArgumentError
|
1860
|
+
end
|
1861
|
+
|
1862
|
+
end
|
1863
|
+
|
1864
|
+
describe 'the dtmf command' do
|
1865
|
+
|
1866
|
+
include DialplanCommandTestHelpers
|
1867
|
+
|
1868
|
+
it 'should send the proper AGI command' do
|
1869
|
+
does_not_read_data_back
|
1870
|
+
digits = '8404#4*'
|
1871
|
+
mock_call.dtmf digits
|
1872
|
+
pbx_should_have_been_sent "EXEC SendDTMF \"#{digits}\""
|
1873
|
+
end
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
describe "the last_dial_status command and family" do
|
1877
|
+
|
1878
|
+
include DialplanCommandTestHelpers
|
1879
|
+
|
1880
|
+
it 'should convert common DIALSTATUS variables to their appropriate symbols' do
|
1881
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
|
1882
|
+
mock_call.last_dial_status.should be :answered
|
1883
|
+
|
1884
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CONGESTION')
|
1885
|
+
mock_call.last_dial_status.should be :congested
|
1886
|
+
|
1887
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("BUSY")
|
1888
|
+
mock_call.last_dial_status.should be :busy
|
1889
|
+
|
1890
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CANCEL")
|
1891
|
+
mock_call.last_dial_status.should be :cancelled
|
1892
|
+
|
1893
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("NOANSWER")
|
1894
|
+
mock_call.last_dial_status.should be :unanswered
|
1895
|
+
|
1896
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("CHANUNAVAIL")
|
1897
|
+
mock_call.last_dial_status.should be :channel_unavailable
|
1898
|
+
|
1899
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return("THISISNOTVALID")
|
1900
|
+
mock_call.last_dial_status.should be :unknown
|
1901
|
+
end
|
1902
|
+
|
1903
|
+
it 'last_dial_successful? should return true if last_dial_status == :answered' do
|
1904
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
|
1905
|
+
mock_call.last_dial_successful?.should be true
|
1906
|
+
|
1907
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL')
|
1908
|
+
mock_call.last_dial_successful?.should be false
|
1909
|
+
end
|
1910
|
+
|
1911
|
+
it 'last_dial_unsuccessful? should be the opposite of last_dial_successful?' do
|
1912
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('ANSWER')
|
1913
|
+
mock_call.last_dial_unsuccessful?.should be false
|
1914
|
+
|
1915
|
+
mock_call.should_receive(:variable).with("DIALSTATUS").once.and_return('CHANUNAVAIL')
|
1916
|
+
mock_call.last_dial_unsuccessful?.should be true
|
1917
|
+
end
|
1918
|
+
|
1919
|
+
it 'last_dial_status should not blow up if variable() returns nil. it should return :cancelled' do
|
1920
|
+
the_following_code {
|
1921
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil
|
1922
|
+
mock_call.last_dial_status.should be :cancelled
|
1923
|
+
|
1924
|
+
mock_call.should_receive(:variable).once.with("DIALSTATUS").and_return nil
|
1925
|
+
mock_call.last_dial_successful?.should be false
|
1926
|
+
}.should_not raise_error
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
describe "set_caller_id_number command" do
|
1932
|
+
include DialplanCommandTestHelpers
|
1933
|
+
|
1934
|
+
it "should encapsulate the number with quotes" do
|
1935
|
+
caller_id = "14445556666"
|
1936
|
+
mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(num)" "#{caller_id}")).and_return true
|
1937
|
+
mock_call.send(:set_caller_id_number, caller_id)
|
1938
|
+
end
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
describe 'set_caller_id_name command' do
|
1942
|
+
include DialplanCommandTestHelpers
|
1943
|
+
|
1944
|
+
it "should wrap the name in quotes" do
|
1945
|
+
name = "Jay Phillips"
|
1946
|
+
mock_call.should_receive(:raw_response).once.with(%(SET VARIABLE "CALLERID(name)" "#{name}")).and_return true
|
1947
|
+
mock_call.send(:set_caller_id_name, name)
|
1948
|
+
end
|
1949
|
+
end
|
1950
|
+
|
1951
|
+
describe "record command" do
|
1952
|
+
include DialplanCommandTestHelpers
|
1953
|
+
|
1954
|
+
it "executes the command SpeechEngine gives it based on the engine name" do
|
1955
|
+
mock_speak_command = "returned command doesn't matter"
|
1956
|
+
flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:cepstral).
|
1957
|
+
once.and_return(mock_speak_command)
|
1958
|
+
mock_call.should_receive(:execute).once.with(mock_speak_command)
|
1959
|
+
mock_call.speak "Spoken text doesn't matter", :cepstral
|
1960
|
+
end
|
1961
|
+
|
1962
|
+
it "raises an InvalidSpeechEngine exception when the engine is 'none'" do
|
1963
|
+
the_following_code {
|
1964
|
+
mock_call.speak("o hai!", :none)
|
1965
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
|
1966
|
+
end
|
1967
|
+
|
1968
|
+
it "should default its engine to :none" do
|
1969
|
+
the_following_code {
|
1970
|
+
flexmock(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines).should_receive(:none).once.
|
1971
|
+
and_raise(Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine)
|
1972
|
+
mock_call.speak "ruby ruby ruby ruby!"
|
1973
|
+
}.should raise_error Adhearsion::VoIP::Asterisk::Commands::SpeechEngines::InvalidSpeechEngine
|
1974
|
+
end
|
1975
|
+
|
1976
|
+
#it "Properly escapes spoken text" # TODO: What are the escaping needs?
|
1977
|
+
|
1978
|
+
end
|
1979
|
+
|
1980
|
+
describe 'The join command' do
|
1981
|
+
|
1982
|
+
include DialplanCommandTestHelpers
|
1983
|
+
|
1984
|
+
it "should pass the 'd' flag when no options are given" do
|
1985
|
+
conference_id = "123"
|
1986
|
+
mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "d", nil)
|
1987
|
+
mock_call.join conference_id
|
1988
|
+
end
|
1989
|
+
|
1990
|
+
it "should pass through any given flags with 'd' appended to it if necessary" do
|
1991
|
+
conference_id, flags = "1000", "zomgs"
|
1992
|
+
mock_call.should_receive(:execute).once.with("MeetMe", conference_id, flags + "d", nil)
|
1993
|
+
mock_call.join conference_id, :options => flags
|
1994
|
+
end
|
1995
|
+
|
1996
|
+
it "should NOT pass the 'd' flag when requiring static conferences" do
|
1997
|
+
conference_id, options = "1000", {:use_static_conf => true}
|
1998
|
+
mock_call.should_receive(:execute).once.with("MeetMe", conference_id, "", nil)
|
1999
|
+
mock_call.join conference_id, options
|
2000
|
+
end
|
2001
|
+
|
2002
|
+
it "should raise an ArgumentError when the pin is not numerical" do
|
2003
|
+
the_following_code {
|
2004
|
+
mock_call.should_receive(:execute).never
|
2005
|
+
mock_call.join 3333, :pin => "letters are bad, mkay?!1"
|
2006
|
+
}.should raise_error ArgumentError
|
2007
|
+
end
|
2008
|
+
|
2009
|
+
it "should strip out illegal characters from a conference name" do
|
2010
|
+
bizarre_conference_name = "a- bc!d&&e--`"
|
2011
|
+
normal_conference_name = "abcde"
|
2012
|
+
mock_call.should_receive(:execute).twice.with("MeetMe", normal_conference_name, "d", nil)
|
2013
|
+
|
2014
|
+
mock_call.join bizarre_conference_name
|
2015
|
+
mock_call.join normal_conference_name
|
2016
|
+
end
|
2017
|
+
|
2018
|
+
it "should allow textual conference names" do
|
2019
|
+
the_following_code {
|
2020
|
+
mock_call.should_receive(:execute).once.with_any_args
|
2021
|
+
mock_call.join "david bowie's pants"
|
2022
|
+
}.should_not raise_error
|
2023
|
+
end
|
2024
|
+
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
|
2028
|
+
describe 'the DialPlan::ConfirmationManager' do
|
2029
|
+
|
2030
|
+
include ConfirmationManagerTestHelper
|
2031
|
+
include DialplanCommandTestHelpers
|
2032
|
+
|
2033
|
+
attr_reader :example_encoded_hash, :example_encoded_hash_without_macro_name
|
2034
|
+
before :each do
|
2035
|
+
@example_encoded_hash_without_macro_name = 'timeout:20!play:foo-bar++qaz_qwerty.gsm!key:#'
|
2036
|
+
@example_encoded_hash = 'confirm!' + @example_encoded_hash_without_macro_name
|
2037
|
+
end
|
2038
|
+
|
2039
|
+
it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do
|
2040
|
+
Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash).should ==
|
2041
|
+
{:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'}
|
2042
|
+
end
|
2043
|
+
|
2044
|
+
it '::decode_hash() should convert the String of key/value escaped pairs into a Hash with Symbol keys when the macro name is not given' do
|
2045
|
+
Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash_without_macro_name).should ==
|
2046
|
+
{:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#'}
|
2047
|
+
end
|
2048
|
+
|
2049
|
+
it '::decode_hash() should split the sound files in the :play key to an array by splitting by "++"' do
|
2050
|
+
decoded_sound_files = Adhearsion::DialPlan::ConfirmationManager.decode_hash(example_encoded_hash)[:play]
|
2051
|
+
decoded_sound_files.should be_a_kind_of Array
|
2052
|
+
decoded_sound_files.size.should be 2
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
it 'a call to a party which is acknowledged with the proper key during the call to interruptible_play' do
|
2056
|
+
variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
|
2057
|
+
encoded_variables = {:network_script => encode_hash(variables)}
|
2058
|
+
io_mock = StringIO.new
|
2059
|
+
|
2060
|
+
mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
|
2061
|
+
mock_call.should_receive(:variables).once.and_return encoded_variables
|
2062
|
+
|
2063
|
+
sound_files = variables[:play]
|
2064
|
+
|
2065
|
+
manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
|
2066
|
+
|
2067
|
+
flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
|
2068
|
+
flexstub(manager).should_receive(:raw_response).and_return nil
|
2069
|
+
|
2070
|
+
flexmock(manager).should_receive(:answer).once
|
2071
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#'
|
2072
|
+
|
2073
|
+
manager.handle
|
2074
|
+
end
|
2075
|
+
|
2076
|
+
it 'when an timeout is encountered, it should set the MACRO_RESULT variable to CONTINUE' do
|
2077
|
+
variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
|
2078
|
+
encoded_variables = {:network_script => encode_hash(variables)}
|
2079
|
+
io_mock = StringIO.new
|
2080
|
+
|
2081
|
+
mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
|
2082
|
+
mock_call.should_receive(:variables).once.and_return encoded_variables
|
2083
|
+
|
2084
|
+
sound_files = variables[:play]
|
2085
|
+
|
2086
|
+
manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
|
2087
|
+
|
2088
|
+
flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
|
2089
|
+
flexstub(manager).should_receive(:raw_response).and_return nil
|
2090
|
+
|
2091
|
+
flexmock(manager).should_receive(:answer).once
|
2092
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil
|
2093
|
+
flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return nil
|
2094
|
+
|
2095
|
+
flexmock(manager).should_receive(:variable).once.with("MACRO_RESULT" => 'CONTINUE')
|
2096
|
+
|
2097
|
+
manager.handle
|
2098
|
+
end
|
2099
|
+
|
2100
|
+
it 'should wait the :timeout number of seconds if no digit was received when playing the files and continue when the right key is pressed' do
|
2101
|
+
variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '#', :macro => 'confirmer'}
|
2102
|
+
encoded_variables = {:network_script => encode_hash(variables)}
|
2103
|
+
io_mock = StringIO.new
|
2104
|
+
|
2105
|
+
mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
|
2106
|
+
mock_call.should_receive(:variables).once.and_return encoded_variables
|
2107
|
+
|
2108
|
+
sound_files = variables[:play]
|
2109
|
+
|
2110
|
+
manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
|
2111
|
+
|
2112
|
+
flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
|
2113
|
+
flexstub(manager).should_receive(:raw_response).and_return nil
|
2114
|
+
|
2115
|
+
flexmock(manager).should_receive(:answer).once
|
2116
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return nil
|
2117
|
+
flexmock(manager).should_receive(:wait_for_digit).once.with(20).and_return '#'
|
2118
|
+
|
2119
|
+
manager.handle
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
it 'should restart playback if the key received was not recognized' do
|
2123
|
+
variables = {:timeout => 20, :play => ['foo-bar', 'qaz_qwerty.gsm'], :key => '2', :macro => 'confirmer'}
|
2124
|
+
encoded_variables = {:network_script => encode_hash(variables)}
|
2125
|
+
io_mock = StringIO.new
|
2126
|
+
|
2127
|
+
mock_call.should_receive(:originating_voip_platform).once.and_return :asterisk
|
2128
|
+
mock_call.should_receive(:variables).once.and_return encoded_variables
|
2129
|
+
|
2130
|
+
sound_files = variables[:play]
|
2131
|
+
|
2132
|
+
manager = Adhearsion::DialPlan::ConfirmationManager.new(mock_call)
|
2133
|
+
|
2134
|
+
flexstub(manager).should_receive(:result_digit_from).and_return ?0.ord
|
2135
|
+
flexstub(manager).should_receive(:raw_response).and_return nil
|
2136
|
+
|
2137
|
+
flexmock(manager).should_receive(:answer).once
|
2138
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '3' # not :key
|
2139
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '#' # not :key
|
2140
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '1' # not :key
|
2141
|
+
flexmock(manager).should_receive(:interruptible_play).once.with(*sound_files).and_return '2' # matches :key
|
2142
|
+
|
2143
|
+
flexmock(manager).should_receive(:wait_for_digit).never # We never let it get to the point where it may timeout
|
2144
|
+
flexmock(manager).should_receive(:variable).never # We succeed by not setting the MACRO_RESULT variable
|
2145
|
+
|
2146
|
+
manager.handle
|
2147
|
+
end
|
2148
|
+
|
2149
|
+
end
|
2150
|
+
|
2151
|
+
describe 'say_phonetic command' do
|
2152
|
+
include DialplanCommandTestHelpers
|
2153
|
+
it 'Can execute the sayphonetic application using say_phonetic' do
|
2154
|
+
text = 'Say This'
|
2155
|
+
mock_call.should_receive(:execute).once.with("sayphonetic", text)
|
2156
|
+
mock_call.say_phonetic text
|
2157
|
+
end
|
2158
|
+
|
2159
|
+
it 'Can use special characters with say_phonetic' do
|
2160
|
+
text = '*Say This!*'
|
2161
|
+
mock_call.should_receive(:execute).once.with("sayphonetic", text)
|
2162
|
+
mock_call.say_phonetic text
|
2163
|
+
end
|
2164
|
+
end
|
2165
|
+
|
2166
|
+
describe 'say_chars command' do
|
2167
|
+
include DialplanCommandTestHelpers
|
2168
|
+
it 'Can execute the sayalpha application using say_chars' do
|
2169
|
+
text = 'ha124d9'
|
2170
|
+
mock_call.should_receive(:execute).once.with("sayalpha", text)
|
2171
|
+
mock_call.say_chars text
|
2172
|
+
end
|
2173
|
+
|
2174
|
+
it 'Can use special characters with say_chars' do
|
2175
|
+
text = "1a2.#"
|
2176
|
+
mock_call.should_receive(:execute).once.with("sayalpha", text)
|
2177
|
+
mock_call.say_chars text
|
2178
|
+
end
|
2179
|
+
end
|