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.
Files changed (64) hide show
  1. data/.gitignore +10 -0
  2. data/CHANGELOG +8 -0
  3. data/README.markdown +33 -0
  4. data/Rakefile +28 -68
  5. data/adhearsion.gemspec +19 -133
  6. data/app_generators/ahn/templates/Gemfile +0 -4
  7. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +4 -16
  8. data/lib/adhearsion/cli.rb +17 -0
  9. data/lib/adhearsion/component_manager/component_tester.rb +1 -3
  10. data/lib/adhearsion/component_manager/spec_framework.rb +4 -10
  11. data/lib/adhearsion/foundation/object.rb +10 -0
  12. data/lib/adhearsion/version.rb +1 -1
  13. data/spec/ahn_command_spec.rb +284 -0
  14. data/spec/component_manager_spec.rb +292 -0
  15. data/spec/constants_spec.rb +8 -0
  16. data/spec/drb_spec.rb +65 -0
  17. data/spec/fixtures/dialplan.rb +3 -0
  18. data/spec/foundation/event_socket_spec.rb +168 -0
  19. data/spec/host_definitions_spec.rb +79 -0
  20. data/spec/initialization_spec.rb +163 -0
  21. data/spec/initializer/configuration_spec.rb +270 -0
  22. data/spec/initializer/loading_spec.rb +149 -0
  23. data/spec/initializer/paths_spec.rb +74 -0
  24. data/spec/logging_spec.rb +86 -0
  25. data/spec/relationship_properties_spec.rb +54 -0
  26. data/spec/silence.rb +10 -0
  27. data/spec/spec_helper.rb +101 -0
  28. data/spec/voip/asterisk/agi_server_spec.rb +473 -0
  29. data/spec/voip/asterisk/ami/ami_spec.rb +549 -0
  30. data/spec/voip/asterisk/ami/lexer/ami_fixtures.yml +30 -0
  31. data/spec/voip/asterisk/ami/lexer/lexer_story +291 -0
  32. data/spec/voip/asterisk/ami/lexer/lexer_story.rb +241 -0
  33. data/spec/voip/asterisk/ami/lexer/story_helper.rb +124 -0
  34. data/spec/voip/asterisk/ami/old_tests.rb +204 -0
  35. data/spec/voip/asterisk/ami/super_manager/super_manager_story +25 -0
  36. data/spec/voip/asterisk/ami/super_manager/super_manager_story.rb +15 -0
  37. data/spec/voip/asterisk/ami/super_manager/super_manager_story_helper.rb +5 -0
  38. data/spec/voip/asterisk/commands_spec.rb +2179 -0
  39. data/spec/voip/asterisk/config_file_generators/agents_spec.rb +251 -0
  40. data/spec/voip/asterisk/config_file_generators/queues_spec.rb +323 -0
  41. data/spec/voip/asterisk/config_file_generators/voicemail_spec.rb +306 -0
  42. data/spec/voip/asterisk/config_manager_spec.rb +127 -0
  43. data/spec/voip/asterisk/menu_command/calculated_match_spec.rb +109 -0
  44. data/spec/voip/asterisk/menu_command/matchers_spec.rb +97 -0
  45. data/spec/voip/call_routing_spec.rb +125 -0
  46. data/spec/voip/dialplan_manager_spec.rb +468 -0
  47. data/spec/voip/dsl/dialing_dsl_spec.rb +270 -0
  48. data/spec/voip/dsl/dispatcher_spec.rb +82 -0
  49. data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
  50. data/spec/voip/dsl/parser_spec.rb +69 -0
  51. data/spec/voip/freeswitch/basic_connection_manager_spec.rb +39 -0
  52. data/spec/voip/freeswitch/inbound_connection_manager_spec.rb +39 -0
  53. data/spec/voip/freeswitch/oes_server_spec.rb +9 -0
  54. data/spec/voip/numerical_string_spec.rb +61 -0
  55. data/spec/voip/phone_number_spec.rb +45 -0
  56. data/theatre-spec/dsl_examples/dynamic_stomp.rb +7 -0
  57. data/theatre-spec/dsl_examples/simple_before_call.rb +7 -0
  58. data/theatre-spec/dsl_spec.rb +43 -0
  59. data/theatre-spec/invocation_spec.rb +167 -0
  60. data/theatre-spec/namespace_spec.rb +125 -0
  61. data/theatre-spec/spec_helper.rb +37 -0
  62. data/theatre-spec/spec_helper_spec.rb +28 -0
  63. data/theatre-spec/theatre_class_spec.rb +150 -0
  64. 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
@@ -0,0 +1,10 @@
1
+ #
2
+ # Seeing the dump of pending specs every time you run them is distracting.
3
+ # This file lets you skip pending specs.
4
+ class Test::Unit::TestResult
5
+ def add_disabled(name)
6
+ #nothing!
7
+ end
8
+ end
9
+
10
+ Adhearsion::Logging.silence!
@@ -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