adhearsion 2.6.4 → 3.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -9
  3. data/CHANGELOG.md +22 -7
  4. data/Gemfile +2 -0
  5. data/README.markdown +4 -5
  6. data/Rakefile +1 -2
  7. data/adhearsion.gemspec +19 -8
  8. data/features/cli_create.feature +19 -3
  9. data/features/step_definitions/cli_steps.rb +0 -11
  10. data/features/support/env.rb +3 -4
  11. data/lib/adhearsion.rb +48 -27
  12. data/lib/adhearsion/call.rb +34 -50
  13. data/lib/adhearsion/call_controller.rb +6 -12
  14. data/lib/adhearsion/call_controller/dial.rb +15 -53
  15. data/lib/adhearsion/call_controller/input.rb +39 -162
  16. data/lib/adhearsion/call_controller/input/ask_grammar_builder.rb +44 -0
  17. data/lib/adhearsion/call_controller/input/menu_builder.rb +136 -0
  18. data/lib/adhearsion/call_controller/input/prompt_builder.rb +78 -0
  19. data/lib/adhearsion/call_controller/input/result.rb +46 -0
  20. data/lib/adhearsion/call_controller/output.rb +48 -67
  21. data/lib/adhearsion/call_controller/output/abstract_player.rb +3 -3
  22. data/lib/adhearsion/call_controller/output/async_player.rb +3 -3
  23. data/lib/adhearsion/call_controller/output/player.rb +1 -1
  24. data/lib/adhearsion/call_controller/record.rb +23 -8
  25. data/lib/adhearsion/calls.rb +1 -1
  26. data/lib/adhearsion/cli_commands/ahn_command.rb +2 -65
  27. data/lib/adhearsion/cli_commands/thor_errors.rb +0 -6
  28. data/lib/adhearsion/configuration.rb +91 -39
  29. data/lib/adhearsion/core_ext/blather/stanza.rb +41 -0
  30. data/lib/adhearsion/core_ext/blather/stanza/presence.rb +13 -0
  31. data/lib/adhearsion/error.rb +5 -0
  32. data/lib/adhearsion/event.rb +21 -0
  33. data/lib/adhearsion/event/active_speaker.rb +11 -0
  34. data/lib/adhearsion/event/answered.rb +11 -0
  35. data/lib/adhearsion/event/asterisk.rb +10 -0
  36. data/lib/adhearsion/event/asterisk/ami.rb +34 -0
  37. data/lib/adhearsion/event/complete.rb +75 -0
  38. data/lib/adhearsion/event/dtmf.rb +11 -0
  39. data/lib/adhearsion/event/end.rb +22 -0
  40. data/lib/adhearsion/event/input_timers_started.rb +9 -0
  41. data/lib/adhearsion/event/joined.rb +17 -0
  42. data/lib/adhearsion/event/offer.rb +14 -0
  43. data/lib/adhearsion/event/ringing.rb +11 -0
  44. data/lib/adhearsion/event/started_speaking.rb +13 -0
  45. data/lib/adhearsion/event/stopped_speaking.rb +13 -0
  46. data/lib/adhearsion/event/unjoined.rb +17 -0
  47. data/lib/adhearsion/events.rb +47 -66
  48. data/lib/adhearsion/foundation.rb +0 -1
  49. data/lib/adhearsion/foundation/object.rb +0 -5
  50. data/lib/adhearsion/generators/app/app_generator.rb +4 -1
  51. data/lib/adhearsion/generators/app/templates/Gemfile.erb +2 -10
  52. data/lib/adhearsion/generators/app/templates/adhearsion.erb +9 -9
  53. data/lib/adhearsion/generators/app/templates/config.ru +7 -0
  54. data/lib/adhearsion/generators/app/templates/en.yml +4 -0
  55. data/lib/adhearsion/generators/app/templates/events.erb +2 -2
  56. data/lib/adhearsion/generators/app/templates/hello_world.wav +0 -0
  57. data/lib/adhearsion/generators/app/templates/simon_game.rb +2 -1
  58. data/lib/adhearsion/generators/app/templates/simon_game_spec.rb +2 -2
  59. data/lib/adhearsion/has_headers.rb +34 -0
  60. data/lib/adhearsion/http_server.rb +37 -0
  61. data/lib/adhearsion/initializer.rb +19 -153
  62. data/lib/adhearsion/logging.rb +6 -25
  63. data/lib/adhearsion/outbound_call.rb +5 -5
  64. data/lib/adhearsion/plugin.rb +1 -0
  65. data/lib/adhearsion/protocol_error.rb +26 -0
  66. data/lib/adhearsion/rayo.rb +30 -0
  67. data/lib/adhearsion/rayo/client.rb +62 -0
  68. data/lib/adhearsion/rayo/client/component_registry.rb +33 -0
  69. data/lib/adhearsion/rayo/command.rb +21 -0
  70. data/lib/adhearsion/rayo/command/accept.rb +16 -0
  71. data/lib/adhearsion/rayo/command/answer.rb +16 -0
  72. data/lib/adhearsion/rayo/command/dial.rb +57 -0
  73. data/lib/adhearsion/rayo/command/hangup.rb +16 -0
  74. data/lib/adhearsion/rayo/command/join.rb +43 -0
  75. data/lib/adhearsion/rayo/command/mute.rb +13 -0
  76. data/lib/adhearsion/rayo/command/redirect.rb +23 -0
  77. data/lib/adhearsion/rayo/command/reject.rb +40 -0
  78. data/lib/adhearsion/rayo/command/unjoin.rb +24 -0
  79. data/lib/adhearsion/rayo/command/unmute.rb +13 -0
  80. data/lib/adhearsion/rayo/command_node.rb +47 -0
  81. data/lib/adhearsion/rayo/component.rb +21 -0
  82. data/lib/adhearsion/rayo/component/asterisk.rb +13 -0
  83. data/lib/adhearsion/rayo/component/asterisk/agi.rb +14 -0
  84. data/lib/adhearsion/rayo/component/asterisk/agi/command.rb +46 -0
  85. data/lib/adhearsion/rayo/component/asterisk/ami.rb +14 -0
  86. data/lib/adhearsion/rayo/component/asterisk/ami/action.rb +61 -0
  87. data/lib/adhearsion/rayo/component/component_node.rb +90 -0
  88. data/lib/adhearsion/rayo/component/input.rb +186 -0
  89. data/lib/adhearsion/rayo/component/output.rb +471 -0
  90. data/lib/adhearsion/rayo/component/prompt.rb +53 -0
  91. data/lib/adhearsion/rayo/component/receive_fax.rb +26 -0
  92. data/lib/adhearsion/rayo/component/record.rb +165 -0
  93. data/lib/adhearsion/rayo/component/send_fax.rb +64 -0
  94. data/lib/adhearsion/rayo/component/stop.rb +11 -0
  95. data/lib/adhearsion/rayo/connection.rb +12 -0
  96. data/lib/adhearsion/rayo/connection/asterisk.rb +74 -0
  97. data/lib/adhearsion/rayo/connection/connected.rb +22 -0
  98. data/lib/adhearsion/rayo/connection/generic_connection.rb +22 -0
  99. data/lib/adhearsion/rayo/connection/xmpp.rb +198 -0
  100. data/lib/adhearsion/rayo/disconnected_error.rb +22 -0
  101. data/lib/adhearsion/{punchblock_plugin → rayo}/initializer.rb +19 -19
  102. data/lib/adhearsion/rayo/rayo_node.rb +127 -0
  103. data/lib/adhearsion/rayo/ref.rb +57 -0
  104. data/lib/adhearsion/statistics.rb +1 -1
  105. data/lib/adhearsion/tasks.rb +1 -2
  106. data/lib/adhearsion/tasks/configuration.rb +1 -1
  107. data/lib/adhearsion/tasks/environment.rb +0 -2
  108. data/lib/adhearsion/tasks/i18n.rb +49 -0
  109. data/lib/adhearsion/translator.rb +11 -0
  110. data/lib/adhearsion/translator/asterisk.rb +234 -0
  111. data/lib/adhearsion/translator/asterisk/agi_app.rb +17 -0
  112. data/lib/adhearsion/translator/asterisk/agi_command.rb +45 -0
  113. data/lib/adhearsion/translator/asterisk/ami_error_converter.rb +20 -0
  114. data/lib/adhearsion/translator/asterisk/call.rb +416 -0
  115. data/lib/adhearsion/translator/asterisk/channel.rb +43 -0
  116. data/lib/adhearsion/translator/asterisk/component.rb +88 -0
  117. data/lib/adhearsion/translator/asterisk/component/asterisk.rb +15 -0
  118. data/lib/adhearsion/translator/asterisk/component/asterisk/agi_command.rb +42 -0
  119. data/lib/adhearsion/translator/asterisk/component/asterisk/ami_action.rb +68 -0
  120. data/lib/adhearsion/translator/asterisk/component/composed_prompt.rb +76 -0
  121. data/lib/adhearsion/translator/asterisk/component/dtmf_recognizer.rb +137 -0
  122. data/lib/adhearsion/translator/asterisk/component/input.rb +34 -0
  123. data/lib/adhearsion/translator/asterisk/component/input_component.rb +90 -0
  124. data/lib/adhearsion/translator/asterisk/component/mrcp_native_prompt.rb +71 -0
  125. data/lib/adhearsion/translator/asterisk/component/mrcp_prompt.rb +55 -0
  126. data/lib/adhearsion/translator/asterisk/component/mrcp_recog_prompt.rb +165 -0
  127. data/lib/adhearsion/translator/asterisk/component/output.rb +233 -0
  128. data/lib/adhearsion/translator/asterisk/component/record.rb +101 -0
  129. data/lib/adhearsion/translator/asterisk/component/stop_by_redirect.rb +30 -0
  130. data/lib/adhearsion/translator/asterisk/unimrcp_app.rb +28 -0
  131. data/lib/adhearsion/uri_list.rb +21 -0
  132. data/lib/adhearsion/version.rb +1 -1
  133. data/spec/adhearsion/call_controller/dial_spec.rb +79 -1420
  134. data/spec/adhearsion/call_controller/input_spec.rb +1141 -237
  135. data/spec/adhearsion/call_controller/output/async_player_spec.rb +10 -10
  136. data/spec/adhearsion/call_controller/output/player_spec.rb +8 -8
  137. data/spec/adhearsion/call_controller/output_spec.rb +162 -215
  138. data/spec/adhearsion/call_controller/record_spec.rb +15 -16
  139. data/spec/adhearsion/call_controller_spec.rb +23 -40
  140. data/spec/adhearsion/call_spec.rb +123 -129
  141. data/spec/adhearsion/calls_spec.rb +3 -3
  142. data/spec/adhearsion/configuration_spec.rb +94 -108
  143. data/spec/adhearsion/event/answered_spec.rb +50 -0
  144. data/spec/adhearsion/event/asterisk/Find Results +402 -0
  145. data/spec/adhearsion/event/asterisk/ami_spec.rb +81 -0
  146. data/spec/adhearsion/event/complete_spec.rb +176 -0
  147. data/spec/adhearsion/event/dtmf_spec.rb +35 -0
  148. data/spec/adhearsion/event/end_spec.rb +85 -0
  149. data/spec/adhearsion/event/input_timers_started_spec.rb +19 -0
  150. data/spec/adhearsion/event/joined_spec.rb +53 -0
  151. data/spec/adhearsion/event/offer_spec.rb +106 -0
  152. data/spec/adhearsion/event/ringing_spec.rb +50 -0
  153. data/spec/adhearsion/event/started_speaking_spec.rb +37 -0
  154. data/spec/adhearsion/event/stopped_speaking_spec.rb +37 -0
  155. data/spec/adhearsion/event/unjoined_spec.rb +48 -0
  156. data/spec/adhearsion/event/untitled +0 -0
  157. data/spec/adhearsion/events_spec.rb +19 -45
  158. data/spec/adhearsion/initializer_spec.rb +12 -184
  159. data/spec/adhearsion/logging_spec.rb +5 -20
  160. data/spec/adhearsion/outbound_call_spec.rb +13 -13
  161. data/spec/adhearsion/plugin_spec.rb +3 -4
  162. data/spec/adhearsion/protocol_error_spec.rb +91 -0
  163. data/spec/adhearsion/rayo/client/component_registry_spec.rb +26 -0
  164. data/spec/adhearsion/rayo/client_spec.rb +134 -0
  165. data/spec/adhearsion/rayo/command/accept_spec.rb +63 -0
  166. data/spec/adhearsion/rayo/command/answer_spec.rb +73 -0
  167. data/spec/adhearsion/rayo/command/dial_spec.rb +156 -0
  168. data/spec/adhearsion/rayo/command/hangup_spec.rb +63 -0
  169. data/spec/adhearsion/rayo/command/join_spec.rb +158 -0
  170. data/spec/adhearsion/rayo/command/mute_spec.rb +32 -0
  171. data/spec/adhearsion/rayo/command/redirect_spec.rb +89 -0
  172. data/spec/adhearsion/rayo/command/reject_spec.rb +117 -0
  173. data/spec/adhearsion/rayo/command/unjoin_spec.rb +82 -0
  174. data/spec/adhearsion/rayo/command/unmute_spec.rb +32 -0
  175. data/spec/adhearsion/rayo/command_node_spec.rb +101 -0
  176. data/spec/adhearsion/rayo/component/asterisk/agi/command_spec.rb +111 -0
  177. data/spec/adhearsion/rayo/component/asterisk/ami/action_spec.rb +173 -0
  178. data/spec/adhearsion/rayo/component/component_node_spec.rb +110 -0
  179. data/spec/adhearsion/rayo/component/input_spec.rb +715 -0
  180. data/spec/adhearsion/rayo/component/output_spec.rb +1030 -0
  181. data/spec/adhearsion/rayo/component/prompt_spec.rb +171 -0
  182. data/spec/adhearsion/rayo/component/receive_fax_spec.rb +136 -0
  183. data/spec/adhearsion/rayo/component/record_spec.rb +497 -0
  184. data/spec/adhearsion/rayo/component/send_fax_spec.rb +144 -0
  185. data/spec/adhearsion/rayo/connection/asterisk_spec.rb +118 -0
  186. data/spec/adhearsion/rayo/connection/xmpp_spec.rb +449 -0
  187. data/spec/adhearsion/rayo/initializer_spec.rb +353 -0
  188. data/spec/adhearsion/rayo/ref_spec.rb +168 -0
  189. data/spec/adhearsion/rayo_spec.rb +7 -0
  190. data/spec/adhearsion/router/route_spec.rb +1 -1
  191. data/spec/adhearsion/statistics_spec.rb +2 -5
  192. data/spec/adhearsion/translator/asterisk/call_spec.rb +2047 -0
  193. data/spec/adhearsion/translator/asterisk/component/asterisk/agi_command_spec.rb +256 -0
  194. data/spec/adhearsion/translator/asterisk/component/asterisk/ami_action_spec.rb +151 -0
  195. data/spec/adhearsion/translator/asterisk/component/composed_prompt_spec.rb +257 -0
  196. data/spec/adhearsion/translator/asterisk/component/input_spec.rb +571 -0
  197. data/spec/adhearsion/translator/asterisk/component/mrcp_native_prompt_spec.rb +774 -0
  198. data/spec/adhearsion/translator/asterisk/component/mrcp_prompt_spec.rb +1244 -0
  199. data/spec/adhearsion/translator/asterisk/component/output_spec.rb +1850 -0
  200. data/spec/adhearsion/translator/asterisk/component/record_spec.rb +426 -0
  201. data/spec/adhearsion/translator/asterisk/component/stop_by_redirect_spec.rb +62 -0
  202. data/spec/adhearsion/translator/asterisk/component_spec.rb +83 -0
  203. data/spec/adhearsion/translator/asterisk_spec.rb +685 -0
  204. data/spec/adhearsion/uri_list_spec.rb +88 -0
  205. data/spec/adhearsion_spec.rb +89 -14
  206. data/spec/fixtures/locale/en.yml +13 -0
  207. data/spec/fixtures/locale/it.yml +13 -0
  208. data/spec/spec_helper.rb +18 -5
  209. data/spec/support/call_controller_test_helpers.rb +3 -2
  210. data/spec/support/initializer_stubs.rb +3 -1
  211. data/spec/support/punchblock_examples.rb +65 -0
  212. data/spec/support/punchblock_mocks.rb +12 -0
  213. metadata +412 -70
  214. data/features/cli_daemon.feature +0 -20
  215. data/features/cli_restart.feature +0 -52
  216. data/features/cli_stop.feature +0 -50
  217. data/lib/adhearsion/call_controller/menu_dsl.rb +0 -21
  218. data/lib/adhearsion/call_controller/menu_dsl/array_match_calculator.rb +0 -26
  219. data/lib/adhearsion/call_controller/menu_dsl/calculated_match.rb +0 -43
  220. data/lib/adhearsion/call_controller/menu_dsl/calculated_match_collection.rb +0 -45
  221. data/lib/adhearsion/call_controller/menu_dsl/fixnum_match_calculator.rb +0 -11
  222. data/lib/adhearsion/call_controller/menu_dsl/match_calculator.rb +0 -40
  223. data/lib/adhearsion/call_controller/menu_dsl/menu.rb +0 -207
  224. data/lib/adhearsion/call_controller/menu_dsl/menu_builder.rb +0 -92
  225. data/lib/adhearsion/call_controller/menu_dsl/range_match_calculator.rb +0 -60
  226. data/lib/adhearsion/call_controller/menu_dsl/string_match_calculator.rb +0 -25
  227. data/lib/adhearsion/call_controller/utility.rb +0 -77
  228. data/lib/adhearsion/foundation/custom_daemonizer.rb +0 -52
  229. data/lib/adhearsion/punchblock_plugin.rb +0 -63
  230. data/scripts/cloc-1.64.pl +0 -10483
  231. data/spec/adhearsion/call_controller/menu_dsl/array_match_calculator_spec.rb +0 -76
  232. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_collection_spec.rb +0 -60
  233. data/spec/adhearsion/call_controller/menu_dsl/calculated_match_spec.rb +0 -61
  234. data/spec/adhearsion/call_controller/menu_dsl/fixnum_match_calculator_spec.rb +0 -39
  235. data/spec/adhearsion/call_controller/menu_dsl/match_calculator_spec.rb +0 -17
  236. data/spec/adhearsion/call_controller/menu_dsl/menu_builder_spec.rb +0 -165
  237. data/spec/adhearsion/call_controller/menu_dsl/menu_spec.rb +0 -420
  238. data/spec/adhearsion/call_controller/menu_dsl/range_match_calculator_spec.rb +0 -32
  239. data/spec/adhearsion/call_controller/menu_dsl/string_match_calculator_spec.rb +0 -40
  240. data/spec/adhearsion/call_controller/utility_spec.rb +0 -90
  241. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +0 -356
  242. data/spec/adhearsion/punchblock_plugin_spec.rb +0 -59
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ module Asterisk
10
+ describe AGICommand do
11
+ include HasMockCallbackConnection
12
+
13
+ let(:channel) { 'SIP/foo' }
14
+ let(:ami_client) { double('AMI Client').as_null_object }
15
+ let(:translator) { Translator::Asterisk.new ami_client, connection }
16
+ let(:mock_call) { Translator::Asterisk::Call.new channel, translator, ami_client, connection }
17
+ let(:component_id) { Adhearsion.new_uuid }
18
+
19
+ before { stub_uuids component_id }
20
+
21
+ let :original_command do
22
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command.new :name => 'EXEC ANSWER'
23
+ end
24
+
25
+ subject { AGICommand.new original_command, mock_call }
26
+
27
+ let :response do
28
+ RubyAMI::Response.new
29
+ end
30
+
31
+ context 'initial execution' do
32
+ before { original_command.request! }
33
+
34
+ it 'should send the appropriate action' do
35
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'EXEC ANSWER', 'CommandID' => component_id).and_return(response)
36
+ subject.execute
37
+ end
38
+
39
+ context 'with some parameters' do
40
+ let(:params) { [1000, 'foo'] }
41
+
42
+ let :original_command do
43
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command.new :name => 'WAIT FOR DIGIT', :params => params
44
+ end
45
+
46
+ it 'should send the appropriate action' do
47
+ expect(ami_client).to receive(:send_action).once.with('AGI', 'Channel' => channel, 'Command' => 'WAIT FOR DIGIT "1000" "foo"', 'CommandID' => component_id).and_return(response)
48
+ subject.execute
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'when the AMI action completes' do
54
+ before do
55
+ original_command.request!
56
+ end
57
+
58
+ let :expected_response do
59
+ Adhearsion::Rayo::Ref.new uri: component_id
60
+ end
61
+
62
+ let :response do
63
+ RubyAMI::Response.new 'ActionID' => "552a9d9f-46d7-45d8-a257-06fe95f48d99",
64
+ 'Message' => 'Added AGI original_command to queue'
65
+ end
66
+
67
+ it 'should send the component node a ref with the action ID' do
68
+ expect(ami_client).to receive(:send_action).once.and_return response
69
+ subject.execute
70
+ expect(original_command.response(1)).to eq(expected_response)
71
+ end
72
+
73
+ context 'with an error' do
74
+ let(:message) { 'Action failed' }
75
+ let :response do
76
+ RubyAMI::Error.new.tap { |e| e.message = message }
77
+ end
78
+
79
+ before { expect(ami_client).to receive(:send_action).once.and_raise response }
80
+
81
+ it 'should send the component node false' do
82
+ subject.execute
83
+ expect(original_command.response(1)).to be_falsey
84
+ end
85
+
86
+ context "which is 'No such channel'" do
87
+ let(:message) { 'No such channel' }
88
+
89
+ it "should return an :item_not_found error for the command" do
90
+ subject.execute
91
+ expect(original_command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id))
92
+ end
93
+ end
94
+
95
+ context "which is 'Channel SIP/nosuchchannel does not exist.'" do
96
+ let(:message) { 'Channel SIP/nosuchchannel does not exist.' }
97
+
98
+ it "should return an :item_not_found error for the command" do
99
+ subject.execute
100
+ expect(original_command.response(0.5)).to eq(Adhearsion::ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{mock_call.id}", mock_call.id))
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ describe 'when receiving an AsyncAGI event' do
107
+ before do
108
+ original_command.request!
109
+ original_command.execute!
110
+ end
111
+
112
+ context 'of type start'
113
+
114
+ context 'of type Exec' do
115
+ let (:ami_event_result) { "200%20result=123%20(timeout)%0A" }
116
+ let(:ami_event) do
117
+ RubyAMI::Event.new 'AsyncAGI',
118
+ "SubEvent" => "Exec",
119
+ "Channel" => channel,
120
+ "CommandId" => component_id,
121
+ "Command" => "EXEC ANSWER",
122
+ "Result" => ami_event_result
123
+ end
124
+
125
+ let(:ami_event_ast13) do
126
+ RubyAMI::Event.new 'AsyncAGIExec',
127
+ "Channel" => channel,
128
+ "CommandId" => component_id,
129
+ "Command" => "EXEC ANSWER",
130
+ "Result" => ami_event_result
131
+ end
132
+
133
+ let :expected_complete_reason do
134
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command::Complete::Success.new :code => 200,
135
+ :result => 123,
136
+ :data => 'timeout'
137
+ end
138
+
139
+ def should_send_complete_event
140
+ subject.handle_ami_event ami_event
141
+
142
+ complete_event = original_command.complete_event 0.5
143
+
144
+ expect(original_command).to be_complete
145
+
146
+ expect(complete_event.component_id).to eq(component_id.to_s)
147
+ expect(complete_event.reason).to eq(expected_complete_reason)
148
+ end
149
+
150
+ it 'should send a complete event' do
151
+ should_send_complete_event
152
+ end
153
+
154
+ context 'with an AsyncAGIExec event' do
155
+ let(:ami_event) { ami_event_ast13 }
156
+
157
+ it 'should send a complete event' do
158
+ should_send_complete_event
159
+ end
160
+ end
161
+
162
+ context 'when the result contains illegal characters in the AGI response' do
163
+ let (:ami_event_result) { '$' }
164
+ let :expected_complete_reason do
165
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command::Complete::Success.new :code => -1,
166
+ :result => nil,
167
+ :data => nil
168
+ end
169
+
170
+ def treat_as_failure
171
+ subject.handle_ami_event ami_event
172
+
173
+ complete_event = original_command.complete_event 0.5
174
+
175
+ expect(original_command).to be_complete
176
+
177
+ expect(complete_event.component_id).to eq(component_id.to_s)
178
+ expect(complete_event.reason).to eq(expected_complete_reason)
179
+ end
180
+
181
+ it 'treats it as a failure with code -1' do
182
+ treat_as_failure
183
+ end
184
+
185
+ context 'with an AsyncAGIExec event' do
186
+ let(:ami_event) { ami_event_ast13 }
187
+
188
+ it 'treats it as a failure with code -1' do
189
+ treat_as_failure
190
+ end
191
+ end
192
+ end
193
+
194
+ context "when the command was ASYNCAGI BREAK" do
195
+ let :original_command do
196
+ Adhearsion::Rayo::Component::Asterisk::AGI::Command.new :name => 'ASYNCAGI BREAK'
197
+ end
198
+
199
+ let(:chan_var) { nil }
200
+
201
+ before do
202
+ response = RubyAMI::Response.new 'Value' => chan_var
203
+ expect(ami_client).to receive(:send_action).once.with('GetVar', 'Channel' => channel, 'Variable' => 'ADHEARSION_END_ON_ASYNCAGI_BREAK').and_return response
204
+ end
205
+
206
+ it 'should not send an end (hangup) event to the translator' do
207
+ expect(translator).to receive(:handle_pb_event).once.with kind_of(Adhearsion::Event::Complete)
208
+ expect(translator).to receive(:handle_pb_event).never.with kind_of(Adhearsion::Event::End)
209
+ subject.handle_ami_event ami_event
210
+ end
211
+
212
+ context "when the ADHEARSION_END_ON_ASYNCAGI_BREAK channel var is set" do
213
+ let(:chan_var) { 'true' }
214
+
215
+ it 'should send an end (hungup) event to the translator' do
216
+ expected_end_event = Adhearsion::Event::End.new reason: :hungup,
217
+ platform_code: 16,
218
+ target_call_id: mock_call.id
219
+
220
+ expect(translator).to receive(:handle_pb_event).once.with kind_of(Adhearsion::Event::Complete)
221
+ expect(translator).to receive(:handle_pb_event).once.with expected_end_event
222
+ subject.handle_ami_event ami_event
223
+ end
224
+
225
+ context "when the AMI event has a timestamp" do
226
+ let :ami_event do
227
+ RubyAMI::Event.new 'AsyncAGI',
228
+ "SubEvent" => "Exec",
229
+ "Channel" => channel,
230
+ "CommandId" => component_id,
231
+ "Command" => "EXEC ANSWER",
232
+ "Result" => "200%20result=123%20(timeout)%0A",
233
+ 'Timestamp' => '1393368380.572575'
234
+ end
235
+
236
+ it "should use the AMI timestamp for the Rayo event" do
237
+ expected_end_event = Adhearsion::Event::End.new reason: :hungup,
238
+ platform_code: 16,
239
+ target_call_id: mock_call.id,
240
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
241
+ expect(translator).to receive(:handle_pb_event).once.with kind_of(Adhearsion::Event::Complete)
242
+ expect(translator).to receive(:handle_pb_event).once.with expected_end_event
243
+
244
+ subject.handle_ami_event ami_event
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ module Asterisk
10
+ describe AMIAction do
11
+ include HasMockCallbackConnection
12
+
13
+ let(:ami_client) { double('AMI Client').as_null_object }
14
+ let(:mock_translator) { Translator::Asterisk.new ami_client, connection }
15
+
16
+ let :original_command do
17
+ Adhearsion::Rayo::Component::Asterisk::AMI::Action.new :name => 'ExtensionStatus', :params => { :context => 'default', :exten => 'idonno' }
18
+ end
19
+
20
+ before do
21
+ original_command.request!
22
+ end
23
+
24
+ subject { AMIAction.new original_command, mock_translator, ami_client }
25
+
26
+ context 'initial execution' do
27
+ let(:component_id) { Adhearsion.new_uuid }
28
+
29
+ let :expected_response do
30
+ Adhearsion::Rayo::Ref.new uri: component_id
31
+ end
32
+
33
+ before { stub_uuids component_id }
34
+
35
+ it 'should send the appropriate RubyAMI::Action and send the component node a ref' do
36
+ expect(ami_client).to receive(:send_action).once.with('ExtensionStatus', 'Context' => 'default', 'Exten' => 'idonno').and_return(RubyAMI::Response.new)
37
+ subject.execute
38
+ expect(original_command.response(1)).to eq(expected_response)
39
+ end
40
+ end
41
+
42
+ context 'when the AMI action completes' do
43
+ let :response do
44
+ RubyAMI::Response.new 'ActionID' => '552a9d9f-46d7-45d8-a257-06fe95f48d99',
45
+ 'Message' => 'Channel status will follow',
46
+ 'Exten' => 'idonno',
47
+ 'Context' => 'default',
48
+ 'Hint' => '',
49
+ 'Status' => '-1'
50
+ end
51
+
52
+ before { response.text_body = 'Some text body' }
53
+
54
+ let :expected_complete_reason do
55
+ Adhearsion::Rayo::Component::Asterisk::AMI::Action::Complete::Success.new message: 'Channel status will follow', text_body: 'Some text body', headers: {'Exten' => "idonno", 'Context' => "default", 'Hint' => "", 'Status' => "-1"}
56
+ end
57
+
58
+ context 'for a non-causal action' do
59
+ it 'should send a complete event to the component node' do
60
+ expect(ami_client).to receive(:send_action).once.and_return response
61
+ expect(subject).to receive(:send_complete_event).once.with expected_complete_reason
62
+ subject.execute
63
+ end
64
+ end
65
+
66
+ context 'for a causal action' do
67
+ let :original_command do
68
+ Adhearsion::Rayo::Component::Asterisk::AMI::Action.new :name => 'CoreShowChannels'
69
+ end
70
+
71
+ let :expected_action do
72
+ RubyAMI::Action.new 'CoreShowChannels'
73
+ end
74
+
75
+ let :event do
76
+ RubyAMI::Event.new 'CoreShowChannel', 'ActionID' => "552a9d9f-46d7-45d8-a257-06fe95f48d99",
77
+ 'Channel' => 'SIP/127.0.0.1-00000013',
78
+ 'UniqueID' => '1287686437.19',
79
+ 'Context' => 'adhearsion',
80
+ 'Extension' => '23432',
81
+ 'Priority' => '2',
82
+ 'ChannelState' => '6',
83
+ 'ChannelStateDesc' => 'Up'
84
+ end
85
+
86
+ let :terminating_event do
87
+ RubyAMI::Event.new 'CoreShowChannelsComplete', 'EventList' => 'Complete',
88
+ 'ListItems' => '3',
89
+ 'ActionID' => 'umtLtvSg-RN5n-GEay-Z786-YdiaSLNXkcYN'
90
+ end
91
+
92
+ let :event_node do
93
+ Adhearsion::Event::Asterisk::AMI.new name: 'CoreShowChannel', component_id: subject.id, source_uri: subject.id, headers: {
94
+ 'Channel' => 'SIP/127.0.0.1-00000013',
95
+ 'UniqueID' => '1287686437.19',
96
+ 'Context' => 'adhearsion',
97
+ 'Extension' => '23432',
98
+ 'Priority' => '2',
99
+ 'ChannelState' => '6',
100
+ 'ChannelStateDesc' => 'Up'
101
+ }
102
+ end
103
+
104
+ let :expected_complete_reason do
105
+ Adhearsion::Rayo::Component::Asterisk::AMI::Action::Complete::Success.new message: 'Channel status will follow', text_body: 'Some text body', headers: {'Exten' => "idonno", 'Context' => "default", 'Hint' => "", 'Status' => "-1", 'EventList' => 'Complete', 'ListItems' => '3'}
106
+ end
107
+
108
+ before { expect(ami_client).to receive(:send_action).once.and_return response }
109
+
110
+ it 'should send events to the component node' do
111
+ event_node
112
+ original_command.register_handler :internal, Adhearsion::Event::Asterisk::AMI do |event|
113
+ @event = event
114
+ end
115
+ response.events << event << terminating_event
116
+ subject.execute
117
+ expect(@event).to eq(event_node)
118
+ end
119
+
120
+ it 'should send a complete event to the component node' do
121
+ response.events << event << terminating_event
122
+
123
+ subject.execute
124
+
125
+ expect(original_command.complete_event(0.5).reason).to eq(expected_complete_reason)
126
+ end
127
+ end
128
+
129
+ context 'with an error' do
130
+ let :error do
131
+ RubyAMI::Error.new.tap { |e| e.message = 'Action failed' }
132
+ end
133
+
134
+ let :expected_complete_reason do
135
+ Adhearsion::Event::Complete::Error.new details: 'Action failed'
136
+ end
137
+
138
+ it 'should send a complete event to the component node' do
139
+ expect(ami_client).to receive(:send_action).once.and_raise error
140
+ expected_complete_reason
141
+ subject.execute
142
+ expect(original_command.complete_event(0.5).reason).to eq(expected_complete_reason)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,257 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ module Adhearsion
6
+ module Translator
7
+ class Asterisk
8
+ module Component
9
+ describe ComposedPrompt do
10
+ class MockConnection
11
+ attr_reader :events
12
+
13
+ def initialize
14
+ @events = []
15
+ end
16
+
17
+ def handle_event(event)
18
+ @events << event
19
+ end
20
+ end
21
+
22
+ let(:connection) { MockConnection.new }
23
+ let(:ami_client) { double('AMI') }
24
+ let(:translator) { Translator::Asterisk.new ami_client, connection }
25
+ let(:call) { Translator::Asterisk::Call.new 'foo', translator, ami_client, connection }
26
+
27
+ let :ssml_doc do
28
+ RubySpeech::SSML.draw do
29
+ audio src: 'tt-monkeys'
30
+ end
31
+ end
32
+
33
+ let :dtmf_grammar do
34
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digit' do
35
+ rule id: 'digit' do
36
+ one_of do
37
+ item { '1' }
38
+ item { '2' }
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ let :output_command_options do
45
+ { render_document: {value: ssml_doc} }
46
+ end
47
+
48
+ let :input_command_options do
49
+ {
50
+ mode: :dtmf,
51
+ grammar: {value: dtmf_grammar}
52
+ }
53
+ end
54
+
55
+ let(:command_options) { {} }
56
+
57
+ let :output_command do
58
+ Adhearsion::Rayo::Component::Output.new output_command_options
59
+ end
60
+
61
+ let :input_command do
62
+ Adhearsion::Rayo::Component::Input.new input_command_options
63
+ end
64
+
65
+ let :original_command do
66
+ Adhearsion::Rayo::Component::Prompt.new output_command, input_command, command_options
67
+ end
68
+
69
+ subject { described_class.new original_command, call }
70
+
71
+ let(:playbackstatus) { 'SUCCESS' }
72
+
73
+ before do
74
+ allow(call).to receive_messages answered?: true, execute_agi_command: true
75
+ allow(call).to receive(:channel_var).with('PLAYBACKSTATUS').and_return playbackstatus
76
+ original_command.request!
77
+ end
78
+
79
+ let (:ast13mode) { false }
80
+
81
+ def ami_event_for_dtmf(digit, position)
82
+ if ast13mode
83
+ RubyAMI::Event.new 'DTMF' + (position == :start ? 'Begin' : '') + (position == :end ? 'End' : ''),
84
+ 'Digit' => digit.to_s
85
+ else
86
+ RubyAMI::Event.new 'DTMF',
87
+ 'Digit' => digit.to_s,
88
+ 'Start' => position == :start ? 'Yes' : 'No',
89
+ 'End' => position == :end ? 'Yes' : 'No'
90
+ end
91
+ end
92
+
93
+ def send_ami_events_for_dtmf(digit)
94
+ call.process_ami_event ami_event_for_dtmf(digit, :start)
95
+ call.process_ami_event ami_event_for_dtmf(digit, :end)
96
+ end
97
+
98
+ describe '#execute' do
99
+ context '#barge_in' do
100
+ context 'true' do
101
+ it "should execute an output component on the call and return a ref" do
102
+ expect(call).to receive(:execute_agi_command).once.with('EXEC Playback', 'tt-monkeys')
103
+ subject.execute
104
+ expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
105
+ end
106
+
107
+ context "if output fails to start" do
108
+ it "should return the error returned by output"
109
+ end
110
+
111
+ context "receiving dtmf during output" do
112
+ it "should stop the output"
113
+
114
+ it "should contribute to the input result"
115
+
116
+ it "should return a match complete event"
117
+ end
118
+
119
+ context "when not receiving any DTMF input at all" do
120
+ it "should not start the initial timer until output completes"
121
+ end
122
+ end
123
+
124
+ context 'false' do
125
+ it "should execute an output component on the call" do
126
+ expect(call).to receive(:execute_agi_command).once.with('EXEC Playback', 'tt-monkeys')
127
+ subject.execute
128
+ expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
129
+ end
130
+
131
+ context "if output fails to start" do
132
+ let(:output_response) { Adhearsion::ProtocolError.new.setup 'unrenderable document error', 'The provided document could not be rendered. See http://adhearsion.com/docs/common_problems#unrenderable-document-error for details.' }
133
+
134
+ let :ssml_doc do
135
+ RubySpeech::SSML.draw do
136
+ string "Foo Bar"
137
+ end
138
+ end
139
+
140
+ it "should return the error returned by output" do
141
+ subject.execute
142
+ expect(original_command.response(0.1)).to eq(output_response)
143
+ end
144
+ end
145
+
146
+ context "receiving dtmf during output" do
147
+ it "should not stop the output"
148
+
149
+ it "should not contribute to the input result"
150
+ end
151
+
152
+ context "receiving matching dtmf after output completes" do
153
+ let :expected_nlsml do
154
+ RubySpeech::NLSML.draw do
155
+ interpretation confidence: 1 do
156
+ instance "dtmf-1"
157
+ input "1", mode: :dtmf
158
+ end
159
+ end
160
+ end
161
+
162
+ let :expected_event do
163
+ Adhearsion::Event::Complete.new reason: expected_reason,
164
+ component_id: subject.id,
165
+ source_uri: subject.id,
166
+ target_call_id: call.id
167
+ end
168
+
169
+ let :expected_reason do
170
+ Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: expected_nlsml
171
+ end
172
+
173
+ def should_return_a_match_complete_event
174
+ expected_event
175
+ subject.execute
176
+ expect(original_command.response(0.1)).to be_a Adhearsion::Rayo::Ref
177
+ send_ami_events_for_dtmf 1
178
+
179
+ expect(connection.events).to include(expected_event)
180
+ end
181
+
182
+ it "should return a match complete event" do
183
+ should_return_a_match_complete_event
184
+ end
185
+
186
+ context 'with Asterisk 13 DTMFEnd event' do
187
+ let (:ast13mode) { true }
188
+
189
+ it "should return a match complete event" do
190
+ should_return_a_match_complete_event
191
+ end
192
+ end
193
+ end
194
+
195
+ context "when not receiving any DTMF input at all" do
196
+ it "should not start the initial timer until output completes"
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ describe "#execute_command" do
203
+ context "with a command it does not understand" do
204
+ let(:command) { Adhearsion::Rayo::Component::Output::Pause.new }
205
+
206
+ before { command.request! }
207
+ it "returns a Adhearsion::ProtocolError response" do
208
+ subject.execute_command command
209
+ expect(command.response(0.1)).to be_a Adhearsion::ProtocolError
210
+ end
211
+ end
212
+
213
+ context "with a Stop command" do
214
+ let(:command) { Adhearsion::Rayo::Component::Stop.new }
215
+ let(:channel) { "SIP/1234-00000000" }
216
+ let :ami_event do
217
+ RubyAMI::Event.new 'AsyncAGI',
218
+ 'SubEvent' => "Start",
219
+ 'Channel' => channel,
220
+ 'Env' => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2F1234-00000000%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201320835995.0%0Aagi_version%3A%201.8.4.1%0Aagi_callerid%3A%205678%0Aagi_calleridname%3A%20Jane%20Smith%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%201000%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20default%0Aagi_extension%3A%201000%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%204366221312%0A%0A"
221
+ end
222
+
223
+ before do
224
+ command.request!
225
+ original_command.execute!
226
+ end
227
+
228
+ it "sets the command response to true" do
229
+ expect(call).to receive(:redirect_back).once
230
+ subject.execute_command command
231
+ expect(command.response(0.1)).to eq(true)
232
+ end
233
+
234
+ it "sends the correct complete event" do
235
+ expected_reason = Adhearsion::Event::Complete::Stop.new
236
+ expected_event = Adhearsion::Event::Complete.new reason: expected_reason,
237
+ component_id: subject.id,
238
+ source_uri: subject.id,
239
+ target_call_id: call.id
240
+
241
+ expect(call).to receive(:redirect_back)
242
+ subject.execute_command command
243
+ expect(original_command).not_to be_complete
244
+ call.process_ami_event ami_event
245
+
246
+ sleep 0.2
247
+
248
+ expect(connection.events).to include(expected_event)
249
+ end
250
+ end
251
+ end
252
+
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end