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,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'The ahn_log command' do
|
4
|
+
|
5
|
+
it 'should add the ahn_log method to the global namespace' do
|
6
|
+
ahn_log.should be Adhearsion::Logging::DefaultAdhearsionLogger
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should log to the standard Adhearsion logger when given arguments" do
|
10
|
+
message = "o hai. ur home erly."
|
11
|
+
flexmock(Log4r::Logger['ahn']).should_receive(:info).once.with(message)
|
12
|
+
ahn_log message
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should create a new logger when given method_missing' do
|
16
|
+
ahn_log.micromenus 'danger will robinson!'
|
17
|
+
Log4r::Logger['micromenus'].should_not be nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should define a singleton method on itself of any name found by method_missing' do
|
21
|
+
ahn_log.agi "SOMETHING IMPORTANT HAPPENED"
|
22
|
+
Adhearsion::Logging::AdhearsionLogger.instance_methods.map{|m| m.to_sym}.should include :agi
|
23
|
+
end
|
24
|
+
|
25
|
+
it "dynamically generated loggers should support logging with blocks" do
|
26
|
+
# I had to comment out this it because Flexmock makes it impossible to#
|
27
|
+
# set up an expectation for receiving blocks.
|
28
|
+
|
29
|
+
# proc_to_log = lambda { [1,2,3].reverse.join }
|
30
|
+
#
|
31
|
+
# info_catcher = flexmock "A logger that responds to info()"
|
32
|
+
# info_catcher.should_receive(:info).once.with(&proc_to_log)
|
33
|
+
#
|
34
|
+
# flexmock(Log4r::Logger).should_receive(:[]).with('log4r')
|
35
|
+
# flexmock(Log4r::Logger).should_receive(:[]).once.with('ami').and_return info_catcher
|
36
|
+
#
|
37
|
+
# ahn_log.ami(&proc_to_log)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'new loggers created by method_missing() should be instances of AdhearsionLogger' do
|
41
|
+
ahn_log.qwerty.should be_a_kind_of Adhearsion::Logging::AdhearsionLogger
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
# Essential for running the tests
|
47
|
+
describe 'Logger level changing' do
|
48
|
+
|
49
|
+
after :each do
|
50
|
+
Adhearsion::Logging.logging_level = :info
|
51
|
+
end
|
52
|
+
|
53
|
+
after :all do
|
54
|
+
Adhearsion::Logging.logging_level = :fatal # Silence them again
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'changing the logging level should affect all loggers' do
|
58
|
+
loggers = [ahn_log.one, ahn_log.two, ahn_log.three]
|
59
|
+
loggers.map(&:level).should_not == [Log4r::WARN] * 3
|
60
|
+
Adhearsion::Logging.logging_level = :warn
|
61
|
+
loggers.map(&:level).should == [Log4r::WARN] * 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'a new logger should have the global Adhearsion logging level' do
|
65
|
+
ahn_log.foo.level.should be Log4r::INFO
|
66
|
+
Adhearsion::Logging.logging_level = :fatal
|
67
|
+
ahn_log.brand_new.level.should be Log4r::FATAL
|
68
|
+
end
|
69
|
+
|
70
|
+
it '#silence!() should change the level to be FATAL' do
|
71
|
+
flexmock(Adhearsion::Logging::DefaultAdhearsionLogger).should_receive(:level=).once.with(Log4r::FATAL)
|
72
|
+
Adhearsion::Logging.silence!
|
73
|
+
# Verify and close manually here because the after-test hook breaks the expectation
|
74
|
+
flexmock_verify
|
75
|
+
flexmock_close
|
76
|
+
end
|
77
|
+
|
78
|
+
it '#unsilence!() should change the level to be INFO' do
|
79
|
+
flexmock(Adhearsion::Logging::DefaultAdhearsionLogger).should_receive(:level=).once.with(Log4r::INFO)
|
80
|
+
Adhearsion::Logging.unsilence!
|
81
|
+
# Verify and close manually here because the after-test hook breaks the expectation
|
82
|
+
flexmock_verify
|
83
|
+
flexmock_close
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'adhearsion/foundation/relationship_properties'
|
3
|
+
|
4
|
+
describe "Module#relationships" do
|
5
|
+
|
6
|
+
describe "Overriding relationships in subclasses" do
|
7
|
+
|
8
|
+
it "should be overridable in subclasses" do
|
9
|
+
super_class = Class.new do
|
10
|
+
relationships :storage_medium => Array
|
11
|
+
end
|
12
|
+
sub_class = Class.new(super_class) do
|
13
|
+
relationships :storage_medium => Hash
|
14
|
+
end
|
15
|
+
super_class.new.send(:storage_medium).should be Array
|
16
|
+
sub_class.new.send(:storage_medium).should be Hash
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not affect other defined relationships" do
|
20
|
+
super_class = Class.new do
|
21
|
+
relationships :io_class => TCPSocket, :error_class => StandardError
|
22
|
+
end
|
23
|
+
sub_class = Class.new(super_class) do
|
24
|
+
relationships :error_class => RuntimeError
|
25
|
+
end
|
26
|
+
super_class.new.send(:io_class).should be TCPSocket
|
27
|
+
sub_class.new.send(:io_class).should be TCPSocket
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should be accessible within instance methods of that Class as another instance method" do
|
33
|
+
klass = Class.new do
|
34
|
+
relationships :struct => Struct
|
35
|
+
def new_struct
|
36
|
+
struct.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be accessible in subclasses" do
|
42
|
+
super_class = Class.new do
|
43
|
+
relationships :number_class => Bignum
|
44
|
+
end
|
45
|
+
|
46
|
+
Class.new(super_class) do
|
47
|
+
def number_class_name
|
48
|
+
number_class.name
|
49
|
+
end
|
50
|
+
end.new.number_class_name.should == "Bignum"
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
data/spec/silence.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
Dir.chdir File.join(File.dirname(__FILE__), '..')
|
2
|
+
$:.push('.')
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'bundler/setup'
|
7
|
+
|
8
|
+
def require_or_report_dependency(require_name, gem_name)
|
9
|
+
begin
|
10
|
+
require require_name
|
11
|
+
rescue LoadError
|
12
|
+
report_dependency!(gem_name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def report_dependency!(name)
|
17
|
+
2.times { puts }
|
18
|
+
puts "You need #{name} to run these tests: gem install #{name}"
|
19
|
+
2.times { puts }
|
20
|
+
exit!
|
21
|
+
end
|
22
|
+
|
23
|
+
class Object
|
24
|
+
alias :the_following_code :lambda
|
25
|
+
end
|
26
|
+
|
27
|
+
require_or_report_dependency('flexmock/test_unit', 'flexmock')
|
28
|
+
require_or_report_dependency('active_support', 'activesupport')
|
29
|
+
# require_or_report_dependency('ruby-debug', 'ruby-debug')
|
30
|
+
require_or_report_dependency('rubigen', 'rubigen')
|
31
|
+
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.mock_framework = :flexmock
|
34
|
+
config.filter_run_excluding :ignore => true
|
35
|
+
#config.filter_run :focus => true
|
36
|
+
config.color_enabled = true
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'pp'
|
40
|
+
require 'stringio'
|
41
|
+
|
42
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
43
|
+
$: << File.expand_path('lib')
|
44
|
+
$: << File.dirname(__FILE__)
|
45
|
+
|
46
|
+
require 'adhearsion'
|
47
|
+
|
48
|
+
module InitializerStubs
|
49
|
+
|
50
|
+
DEFAULT_AHNRC_DATA_STRUCTURE = YAML.load_file(
|
51
|
+
File.dirname(__FILE__) + "/../app_generators/ahn/templates/.ahnrc"
|
52
|
+
) unless defined? DEFAULT_AHNRC_DATA_STRUCTURE
|
53
|
+
|
54
|
+
UNWANTED_BEHAVIOR = {
|
55
|
+
Adhearsion::Initializer => [:initialize_log_file, :switch_to_root_directory, :daemonize!, :load],
|
56
|
+
Adhearsion::Initializer.metaclass => { :get_rules_from => DEFAULT_AHNRC_DATA_STRUCTURE },
|
57
|
+
} unless defined? UNWANTED_BEHAVIOR
|
58
|
+
|
59
|
+
def stub_behavior_for_initializer_with_no_path_changing_behavior
|
60
|
+
stub_unwanted_behavior
|
61
|
+
yield if block_given?
|
62
|
+
ensure
|
63
|
+
unstub_directory_changing_behavior
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_new_initializer_with_no_path_changing_behavior(&block)
|
67
|
+
stub_behavior_for_initializer_with_no_path_changing_behavior do
|
68
|
+
block.call Adhearsion::Initializer.start('path does not matter')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def stub_unwanted_behavior
|
73
|
+
UNWANTED_BEHAVIOR.each do |stub_victim_class, undesired_methods|
|
74
|
+
undesired_methods.each do |undesired_method_name_or_key_value_pair|
|
75
|
+
undesired_method_name, method_implementation = case undesired_method_name_or_key_value_pair
|
76
|
+
when Array
|
77
|
+
[undesired_method_name_or_key_value_pair.first, lambda { |*args| undesired_method_name_or_key_value_pair.last } ]
|
78
|
+
else
|
79
|
+
[undesired_method_name_or_key_value_pair, lambda{ |*args| }]
|
80
|
+
end
|
81
|
+
stub_victim_class.send(:alias_method, "pre_stubbed_#{undesired_method_name}", undesired_method_name)
|
82
|
+
stub_victim_class.send(:define_method, undesired_method_name, &method_implementation)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def unstub_directory_changing_behavior
|
88
|
+
UNWANTED_BEHAVIOR.each do |stub_victim_class, undesired_methods|
|
89
|
+
undesired_methods.each do |undesired_method_name|
|
90
|
+
undesired_method_name = undesired_method_name.first if undesired_method_name.kind_of? Array
|
91
|
+
stub_victim_class.send(:alias_method, undesired_method_name, "pre_stubbed_#{undesired_method_name}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Adhearsion::Initializer.ahn_root = File.dirname(__FILE__) + '/fixtures'
|
98
|
+
require 'spec/silence' unless ENV['SHOW_DISABLED']
|
99
|
+
|
100
|
+
require 'adhearsion/voip/asterisk'
|
101
|
+
require 'adhearsion/component_manager'
|
@@ -0,0 +1,473 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'adhearsion/voip/asterisk'
|
3
|
+
|
4
|
+
module CallVariableTestHelper
|
5
|
+
def parsed_call_variables_from(io)
|
6
|
+
Adhearsion::Call::Variables::Parser.parse(io).variables
|
7
|
+
end
|
8
|
+
|
9
|
+
def coerce_call_variables(variables)
|
10
|
+
Adhearsion::Call::Variables::Parser.coerce_variables(variables)
|
11
|
+
end
|
12
|
+
|
13
|
+
def merged_hash_with_call_variables(new_hash)
|
14
|
+
call_variables_with_new_query = typical_call_variables_hash.merge new_hash
|
15
|
+
coerce_call_variables call_variables_with_new_query
|
16
|
+
end
|
17
|
+
|
18
|
+
def parsed_call_variables_with_query(query_string)
|
19
|
+
request_string = "agi://10.0.0.0/#{query_string}"
|
20
|
+
merged_hash_with_call_variables({:request => request_string})[:query]
|
21
|
+
end
|
22
|
+
|
23
|
+
def typical_call_variable_io
|
24
|
+
StringIO.new(typical_call_variable_section)
|
25
|
+
end
|
26
|
+
|
27
|
+
def typical_call_variable_section
|
28
|
+
<<-VARIABLES
|
29
|
+
agi_network: yes
|
30
|
+
agi_request: agi://10.0.0.152/monkey?foo=bar&qaz=qwerty
|
31
|
+
agi_channel: SIP/marcel-b58046e0
|
32
|
+
agi_language: en
|
33
|
+
agi_type: SIP
|
34
|
+
agi_uniqueid: 1191245124.16
|
35
|
+
agi_callerid: 011441234567899
|
36
|
+
agi_calleridname: unknown
|
37
|
+
agi_callingpres: 0
|
38
|
+
agi_callingani2: 0
|
39
|
+
agi_callington: 0
|
40
|
+
agi_callingtns: 0
|
41
|
+
agi_dnid: 911
|
42
|
+
agi_rdnis: unknown
|
43
|
+
agi_context: adhearsion
|
44
|
+
agi_extension: 911
|
45
|
+
agi_priority: 1
|
46
|
+
agi_enhanced: 0.0
|
47
|
+
agi_accountcode:
|
48
|
+
|
49
|
+
VARIABLES
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO:
|
53
|
+
# - "unknown" should be converted to nil
|
54
|
+
# - "yes" or "no" should be converted to true or false
|
55
|
+
# - numbers beginning with a 0 MUST be converted to a NumericalString
|
56
|
+
# - Look up why there are so many zeroes. They're likely reprentative of some PRI definition.
|
57
|
+
|
58
|
+
def typical_call_variables_hash
|
59
|
+
uncoerced_variable_map = expected_uncoerced_variable_map
|
60
|
+
|
61
|
+
uncoerced_variable_map[:request] = URI.parse(uncoerced_variable_map[:request])
|
62
|
+
uncoerced_variable_map[:extension] = 911 # Adhearsion::VoIP::DSL::PhoneNumber.new(uncoerced_variable_map[:extension]) #
|
63
|
+
uncoerced_variable_map[:callerid] = Adhearsion::VoIP::DSL::NumericalString.new(uncoerced_variable_map[:callerid])
|
64
|
+
|
65
|
+
uncoerced_variable_map[:network] = true
|
66
|
+
uncoerced_variable_map[:calleridname] = nil
|
67
|
+
uncoerced_variable_map[:callingpres] = 0
|
68
|
+
uncoerced_variable_map[:callingani2] = 0
|
69
|
+
uncoerced_variable_map[:callingtns] = 0
|
70
|
+
uncoerced_variable_map[:dnid] = 911
|
71
|
+
uncoerced_variable_map[:rdnis] = nil
|
72
|
+
uncoerced_variable_map[:priority] = 1
|
73
|
+
uncoerced_variable_map[:enhanced] = 0.0
|
74
|
+
uncoerced_variable_map[:uniqueid] = "1191245124.16"
|
75
|
+
|
76
|
+
uncoerced_variable_map[:type_of_calling_number] = Adhearsion::VoIP::Constants::Q931_TYPE_OF_NUMBER[uncoerced_variable_map.delete(:callington).to_i]
|
77
|
+
|
78
|
+
coerced_variable_map = uncoerced_variable_map
|
79
|
+
coerced_variable_map[:query] = {"foo" => "bar", "qaz" => "qwerty"}
|
80
|
+
coerced_variable_map[:foo] = 'bar'
|
81
|
+
coerced_variable_map[:qaz] = 'qwerty'
|
82
|
+
coerced_variable_map
|
83
|
+
end
|
84
|
+
|
85
|
+
def expected_uncoerced_variable_map
|
86
|
+
{:network => 'yes',
|
87
|
+
:request => 'agi://10.0.0.152/monkey?foo=bar&qaz=qwerty',
|
88
|
+
:channel => 'SIP/marcel-b58046e0',
|
89
|
+
:language => 'en',
|
90
|
+
:type => 'SIP',
|
91
|
+
:uniqueid => '1191245124.16',
|
92
|
+
:callerid => '011441234567899',
|
93
|
+
:calleridname => 'unknown',
|
94
|
+
:callingpres => '0',
|
95
|
+
:callingani2 => '0',
|
96
|
+
:callington => '0',
|
97
|
+
:callingtns => '0',
|
98
|
+
:dnid => '911',
|
99
|
+
:rdnis => 'unknown',
|
100
|
+
:context => 'adhearsion',
|
101
|
+
:extension => '911',
|
102
|
+
:priority => '1',
|
103
|
+
:enhanced => '0.0',
|
104
|
+
:accountcode => ''}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module AgiServerTestHelper
|
109
|
+
def stub_before_call_hooks!
|
110
|
+
flexstub(Adhearsion::Events).should_receive(:trigger).with([:asterisk, :before_call], Proc).and_return
|
111
|
+
end
|
112
|
+
|
113
|
+
def stub_confirmation_manager!
|
114
|
+
flexstub(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).and_return false
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "The AGI server's serve() method" do
|
119
|
+
|
120
|
+
include AgiServerTestHelper
|
121
|
+
|
122
|
+
attr_reader :server_class, :server
|
123
|
+
before :each do
|
124
|
+
@server_class = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer
|
125
|
+
@server = @server_class.new(:port,:host)
|
126
|
+
end
|
127
|
+
|
128
|
+
before :each do
|
129
|
+
Adhearsion::Events.reinitialize_theatre!
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should instantiate a new Call with the IO object it receives' do
|
133
|
+
stub_before_call_hooks!
|
134
|
+
io_mock = flexmock "Mock IO object that's passed to the serve() method"
|
135
|
+
call_mock = flexmock "A Call mock that's returned by Adhearsion#receive_call_from", :variable => {}
|
136
|
+
flexstub(server_class).should_receive(:ahn_log)
|
137
|
+
the_following_code {
|
138
|
+
flexmock(Adhearsion).should_receive(:receive_call_from).once.with(io_mock).and_throw :created_call!
|
139
|
+
server.serve(io_mock)
|
140
|
+
}.should throw_symbol :created_call!
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should hand the call off to a new Manager if the request is agi://IP_ADDRESS_HERE' do
|
144
|
+
stub_before_call_hooks!
|
145
|
+
call_mock = flexmock 'A new mock call that will be passed to the manager', :variables => {}, :unique_identifier => "X"
|
146
|
+
|
147
|
+
flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return call_mock
|
148
|
+
manager_mock = flexmock 'a mock dialplan manager'
|
149
|
+
manager_mock.should_receive(:handle).once.with(call_mock)
|
150
|
+
flexmock(Adhearsion::DialPlan::Manager).should_receive(:new).once.and_return manager_mock
|
151
|
+
server.serve(nil)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should hand off a call to a ConfirmationManager if the request begins with confirm!' do
|
155
|
+
confirm_options = Adhearsion::DialPlan::ConfirmationManager.encode_hash_for_dial_macro_argument :timeout => 20, :key => "#"
|
156
|
+
call_mock = flexmock "a call that has network_script as a variable", :variables => {:network_script => "confirm!#{confirm_options[/^M\(\^?(.+)\)$/,1]}"}, :unique_identifier => "X"
|
157
|
+
manager_mock = flexmock 'a mock ConfirmationManager'
|
158
|
+
|
159
|
+
the_following_code {
|
160
|
+
flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
|
161
|
+
flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:confirmation_call?).once.with(call_mock).and_return true
|
162
|
+
flexmock(Adhearsion::DialPlan::ConfirmationManager).should_receive(:handle).once.with(call_mock).and_throw :handled_call!
|
163
|
+
server.serve(nil)
|
164
|
+
}.should throw_symbol :handled_call!
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'calling the serve() method invokes the before_call event' do
|
168
|
+
mock_io = flexmock "mock IO object given to AGIServer#serve"
|
169
|
+
mock_call = flexmock "mock Call"
|
170
|
+
flexmock(Adhearsion).should_receive(:receive_call_from).once.with(mock_io).and_return mock_call
|
171
|
+
|
172
|
+
flexmock(Adhearsion::Events).should_receive(:trigger_immediately).once.with([:asterisk, :before_call], mock_call).
|
173
|
+
and_throw :triggered
|
174
|
+
|
175
|
+
the_following_code {
|
176
|
+
server.serve mock_io
|
177
|
+
}.should throw_symbol :triggered
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should execute the hungup_call event when a HungupExtensionCallException is raised' do
|
181
|
+
call_mock = flexmock 'a bogus call', :hungup_call? => true, :variables => {:extension => "h"}, :unique_identifier => "X"
|
182
|
+
mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException"
|
183
|
+
|
184
|
+
stub_confirmation_manager!
|
185
|
+
flexstub(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
|
186
|
+
flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::HungupExtensionCallException.new(mock_env)
|
187
|
+
flexmock(Adhearsion::Events).should_receive(:trigger).once.with([:asterisk, :hungup_call], mock_env).and_throw :hungup_call
|
188
|
+
|
189
|
+
the_following_code { server.serve nil }.should throw_symbol :hungup_call
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should execute the OnFailedCall hooks when a FailedExtensionCallException is raised' do
|
193
|
+
call_mock = flexmock 'a bogus call', :failed_call? => true, :variables => {:extension => "failed"}, :unique_identifier => "X"
|
194
|
+
mock_env = flexmock "A mock execution environment which gets passed along in the HungupExtensionCallException", :failed_reason => "does not matter"
|
195
|
+
|
196
|
+
server = Adhearsion::VoIP::Asterisk::AGI::Server::RubyServer.new :port, :host
|
197
|
+
|
198
|
+
flexmock(Adhearsion).should_receive(:receive_call_from).once.and_return(call_mock)
|
199
|
+
flexmock(Adhearsion::DialPlan::Manager).should_receive(:handle).once.and_raise Adhearsion::FailedExtensionCallException.new(mock_env)
|
200
|
+
flexmock(Adhearsion::Events).should_receive(:trigger).once.with([:asterisk, :failed_call], mock_env).and_throw :failed_call
|
201
|
+
the_following_code { server.serve nil }.should throw_symbol :failed_call
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "Active Calls" do
|
207
|
+
include CallVariableTestHelper
|
208
|
+
attr_accessor :typical_call
|
209
|
+
|
210
|
+
before do
|
211
|
+
@mock_io = StringIO.new
|
212
|
+
@typical_call = Adhearsion::Call.new(@mock_io, typical_call_variables_hash)
|
213
|
+
end
|
214
|
+
|
215
|
+
after do
|
216
|
+
Adhearsion::active_calls.clear!
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'A Call object can be instantiated with a hash of attributes' do
|
220
|
+
the_following_code {
|
221
|
+
Adhearsion::Call.new(@mock_io, {})
|
222
|
+
}.should_not raise_error
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'Attributes passed into initialization of call object are accessible as attributes on the object' do
|
226
|
+
Adhearsion::Call.new(@mock_io, {:channel => typical_call_variables_hash[:channel]}).channel.should == typical_call_variables_hash[:channel]
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'Attributes passed into initialization of call object are accessible in the variables() Hash' do
|
230
|
+
variables = Adhearsion::Call.new(@mock_io, typical_call_variables_hash).variables
|
231
|
+
typical_call_variables_hash.each{|key, value|
|
232
|
+
value.should == variables[key]
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'Can add a call to the active calls list' do
|
237
|
+
Adhearsion.active_calls.any?.should be false
|
238
|
+
Adhearsion.active_calls << typical_call
|
239
|
+
Adhearsion.active_calls.size.should == 1
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'A hungup call removes itself from the active calls' do
|
243
|
+
mock_io = flexmock typical_call_variable_io
|
244
|
+
mock_io.should_receive(:close).once
|
245
|
+
|
246
|
+
size_before = Adhearsion.active_calls.size
|
247
|
+
|
248
|
+
call = Adhearsion.receive_call_from mock_io
|
249
|
+
Adhearsion.active_calls.size.should > size_before
|
250
|
+
call.hangup!
|
251
|
+
Adhearsion.active_calls.size.should == size_before
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'Can find active call by unique ID' do
|
255
|
+
Adhearsion.active_calls << typical_call
|
256
|
+
Adhearsion.active_calls.find(typical_call_variables_hash[:channel]).should_not be nil
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'A call can store the IO associated with the PBX/switch connection' do
|
260
|
+
Adhearsion::Call.new(@mock_io, {}).should respond_to(:io)
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'A call can be instantiated given a PBX/switch IO' do
|
264
|
+
call = Adhearsion::Call.receive_from(typical_call_variable_io)
|
265
|
+
call.should be_a_kind_of(Adhearsion::Call)
|
266
|
+
call.channel.should == typical_call_variables_hash[:channel]
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'A call with an extension of "t" raises a UselessCallException' do
|
270
|
+
the_following_code {
|
271
|
+
Adhearsion::Call.new(@mock_io, typical_call_variables_hash.merge(:extension => 't'))
|
272
|
+
}.should raise_error(Adhearsion::UselessCallException)
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'Can create a call and add it via a top-level method on the Adhearsion module' do
|
276
|
+
Adhearsion.active_calls.any?.should be false
|
277
|
+
call = Adhearsion.receive_call_from(typical_call_variable_io)
|
278
|
+
call.should be_a_kind_of(Adhearsion::Call)
|
279
|
+
Adhearsion.active_calls.size.should == 1
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'A call can identify its originating voip platform' do
|
283
|
+
call = Adhearsion::receive_call_from(typical_call_variable_io)
|
284
|
+
call.originating_voip_platform.should be(:asterisk)
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
|
289
|
+
describe 'A new Call object' do
|
290
|
+
|
291
|
+
include CallVariableTestHelper
|
292
|
+
|
293
|
+
it "it should have an @inbox object that's a synchronized Queue" do
|
294
|
+
new_call = Adhearsion::Call.new(nil, {})
|
295
|
+
new_call.inbox.should be_a_kind_of Queue
|
296
|
+
new_call.should respond_to :<<
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'the unique_identifier() method should return the :channel variable for :asterisk calls' do
|
300
|
+
variables = typical_call_variables_hash
|
301
|
+
new_call = Adhearsion::Call.new(nil, typical_call_variables_hash)
|
302
|
+
flexmock(new_call).should_receive(:originating_voip_platform).and_return :asterisk
|
303
|
+
new_call.unique_identifier.should == variables[:channel]
|
304
|
+
end
|
305
|
+
|
306
|
+
it "Call#define_singleton_accessor_with_pair should define a singleton method, not a class method" do
|
307
|
+
control = Adhearsion::Call.new(nil, {})
|
308
|
+
experiment = Adhearsion::Call.new(nil, {})
|
309
|
+
|
310
|
+
experiment.send(:define_singleton_accessor_with_pair, "ohai", 123)
|
311
|
+
experiment.should respond_to "ohai"
|
312
|
+
control.should_not respond_to "ohai"
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
describe 'the Calls collection' do
|
318
|
+
|
319
|
+
include CallVariableTestHelper
|
320
|
+
|
321
|
+
it 'the #<< method should add a Call to the Hash with its unique_id' do
|
322
|
+
id = rand
|
323
|
+
collection = Adhearsion::Calls.new
|
324
|
+
call = Adhearsion::Call.new(nil, {})
|
325
|
+
flexmock(call).should_receive(:unique_identifier).and_return id
|
326
|
+
collection << call
|
327
|
+
hash = collection.instance_variable_get("@calls")
|
328
|
+
hash.empty?.should_not be true
|
329
|
+
hash[id].should be call
|
330
|
+
end
|
331
|
+
|
332
|
+
it '#size should return the size of the Hash' do
|
333
|
+
collection = Adhearsion::Calls.new
|
334
|
+
collection.size.should be 0
|
335
|
+
collection << Adhearsion::Call.new(nil, {})
|
336
|
+
collection.size.should be 1
|
337
|
+
end
|
338
|
+
|
339
|
+
it '#remove_inactive_call should delete the call in the Hash' do
|
340
|
+
collection = Adhearsion::Calls.new
|
341
|
+
|
342
|
+
number_of_calls = 10
|
343
|
+
unique_ids = Array.new(number_of_calls) { rand }
|
344
|
+
calls = unique_ids.map { |id| Adhearsion::Call.new(nil, {:uniqueid => id}) }
|
345
|
+
calls.each { |call| collection << call }
|
346
|
+
|
347
|
+
deleted_call = calls[number_of_calls / 2]
|
348
|
+
collection.remove_inactive_call deleted_call
|
349
|
+
collection.size.should be number_of_calls - 1
|
350
|
+
end
|
351
|
+
|
352
|
+
it '#find_by_unique_id should pull the Call from the Hash using the unique_id' do
|
353
|
+
id = rand
|
354
|
+
call_database = flexmock "a mock Hash in which calls are stored"
|
355
|
+
call_database.should_receive(:[]).once.with(id)
|
356
|
+
collection = Adhearsion::Calls.new
|
357
|
+
flexmock(collection).should_receive(:calls).once.and_return(call_database)
|
358
|
+
collection.find(id)
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
describe 'Typical call variable parsing with typical data that has no special treatment' do
|
364
|
+
include CallVariableTestHelper
|
365
|
+
attr_reader :variables, :io
|
366
|
+
|
367
|
+
before(:each) do
|
368
|
+
@io = typical_call_variable_io
|
369
|
+
@variables = parsed_call_variables_from(io)
|
370
|
+
end
|
371
|
+
|
372
|
+
# it "listing all variable names" do
|
373
|
+
# parser.variable_names.map(&:to_s).sort.should == typical_call_variables_hash.keys.map(&:to_s).sort
|
374
|
+
# end
|
375
|
+
|
376
|
+
it "extracts call variables into a Hash" do
|
377
|
+
variables.kind_of?(Hash).should be true
|
378
|
+
end
|
379
|
+
|
380
|
+
it "extracting and converting variables and their values to a hash" do
|
381
|
+
typical_call_variables_hash.each{|key, value|
|
382
|
+
value.should == variables[key]
|
383
|
+
}
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
describe 'Call variable parsing with data that is treated specially' do
|
388
|
+
include CallVariableTestHelper
|
389
|
+
it "callington is renamed to type_of_calling_number" do
|
390
|
+
variables = parsed_call_variables_from typical_call_variable_io
|
391
|
+
variables[:type_of_calling_number].should == :unknown
|
392
|
+
variables.has_key?(:callington).should == false
|
393
|
+
end
|
394
|
+
it "normalizes the context to be a valid Ruby method name"
|
395
|
+
it "value of 'no' converts to false" do
|
396
|
+
merged_hash_with_call_variables(:no_var => "no")[:no_var].should be(false)
|
397
|
+
end
|
398
|
+
it "value of 'yes' converts to true"
|
399
|
+
it "value of 'unknown' converts to nil"
|
400
|
+
it 'separate_line_into_key_value_pair parses values with spaces in them' do
|
401
|
+
key, value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair("foo: My Name")
|
402
|
+
value.should == 'My Name'
|
403
|
+
end
|
404
|
+
|
405
|
+
it "Uniqueid remains a String, not a Float" do
|
406
|
+
uniqueid = "123456.12831"
|
407
|
+
variables = merged_hash_with_call_variables :uniqueid => uniqueid
|
408
|
+
variables[:uniqueid].should ==uniqueid
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
describe 'Typical call variable line parsing with a typical line that is not treated specially' do
|
414
|
+
include CallVariableTestHelper
|
415
|
+
attr_reader :parser, :line
|
416
|
+
|
417
|
+
before(:each) do
|
418
|
+
@line = 'agi_channel: SIP/marcel-b58046e0'
|
419
|
+
@key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
|
420
|
+
end
|
421
|
+
|
422
|
+
it "raw name is extracted correctly" do
|
423
|
+
@key.should == 'agi_channel'
|
424
|
+
end
|
425
|
+
|
426
|
+
it "raw value is extracted correctly" do
|
427
|
+
@value.should == 'SIP/marcel-b58046e0'
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
describe 'Call variable line parsing with a line that is treated specially' do
|
432
|
+
include CallVariableTestHelper
|
433
|
+
attr_reader :key, :value, :line
|
434
|
+
|
435
|
+
before(:each) do
|
436
|
+
@line = 'agi_request: agi://10.0.0.152'
|
437
|
+
@key, @value = Adhearsion::Call::Variables::Parser.separate_line_into_key_value_pair line
|
438
|
+
end
|
439
|
+
|
440
|
+
it "splits out name and value correctly even if the value contains a semicolon (i.e. the same character that is used as the name/value separators)" do
|
441
|
+
@key.should == 'agi_request'
|
442
|
+
@value.should == 'agi://10.0.0.152'
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
describe "Extracting the query from the request URI" do
|
448
|
+
include CallVariableTestHelper
|
449
|
+
attr_reader :parser
|
450
|
+
|
451
|
+
before(:each) do
|
452
|
+
# We don't want this Call::Variables::Parser to call parse because we only care about handling the request
|
453
|
+
# variable which we mock here.
|
454
|
+
@parser = Adhearsion::Call::Variables::Parser.new(StringIO.new('This argument does not matter'))
|
455
|
+
end
|
456
|
+
|
457
|
+
it "an empty hash is returned if there is no query string" do
|
458
|
+
parsed_call_variables_with_query('').should == {}
|
459
|
+
end
|
460
|
+
|
461
|
+
it "a key and value for parameter is returned when there is one query string parameter" do
|
462
|
+
parsed_call_variables_with_query('?foo=bar').should == {'foo' => 'bar'}
|
463
|
+
end
|
464
|
+
|
465
|
+
it "both key/value pairs are returned when there are a pair of query string parameters" do
|
466
|
+
parsed_call_variables_with_query('?foo=bar&baz=quux').should == {'foo' => 'bar', 'baz' => 'quux'}
|
467
|
+
end
|
468
|
+
|
469
|
+
it "all key/value pairs are returned when there are more than a pair of query string parameters" do
|
470
|
+
parsed_call_variables_with_query('?foo=bar&baz=quux&name=marcel').should == {'foo' => 'bar', 'baz' => 'quux', 'name' => 'marcel'}
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|