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
@@ -8,334 +8,1238 @@ module Adhearsion
8
8
  describe Input do
9
9
  include CallControllerTestHelpers
10
10
 
11
- describe "#play_sound_files_for_menu" do
12
- let(:renderer_options) { { renderer: nil } }
13
- let(:options) { Hash.new }
14
- let(:menu_instance) { MenuDSL::Menu.new(options) }
15
- let(:sound_file) { "press a button" }
16
- let(:sound_files) { [sound_file] }
17
-
18
- it "should play the sound files for the menu" do
19
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return("1")
20
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
11
+ def self.temp_config_value(key, value, namespace = Adhearsion.config.core.media)
12
+ before do
13
+ @original_value = namespace[key]
14
+ namespace[key] = value
21
15
  end
22
16
 
23
- it "should wait for digit if nothing is pressed during playback" do
24
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return(nil)
25
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
26
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
27
- end
17
+ after { namespace[key] = @original_value }
18
+ end
28
19
 
29
- context "with a renderer specified" do
30
- let(:options) { { :renderer => :native } }
31
- let(:renderer_options) { { renderer: :native } }
32
- it "should play the sound files for the menu" do
33
- expect(subject).to receive(:interruptible_play).with(sound_file, renderer_options).and_return("1")
34
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
35
- end
20
+ let(:prompts) { ['http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
36
21
 
22
+ let :expected_ssml do
23
+ RubySpeech::SSML.draw do
24
+ audio src: 'http://example.com/nice-to-meet-you.mp3'
25
+ audio src: 'http://example.com/press-some-buttons.mp3'
37
26
  end
27
+ end
28
+
29
+ let :expected_output_options do
30
+ {
31
+ render_document: {value: expected_ssml},
32
+ renderer: nil
33
+ }
34
+ end
35
+
36
+ let :expected_input_options do
37
+ {
38
+ mode: :dtmf,
39
+ initial_timeout: 5000,
40
+ inter_digit_timeout: 2000,
41
+ max_silence: 5000,
42
+ min_confidence: 0.5,
43
+ recognizer: nil,
44
+ language: 'en-US',
45
+ grammar: { value: expected_grxml }
46
+ }
47
+ end
38
48
 
39
- context "when the menu is not interruptible" do
40
- let(:options) { { :interruptible => false } }
41
- it "should play the sound files and wait for digit" do
42
- expect(subject).to receive(:play).with(sound_file, renderer_options).and_return true
43
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
44
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
49
+ let(:expected_barge_in) { true }
50
+
51
+ let :expected_prompt do
52
+ Adhearsion::Rayo::Component::Prompt.new expected_output_options, expected_input_options, barge_in: expected_barge_in
53
+ end
54
+
55
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoMatch.new }
56
+
57
+ before { allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event).and_return(double(reason: reason)) }
58
+
59
+ describe "#ask" do
60
+ let :digit_limit_grammar do
61
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
62
+ rule id: 'digits', scope: 'public' do
63
+ item repeat: '0-5' do
64
+ one_of do
65
+ 0.upto(9) { |d| item { d.to_s } }
66
+ item { "#" }
67
+ item { "*" }
68
+ end
69
+ end
70
+ end
45
71
  end
46
72
  end
47
73
 
48
- context "with a renderer specified and not interruptible" do
49
- let(:options) { { :renderer => :native, :interruptible => false } }
50
- let(:renderer_options) { { renderer: :native } }
51
- it "should pass the renderer option to #play" do
52
- expect(subject).to receive(:play).with(sound_file, renderer_options).and_return true
53
- expect(subject).to receive(:wait_for_digit).with(menu_instance.timeout).and_return("1")
54
- expect(subject.play_sound_files_for_menu(menu_instance, sound_files)).to eq('1')
74
+ context "without a digit limit, terminator digit or grammar" do
75
+ it "raises ArgumentError" do
76
+ expect { subject.ask prompts }.to raise_error(ArgumentError, "You must specify at least one of limit, terminator or grammar")
55
77
  end
56
78
  end
57
- end#play_sound_files_for_menu
58
79
 
59
- describe "#wait_for_digit" do
60
- let(:timeout) { 2 }
61
- let(:timeout_ms) { 2000 }
80
+ context "with a digit limit" do
81
+ let(:expected_grxml) { digit_limit_grammar }
62
82
 
63
- let(:grxml) {
64
- RubySpeech::GRXML.draw :mode => 'dtmf', :root => 'inputdigits' do
65
- rule id: 'inputdigits', scope: 'public' do
66
- one_of do
67
- 0.upto(9) { |d| item { d.to_s } }
68
- item { "#" }
69
- item { "*" }
70
- end
83
+ it "executes a Prompt component with the correct prompts and grammar" do
84
+ expect_component_execution expected_prompt
85
+
86
+ subject.ask prompts, limit: 5
87
+ end
88
+
89
+ context "with nil prompts" do
90
+ let(:prompts) { [nil, 'http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
91
+
92
+ it "executes a Prompt component with the correct prompts and grammar" do
93
+ expect_component_execution expected_prompt
94
+ subject.ask prompts, limit: 5
95
+ end
96
+ end
97
+
98
+ context "with no prompts" do
99
+ it "executes an Input component with the correct grammar" do
100
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
101
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
102
+ subject.ask limit: 5
71
103
  end
72
104
  end
73
- }
74
105
 
75
- let(:input_component) {
76
- Punchblock::Component::Input.new(
77
- { :mode => :dtmf,
78
- :initial_timeout => timeout_ms,
79
- :inter_digit_timeout => timeout_ms,
80
- :grammar => {
81
- :value => grxml.to_s
106
+ context "with no prompts, but with a render_document option" do
107
+ let :expected_output_options do
108
+ {
109
+ render_document: {url: 'http://foo.com/bar'},
110
+ renderer: nil
82
111
  }
83
- }
84
- )
85
- }
112
+ end
86
113
 
87
- let(:utterance) { 'dtmf-5' }
114
+ it "executes an Input component with the correct grammar" do
115
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
116
+ expect_component_execution expected_prompt
117
+ subject.ask limit: 5, render_document: {url: 'http://foo.com/bar'}
118
+ end
119
+ end
88
120
 
89
- def expect_component_complete_event
90
- expect_input_component_complete_event utterance
121
+ context "with only nil prompts" do
122
+ it "executes an Input component with the correct grammar" do
123
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
124
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
125
+ subject.ask nil, limit: 5
126
+ end
127
+ end
91
128
  end
92
129
 
93
- it "sends the correct input component" do
94
- expect_component_complete_event
95
- expect(subject).to receive(:execute_component_and_await_completion).once.with(input_component).and_return input_component
96
- subject.wait_for_digit timeout
97
- end
130
+ context "with a terminator" do
131
+ let :expected_grxml do
132
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
133
+ rule id: 'digits', scope: 'public' do
134
+ item repeat: '0-' do
135
+ one_of do
136
+ 0.upto(9) { |d| item { d.to_s } }
137
+ item { "#" }
138
+ item { "*" }
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ before do
146
+ expected_input_options.merge! terminator: '#'
147
+ end
148
+
149
+ it "executes a Prompt component with the correct prompts and grammar" do
150
+ expect_component_execution expected_prompt
98
151
 
99
- it "returns the correct pressed digit" do
100
- expect_component_complete_event
101
- expect(subject).to receive(:execute_component_and_await_completion).once.with(kind_of(Punchblock::Component::Input)).and_return input_component
102
- expect(subject.wait_for_digit(timeout)).to eq('5')
152
+ subject.ask prompts, terminator: '#'
153
+ end
103
154
  end
104
155
 
105
- context "when the utterance does not have dtmf- prefix" do
106
- let(:utterance) { '5' }
156
+ context "with a digit limit and a terminator" do
157
+ let :expected_grxml do
158
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'digits', tag_format: 'semantics/1.0-literals' do
159
+ rule id: 'digits', scope: 'public' do
160
+ item repeat: '0-5' do
161
+ one_of do
162
+ 0.upto(9) { |d| item { d.to_s } }
163
+ item { "#" }
164
+ item { "*" }
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ before do
172
+ expected_input_options.merge! grammar: { value: expected_grxml },
173
+ terminator: '#'
174
+ end
107
175
 
108
- it "returns the correct pressed digit" do
109
- expect_component_complete_event
110
- expect(subject).to receive(:execute_component_and_await_completion).once.with(kind_of(Punchblock::Component::Input)).and_return input_component
111
- expect(subject.wait_for_digit(timeout)).to eq('5')
176
+ it "executes a Prompt component with the correct prompts and grammar" do
177
+ expect_component_execution expected_prompt
178
+
179
+ subject.ask prompts, limit: 5, terminator: '#'
112
180
  end
113
181
  end
114
182
 
115
- context "with a nil timeout" do
116
- let(:timeout) { nil }
117
- let(:timeout_ms) { nil }
183
+ context "with an inline GRXML grammar specified" do
184
+ let :expected_grxml do
185
+ RubySpeech::GRXML.draw root: 'main', language: 'en-us', mode: :voice, tag_format: 'semantics/1.0-literals' do
186
+ rule id: 'main', scope: 'public' do
187
+ one_of do
188
+ item { 'yes' }
189
+ item { 'no' }
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ before do
196
+ expected_input_options.merge! grammar: { value: expected_grxml }
197
+ end
198
+
199
+ it "executes a Prompt component with the correct prompts and grammar" do
200
+ expect_component_execution expected_prompt
118
201
 
119
- it "does not set a timeout on the component" do
120
- expect_component_complete_event
121
- expect(subject).to receive(:execute_component_and_await_completion).once.with(input_component).and_return input_component
122
- subject.wait_for_digit timeout
202
+ subject.ask prompts, grammar: expected_grxml
203
+ end
204
+
205
+ context "with multiple grammars specified" do
206
+ let :other_expected_grxml do
207
+ RubySpeech::GRXML.draw root: 'main', mode: :dtmf, tag_format: 'semantics/1.0-literals' do
208
+ rule id: 'main', scope: 'public' do
209
+ one_of do
210
+ item { 1 }
211
+ item { 2 }
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ before do
218
+ expected_input_options.merge! grammars: [{ value: expected_grxml }, { value: other_expected_grxml }]
219
+ end
220
+
221
+ it "executes a Prompt component with the correct prompts and grammar" do
222
+ expect_component_execution expected_prompt
223
+
224
+ subject.ask prompts, grammar: [expected_grxml, other_expected_grxml]
225
+ end
123
226
  end
124
227
  end
125
- end # wait_for_digit
126
228
 
127
- describe "#jump_to" do
128
- let(:match_object) { Class.new }
129
- let(:overrides) { Hash.new(:extension => "1") }
130
- let(:block) { Proc.new {} }
229
+ context "with a grammar URL specified" do
230
+ let(:expected_grxml) { digit_limit_grammar }
231
+ let(:grammar_url) { 'http://example.com/cities.grxml' }
232
+
233
+ before do
234
+ expected_input_options.merge! grammar: { url: grammar_url }
235
+ end
236
+
237
+ it "executes a Prompt component with the correct prompts and grammar" do
238
+ expect_component_execution expected_prompt
239
+
240
+ subject.ask prompts, grammar_url: grammar_url
241
+ end
242
+
243
+ context "with multiple grammar URLs specified" do
244
+ let(:other_grammar_url) { 'http://example.com/states.grxml' }
245
+
246
+ before do
247
+ expected_input_options.merge! grammars: [{ url: grammar_url }, { url: other_grammar_url }]
248
+ end
249
+
250
+ it "executes a Prompt component with the correct prompts and grammar" do
251
+ expect_component_execution expected_prompt
252
+
253
+ subject.ask prompts, grammar_url: [grammar_url, other_grammar_url]
254
+ end
255
+ end
256
+
257
+ context "with grammars specified inline and by URL" do
258
+ before do
259
+ expected_input_options.merge! grammars: [{ value: expected_grxml }, { url: grammar_url }]
260
+ end
261
+
262
+ it "executes a Prompt component with the correct prompts and grammar" do
263
+ expect_component_execution expected_prompt
131
264
 
132
- it "calls instance_exec if the match object has a block" do
133
- allow(match_object).to receive_messages(:block => block)
134
- expect(subject).to receive(:instance_exec).with(overrides[:extension], &block)
135
- subject.jump_to(match_object, overrides)
265
+ subject.ask prompts, grammar: expected_grxml, grammar_url: [grammar_url]
266
+ end
267
+ end
136
268
  end
137
269
 
138
- it "calls invoke if the match object does not have a block" do
139
- expect(match_object).to receive(:block).and_return(false)
140
- expect(match_object).to receive(:match_payload).and_return(:payload)
141
- expect(subject).to receive(:invoke).with(:payload, overrides)
142
- subject.jump_to(match_object, overrides)
270
+ context "with interruptible: false" do
271
+ let(:expected_grxml) { digit_limit_grammar }
272
+
273
+ let(:expected_barge_in) { false }
274
+
275
+ it "executes a Prompt with barge-in disabled" do
276
+ expect_component_execution expected_prompt
277
+
278
+ subject.ask prompts, limit: 5, interruptible: false
279
+ end
143
280
  end
144
- end#jump_to
145
281
 
146
- describe "#menu" do
147
- let(:sound_files) { ["press", "button"] }
282
+ context "with a timeout specified" do
283
+ let(:expected_grxml) { digit_limit_grammar }
284
+
285
+ before do
286
+ expected_input_options.merge! initial_timeout: 10000,
287
+ inter_digit_timeout: 2000,
288
+ max_silence: 10000
289
+ end
290
+
291
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
292
+ expect_component_execution expected_prompt
148
293
 
149
- let(:menu_instance) do
150
- MenuDSL::Menu.new { match(1) {} }
294
+ subject.ask prompts, limit: 5, timeout: 10
295
+ end
151
296
  end
152
297
 
153
- let(:result_done) { MenuDSL::Menu::MenuResultDone.new }
154
- let(:result_terminated) { MenuDSL::Menu::MenuTerminated.new }
155
- let(:result_limit_reached) { MenuDSL::Menu::MenuLimitReached.new }
156
- let(:result_invalid) { MenuDSL::Menu::MenuResultInvalid.new }
157
- let(:result_get_another_or_timeout) { MenuDSL::Menu::MenuGetAnotherDigitOrTimeout.new }
158
- let(:result_get_another_or_finish) { MenuDSL::Menu::MenuGetAnotherDigitOrFinish.new(:match_object, :new_extension) }
159
- let(:result_found) { MenuDSL::Menu::MenuResultFound.new(:match_object, :new_extension) }
298
+ context "with a negative timeout specified" do
299
+ let(:expected_grxml) { digit_limit_grammar }
300
+
301
+ before do
302
+ expected_input_options.merge! initial_timeout: -1,
303
+ inter_digit_timeout: -1,
304
+ max_silence: -1
305
+ end
160
306
 
161
- let(:status) { :foo }
162
- let(:response) { '1234' }
307
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
308
+ expect_component_execution expected_prompt
163
309
 
164
- before do
165
- allow(menu_instance).to receive_messages :status => status, :result => response
166
- expect(MenuDSL::Menu).to receive(:new).and_return(menu_instance)
310
+ subject.ask prompts, limit: 5, timeout: -1, inter_digit_timeout: -1
311
+ end
167
312
  end
168
313
 
169
- it "logs a warning about dropped DTMF" do
170
- expect(menu_instance).to receive(:continue).and_return(result_done)
314
+ context "with a different default timeout" do
315
+ let(:expected_grxml) { digit_limit_grammar }
171
316
 
172
- expect(call.logger).to receive(:warn).with(/deprecated/)
173
- subject.menu sound_files
317
+ before do
318
+ expected_input_options.merge! initial_timeout: 10000,
319
+ inter_digit_timeout: 2000,
320
+ max_silence: 10000
321
+ end
322
+
323
+ temp_config_value :timeout, 10
324
+
325
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
326
+ expect_component_execution expected_prompt
327
+
328
+ subject.ask prompts, limit: 5
329
+ end
174
330
  end
175
331
 
176
- it "exits the function if MenuResultDone" do
177
- expect(menu_instance).to receive(:should_continue?).and_return(true)
178
- expect(menu_instance).to receive(:continue).and_return(result_done)
179
- result = subject.menu sound_files
180
- expect(result.menu).to be menu_instance
181
- expect(result.response).to be response
332
+ context "with a different default minimum confidence" do
333
+ let(:expected_grxml) { digit_limit_grammar }
334
+
335
+ before do
336
+ expected_input_options.merge! min_confidence: 0.8
337
+ end
338
+
339
+ temp_config_value :min_confidence, 0.8
340
+
341
+ it "executes a Prompt with correct minimum confidence" do
342
+ expect_component_execution expected_prompt
343
+
344
+ subject.ask prompts, limit: 5
345
+ end
182
346
  end
183
347
 
184
- it "exits the function if MenuTerminated" do
185
- expect(menu_instance).to receive(:should_continue?).and_return(true)
186
- expect(menu_instance).to receive(:continue).and_return(result_terminated)
187
- result = subject.menu sound_files
188
- expect(result.menu).to be menu_instance
189
- expect(result.response).to be response
348
+ context "with a different default recognizer" do
349
+ let(:expected_grxml) { digit_limit_grammar }
350
+
351
+ before do
352
+ expected_input_options.merge! recognizer: 'something_else'
353
+ end
354
+
355
+ temp_config_value :recognizer, 'something_else'
356
+
357
+ it "executes a Prompt with correct recognizer" do
358
+ expect_component_execution expected_prompt
359
+
360
+ subject.ask prompts, limit: 5
361
+ end
190
362
  end
191
363
 
192
- it "exits the function if MenuLimitReached" do
193
- expect(menu_instance).to receive(:should_continue?).and_return(true)
194
- expect(menu_instance).to receive(:continue).and_return(result_limit_reached)
195
- result = subject.menu sound_files
196
- expect(result.menu).to be menu_instance
197
- expect(result.response).to be response
364
+ context "with a different default input language" do
365
+ let(:expected_grxml) { digit_limit_grammar }
366
+
367
+ before do
368
+ expected_input_options.merge! language: 'pt-BR'
369
+ end
370
+
371
+ temp_config_value :input_language, 'pt-BR'
372
+
373
+ it "executes a Prompt with correct input language" do
374
+ expect_component_execution expected_prompt
375
+
376
+ subject.ask prompts, limit: 5
377
+ end
198
378
  end
199
379
 
200
- it "executes failure hook and returns :failure if menu fails" do
201
- expect(menu_instance).to receive(:should_continue?).and_return(false)
202
- expect(menu_instance).to receive(:execute_failure_hook).once
203
- result = subject.menu sound_files
204
- expect(result.menu).to be menu_instance
205
- expect(result.response).to be response
380
+ context "with a different default output renderer" do
381
+ let(:expected_grxml) { digit_limit_grammar }
382
+
383
+ before do
384
+ expected_output_options.merge! renderer: 'something_else'
385
+ end
386
+
387
+ temp_config_value :default_renderer, 'something_else', Adhearsion.config.core.media
388
+
389
+ it "executes a Prompt with correct renderer" do
390
+ expect_component_execution expected_prompt
391
+
392
+ subject.ask prompts, limit: 5
393
+ end
206
394
  end
207
395
 
208
- it "executes invalid hook if input is invalid" do
209
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
210
- expect(menu_instance).to receive(:continue).and_return(result_invalid, result_done)
211
- expect(menu_instance).to receive(:execute_invalid_hook).once
212
- expect(menu_instance).to receive(:restart!).once
213
- result = subject.menu sound_files
214
- expect(result.menu).to be menu_instance
215
- expect(result.response).to be response
396
+ context "with a different default output voice" do
397
+ let(:expected_grxml) { digit_limit_grammar }
398
+
399
+ before do
400
+ expected_output_options.merge! voice: 'something_else'
401
+ end
402
+
403
+ temp_config_value :default_voice, 'something_else', Adhearsion.config.core.media
404
+
405
+ it "executes a Prompt with correct voice" do
406
+ expect_component_execution expected_prompt
407
+
408
+ subject.ask prompts, limit: 5
409
+ end
216
410
  end
217
411
 
218
- it "plays audio, then executes timeout hook if input times out" do
219
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
220
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
221
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
222
- expect(menu_instance).to receive(:execute_timeout_hook).once
223
- expect(menu_instance).to receive(:restart!).once
224
- subject.menu sound_files
412
+ context "with overridden input options" do
413
+ let(:expected_grxml) { digit_limit_grammar }
414
+
415
+ before do
416
+ expected_input_options.merge! inter_digit_timeout: 35000
417
+ end
418
+
419
+ it "executes a Prompt with correct input options" do
420
+ expect_component_execution expected_prompt
421
+
422
+ subject.ask prompts, limit: 5, input_options: {inter_digit_timeout: 35000}
423
+ end
225
424
  end
226
425
 
227
- it "plays audio, then adds digit to digit buffer if input is received" do
228
- expect(menu_instance).to receive(:should_continue?).twice.and_return(true)
229
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
230
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return("1")
231
- expect(menu_instance).to receive(:<<).with("1").once
232
- subject.menu sound_files
426
+ context "with overridden output options" do
427
+ let(:expected_grxml) { digit_limit_grammar }
428
+
429
+ before do
430
+ expected_output_options.merge! max_time: 35000
431
+ end
432
+
433
+ it "executes a Prompt with correct output options" do
434
+ expect_component_execution expected_prompt
435
+
436
+ subject.ask prompts, limit: 5, output_options: {max_time: 35000}
437
+ end
233
438
  end
234
439
 
235
- it "plays audio, then jumps to payload when input is finished" do
236
- expect(menu_instance).to receive(:should_continue?).and_return(true)
237
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_finish)
238
- expect(subject).to receive(:play_sound_files_for_menu).with(menu_instance, sound_files).and_return(nil)
239
- expect(subject).to receive(:jump_to).with(:match_object, :extension => :new_extension).once
240
- subject.menu sound_files
440
+ context "when the call is dead when trying to execute the prompt" do
441
+ before { call.terminate }
442
+
443
+ it "should raise Adhearsion::Call::Hangup" do
444
+ expect { subject.ask prompts, limit: 5 }.to raise_error Adhearsion::Call::Hangup
445
+ end
241
446
  end
242
447
 
243
- it "jumps to payload when result is found" do
244
- expect(menu_instance).to receive(:should_continue?).and_return(true)
245
- expect(menu_instance).to receive(:continue).and_return(result_found)
246
- expect(subject).to receive(:jump_to).with(:match_object, :extension => :new_extension).once
247
- result = subject.menu sound_files
248
- expect(result.menu).to be menu_instance
249
- expect(result.response).to be response
448
+ context "when a utterance is received" do
449
+ let(:expected_grxml) { digit_limit_grammar }
450
+
451
+ before { expect_component_execution expected_prompt }
452
+
453
+ let(:result) { subject.ask prompts, limit: 5 }
454
+
455
+ context "that is a match" do
456
+ let(:mode) { :dtmf }
457
+ let(:utterance) { '123' }
458
+
459
+ let :nlsml do
460
+ utterance = self.utterance
461
+ mode = self.mode
462
+ RubySpeech::NLSML.draw do
463
+ interpretation confidence: 1 do
464
+ input utterance, mode: mode
465
+ instance 'Foo'
466
+ end
467
+ end
468
+ end
469
+
470
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
471
+
472
+ it "returns :match status and the utterance" do
473
+ expect(result.status).to be :match
474
+ expect(result).to be_match
475
+ expect(result.mode).to be :dtmf
476
+ expect(result.confidence).to eq(1)
477
+ expect(result.utterance).to eq('123')
478
+ expect(result.interpretation).to eq('Foo')
479
+ expect(result.nlsml).to eq(nlsml)
480
+ end
481
+
482
+ context "with speech input" do
483
+ let(:mode) { :speech }
484
+ let(:utterance) { 'Hello world' }
485
+
486
+ it "should not alter the utterance" do
487
+ expect(result.utterance).to eq('Hello world')
488
+ end
489
+ end
490
+
491
+ context "with a single DTMF digit" do
492
+ context 'with dtmf- prefixes' do
493
+ let(:utterance) { 'dtmf-3' }
494
+
495
+ it "removes dtmf- previxes" do
496
+ expect(result.utterance).to eq('3')
497
+ end
498
+ end
499
+
500
+ context 'with "star"' do
501
+ let(:utterance) { "dtmf-star" }
502
+
503
+ it "interprets as *" do
504
+ expect(result.utterance).to eq('*')
505
+ end
506
+ end
507
+
508
+ context 'with "*"' do
509
+ let(:utterance) { '*' }
510
+
511
+ it "interprets as *" do
512
+ expect(result.utterance).to eq('*')
513
+ end
514
+ end
515
+
516
+ context 'with "pound"' do
517
+ let(:utterance) { 'dtmf-pound' }
518
+
519
+ it "interprets pound as #" do
520
+ expect(result.utterance).to eq('#')
521
+ end
522
+ end
523
+
524
+ context 'with "#"' do
525
+ let(:utterance) { '#' }
526
+
527
+ it "interprets # as #" do
528
+ expect(result.utterance).to eq('#')
529
+ end
530
+ end
531
+
532
+ context 'without a dtmf- prefix' do
533
+ let(:utterance) { '1' }
534
+
535
+ it "correctly interprets the digits" do
536
+ expect(result.utterance).to eq('1')
537
+ end
538
+ end
539
+
540
+ context 'with "star"' do
541
+ let(:utterance) { nil }
542
+
543
+ it "is nil when utterance is nil" do
544
+ expect(result.utterance).to eq(nil)
545
+ end
546
+ end
547
+ end
548
+
549
+ context "with multiple digits separated by spaces" do
550
+ let(:utterance) { '1 dtmf-5 dtmf-star # 2' }
551
+
552
+ it "returns the digits without space separation" do
553
+ expect(result.utterance).to eq('15*#2')
554
+ end
555
+ end
556
+ end
557
+
558
+ context "that is a nomatch" do
559
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoMatch.new }
560
+
561
+ it "returns :nomatch status and a nil utterance" do
562
+ expect(result.status).to eql(:nomatch)
563
+ expect(result).not_to be_match
564
+ expect(result.utterance).to be_nil
565
+ end
566
+ end
567
+
568
+ context "that is a noinput" do
569
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoInput.new }
570
+
571
+ it "returns :noinput status and a nil utterance" do
572
+ expect(result.status).to eql(:noinput)
573
+ expect(result).not_to be_match
574
+ expect(result.utterance).to be_nil
575
+ end
576
+ end
577
+
578
+ context "that is a hangup" do
579
+ let(:reason) { Adhearsion::Event::Complete::Hangup.new }
580
+
581
+ it "returns :hangup status and a nil utterance" do
582
+ expect(result.status).to eql(:hangup)
583
+ expect(result).not_to be_match
584
+ expect(result.utterance).to be_nil
585
+ end
586
+ end
587
+
588
+ context "that is a stop" do
589
+ let(:reason) { Adhearsion::Event::Complete::Stop.new }
590
+
591
+ it "returns :stop status and a nil utterance" do
592
+ expect(result.status).to eql(:stop)
593
+ expect(result).not_to be_match
594
+ expect(result.utterance).to be_nil
595
+ end
596
+ end
597
+
598
+ context "that is an error" do
599
+ let(:reason) { Adhearsion::Event::Complete::Error.new details: 'foobar' }
600
+
601
+ it "should raise an error with a message of 'foobar" do
602
+ expect { subject.ask prompts, limit: 5 }.to raise_error(Adhearsion::CallController::Input::InputError, /foobar/)
603
+ end
604
+ end
250
605
  end
606
+ end
251
607
 
252
- context "if a digit limit is specified" do
253
- it "should raise an ArgumentError"
608
+ describe "#menu" do
609
+ context "with no block" do
610
+ it "should raise ArgumentError" do
611
+ expect { subject.menu }.to raise_error(ArgumentError, /specify a block to build the menu/)
612
+ end
254
613
  end
255
614
 
256
- context "if a terminator digit is specified" do
257
- it "should raise an ArgumentError"
615
+ context "with no matches" do
616
+ it "should raise ArgumentError" do
617
+ expect do
618
+ subject.menu "Hello?" do
619
+ end
620
+ end.to raise_error(ArgumentError, /specify one or more matches/)
621
+ end
258
622
  end
259
623
 
260
- end#describe
624
+ context "with several matches specified" do
625
+ let :expected_grxml do
626
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
627
+ rule id: 'options', scope: 'public' do
628
+ item do
629
+ one_of do
630
+ item do
631
+ tag { '0' }
632
+ '1'
633
+ end
634
+ end
635
+ end
636
+ end
637
+ end
638
+ end
261
639
 
262
- describe "#ask" do
263
- let(:sound_files) { ["press", "button"] }
640
+ let(:foo) { :bar }
264
641
 
265
- context "mocking out the menu" do
266
- let(:menu_instance) { MenuDSL::Menu.new :limit => 2 }
642
+ it "makes the block context available" do
643
+ expect_component_execution expected_prompt
644
+ doo = nil
645
+ subject.menu prompts do
646
+ doo = foo
647
+ match(1) { do_nothing }
648
+ end
649
+ expect(doo).to eq(:bar)
650
+ end
267
651
 
268
- let(:result_done) { MenuDSL::Menu::MenuResultDone.new }
269
- let(:result_terminated) { MenuDSL::Menu::MenuTerminated.new }
270
- let(:result_limit_reached) { MenuDSL::Menu::MenuLimitReached.new }
271
- let(:result_invalid) { MenuDSL::Menu::MenuResultInvalid.new }
272
- let(:result_get_another_or_timeout) { MenuDSL::Menu::MenuGetAnotherDigitOrTimeout.new }
273
- let(:result_get_another_or_finish) { MenuDSL::Menu::MenuGetAnotherDigitOrFinish.new(:match_object, :new_extension) }
274
- let(:result_found) { MenuDSL::Menu::MenuResultFound.new(:match_object, :new_extension) }
652
+ context "with nil prompts" do
653
+ let(:prompts) { [nil, 'http://example.com/nice-to-meet-you.mp3', 'http://example.com/press-some-buttons.mp3'] }
275
654
 
276
- before do
277
- menu_instance
278
- expect(MenuDSL::Menu).to receive(:new).and_return(menu_instance)
655
+ it "executes a Prompt component with the correct prompts and grammar" do
656
+ expect_component_execution expected_prompt
657
+ subject.menu prompts do
658
+ match(1) {}
659
+ end
660
+ end
279
661
  end
280
662
 
281
- it "logs a warning about dropped DTMF" do
282
- expect(menu_instance).to receive(:continue).and_return(result_done)
663
+ context "with no prompts" do
664
+ it "executes an Input component with the correct grammar" do
665
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
666
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
667
+ subject.menu do
668
+ match(1) {}
669
+ end
670
+ end
671
+ end
283
672
 
284
- expect(call.logger).to receive(:warn).with(/deprecated/)
285
- subject.ask sound_files
673
+ context "with only nil prompts" do
674
+ it "executes an Input component with the correct grammar" do
675
+ allow_any_instance_of(Adhearsion::Rayo::Component::Input).to receive(:complete_event).and_return(double(reason: reason))
676
+ expect_component_execution Adhearsion::Rayo::Component::Input.new(expected_input_options)
677
+ subject.menu nil do
678
+ match(1) {}
679
+ end
680
+ end
286
681
  end
287
682
 
288
- it "exits the function if MenuResultDone" do
289
- expect(menu_instance).to receive(:continue).and_return(result_done)
290
- result = subject.ask sound_files
291
- expect(result.menu).to be menu_instance
292
- expect(result.response).to eq('')
683
+ context "with interruptible: false" do
684
+ let(:expected_barge_in) { false }
685
+
686
+ it "executes a Prompt with barge-in disabled" do
687
+ expect_component_execution expected_prompt
688
+
689
+ subject.menu prompts, interruptible: false do
690
+ match(1) {}
691
+ end
692
+ end
293
693
  end
294
694
 
295
- it "exits the function if MenuTerminated" do
296
- expect(menu_instance).to receive(:continue).and_return(result_terminated)
297
- result = subject.ask sound_files
298
- expect(result.menu).to be menu_instance
299
- expect(result.response).to eq('')
695
+ context "with a timeout specified" do
696
+ before do
697
+ expected_input_options.merge! initial_timeout: 10000,
698
+ inter_digit_timeout: 10000,
699
+ max_silence: 10000
700
+ end
701
+
702
+ it "executes a Prompt with correct timeout (initial, inter-digit & max-silence)" do
703
+ expect_component_execution expected_prompt
704
+
705
+ subject.menu prompts, timeout: 10, inter_digit_timeout: 10 do
706
+ match(1) {}
707
+ end
708
+ end
300
709
  end
301
710
 
302
- it "exits the function if MenuLimitReached" do
303
- expect(menu_instance).to receive(:continue).and_return(result_limit_reached)
304
- result = subject.ask sound_files
305
- expect(result.menu).to be menu_instance
306
- expect(result.response).to eq('')
711
+ context "with a different default timeout" do
712
+ before do
713
+ expected_input_options.merge! initial_timeout: 10000,
714
+ max_silence: 10000
715
+ end
716
+
717
+ temp_config_value :timeout, 10
718
+
719
+ it "executes a Prompt with correct timeout (initial & max-silence)" do
720
+ expect_component_execution expected_prompt
721
+
722
+ subject.menu prompts do
723
+ match(1) {}
724
+ end
725
+ end
307
726
  end
308
727
 
309
- it "plays audio, then executes timeout hook if input times out" do
310
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout)
311
- expect(subject).to receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return(nil)
312
- result = subject.ask sound_files
313
- expect(result.menu).to be menu_instance
314
- expect(result.response).to eq('')
315
- expect(result.status).to be :timeout
728
+ context "with a different default inter-digit timeout" do
729
+ before do
730
+ expected_input_options.merge! inter_digit_timeout: 10000
731
+ end
732
+
733
+ temp_config_value :inter_digit_timeout, 10
734
+
735
+ it "executes a Prompt with correct timeout (inter-digit)" do
736
+ expect_component_execution expected_prompt
737
+
738
+ subject.menu prompts do
739
+ match(1) {}
740
+ end
741
+ end
316
742
  end
317
743
 
318
- it "plays audio, then adds digit to digit buffer if input is received" do
319
- expect(menu_instance).to receive(:continue).and_return(result_get_another_or_timeout, result_done)
320
- expect(subject).to receive(:play_sound_files_for_menu).once.with(menu_instance, sound_files).and_return("1")
321
- expect(menu_instance).to receive(:<<).with("1").once
322
- subject.ask sound_files
744
+ context "with a different default minimum confidence" do
745
+ before do
746
+ expected_input_options.merge! min_confidence: 0.8
747
+ end
748
+
749
+ temp_config_value :min_confidence, 0.8
750
+
751
+ it "executes a Prompt with correct minimum confidence" do
752
+ expect_component_execution expected_prompt
753
+
754
+ subject.menu prompts do
755
+ match(1) {}
756
+ end
757
+ end
323
758
  end
324
- end
325
759
 
326
- context "with a block passed" do
327
- it "validates the buffer using that block, terminating on something truthy" do
328
- expect(subject).to receive(:play_sound_files_for_menu).twice.and_return("1")
329
- result = subject.ask sound_files, limit: 3 do |buffer|
330
- buffer == '11'
760
+ context "with a different default recognizer" do
761
+ before do
762
+ expected_input_options.merge! recognizer: 'something_else'
763
+ end
764
+
765
+ temp_config_value :recognizer, 'something_else'
766
+
767
+ it "executes a Prompt with correct recognizer" do
768
+ expect_component_execution expected_prompt
769
+
770
+ subject.menu prompts do
771
+ match(1) {}
772
+ end
331
773
  end
332
- expect(result.status).to eq(:validator_terminated)
333
- expect(result.response).to eq('11')
334
774
  end
335
- end
336
775
 
337
- end#describe
776
+ context "with a different default input language" do
777
+ before do
778
+ expected_input_options.merge! language: 'pt-BR'
779
+ end
780
+
781
+ temp_config_value :input_language, 'pt-BR'
782
+
783
+ it "executes a Prompt with correct input language" do
784
+ expect_component_execution expected_prompt
785
+
786
+ subject.menu prompts do
787
+ match(1) {}
788
+ end
789
+ end
790
+ end
791
+
792
+ context "with a different default output renderer" do
793
+ before do
794
+ expected_output_options.merge! renderer: 'something_else'
795
+ end
796
+
797
+ temp_config_value :default_renderer, 'something_else', Adhearsion.config.core.media
798
+
799
+ it "executes a Prompt with correct renderer" do
800
+ expect_component_execution expected_prompt
801
+
802
+ subject.menu prompts do
803
+ match(1) {}
804
+ end
805
+ end
806
+ end
807
+
808
+ context "with a different default output voice" do
809
+ before do
810
+ expected_output_options.merge! voice: 'something_else'
811
+ end
812
+
813
+ temp_config_value :default_voice, 'something_else', Adhearsion.config.core.media
814
+
815
+ it "executes a Prompt with correct voice" do
816
+ expect_component_execution expected_prompt
817
+
818
+ subject.menu prompts do
819
+ match(1) {}
820
+ end
821
+ end
822
+ end
823
+
824
+ context "with overridden input options" do
825
+ before do
826
+ expected_input_options.merge! inter_digit_timeout: 35000
827
+ end
828
+
829
+ it "executes a Prompt with correct input options" do
830
+ expect_component_execution expected_prompt
831
+
832
+ subject.menu prompts, input_options: {inter_digit_timeout: 35000} do
833
+ match(1) {}
834
+ end
835
+ end
836
+ end
837
+
838
+ context "with overridden output options" do
839
+ before do
840
+ expected_output_options.merge! max_time: 35000
841
+ end
842
+
843
+ it "executes a Prompt with correct output options" do
844
+ expect_component_execution expected_prompt
845
+
846
+ subject.menu prompts, output_options: {max_time: 35000} do
847
+ match(1) {}
848
+ end
849
+ end
850
+ end
851
+
852
+ context "when using ASR mode" do
853
+ before do
854
+ expected_input_options.merge! mode: :voice
855
+ end
856
+
857
+ let :expected_grxml do
858
+ RubySpeech::GRXML.draw mode: 'voice', root: 'options', tag_format: 'semantics/1.0-literals' do
859
+ rule id: 'options', scope: 'public' do
860
+ item do
861
+ one_of do
862
+ item do
863
+ tag { '0' }
864
+ 'Hello world'
865
+ end
866
+ end
867
+ end
868
+ end
869
+ end
870
+ end
871
+
872
+ it "executes a Prompt with correct input mode, and the correct grammar mode" do
873
+ expect_component_execution expected_prompt
874
+
875
+ subject.menu prompts, mode: :voice do
876
+ match("Hello world") {}
877
+ end
878
+ end
879
+ end
880
+
881
+ context "when the call is dead when trying to execute the prompt" do
882
+ before { call.terminate }
883
+
884
+ it "should raise Adhearsion::Call::Hangup" do
885
+ expect do
886
+ subject.menu prompts do
887
+ match(1) {}
888
+ end
889
+ end.to raise_error Adhearsion::Call::Hangup
890
+ end
891
+ end
892
+
893
+ context "when input completes with an error" do
894
+ let(:reason) { Adhearsion::Event::Complete::Error.new details: 'foobar' }
895
+
896
+ it "should raise an error with a message of 'foobar'" do
897
+ expect_component_execution expected_prompt
898
+
899
+ expect do
900
+ subject.menu prompts do
901
+ match(1) {}
902
+ end
903
+ end.to raise_error(Adhearsion::CallController::Input::InputError, /foobar/)
904
+ end
905
+ end
906
+
907
+ context "when input doesn't match any of the specified matches" do
908
+ it "runs the invalid and failure handlers" do
909
+ expect_component_execution expected_prompt
910
+ expect(self).to receive(:do_something_on_invalid).once.ordered
911
+ expect(self).to receive(:do_something_on_failure).once.ordered
912
+
913
+ subject.menu prompts do
914
+ match(1) {}
915
+
916
+ invalid { do_something_on_invalid }
917
+ failure { do_something_on_failure }
918
+ end
919
+ end
920
+
921
+ context "when allowed multiple tries" do
922
+ let :nlsml do
923
+ RubySpeech::NLSML.draw do
924
+ interpretation confidence: 1 do
925
+ input '1', mode: :dtmf
926
+ instance '0'
927
+ end
928
+ end
929
+ end
930
+
931
+ let(:reason2) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
932
+
933
+ it "executes the prompt repeatedly until it gets a match" do
934
+ some_controller_class = Class.new Adhearsion::CallController
935
+
936
+ expect_component_execution(expected_prompt).twice
937
+ expect(self).to receive(:do_something_on_invalid).once.ordered
938
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '1').ordered
939
+ expect(self).to receive(:do_something_on_failure).never
940
+
941
+ invocation_count = 0
942
+ allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event) do
943
+ invocation_count += 1
944
+ case invocation_count
945
+ when 1 then double(reason: reason)
946
+ when 2 then double(reason: reason2)
947
+ else raise('Too many attempts')
948
+ end
949
+ end
950
+
951
+ subject.menu prompts, tries: 3 do
952
+ match 1, some_controller_class
953
+
954
+ invalid { do_something_on_invalid }
955
+ failure { do_something_on_failure }
956
+ end
957
+ end
958
+ end
959
+ end
960
+
961
+ context "when we don't get any input" do
962
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::NoInput.new }
963
+
964
+ it "runs the timeout and failure handlers" do
965
+ expect_component_execution expected_prompt
966
+ expect(self).to receive(:do_something_on_timeout).once.ordered
967
+ expect(self).to receive(:do_something_on_failure).once.ordered
968
+
969
+ subject.menu prompts do
970
+ match(1) {}
338
971
 
339
- end#shared
972
+ timeout { do_something_on_timeout }
973
+ failure { do_something_on_failure }
974
+ end
975
+ end
976
+
977
+ context "when allowed multiple tries" do
978
+ let :nlsml do
979
+ RubySpeech::NLSML.draw do
980
+ interpretation confidence: 1 do
981
+ input '1', mode: :dtmf
982
+ instance '0'
983
+ end
984
+ end
985
+ end
986
+
987
+ let(:reason2) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
988
+
989
+ it "executes the prompt repeatedly until it gets a match" do
990
+ some_controller_class = Class.new Adhearsion::CallController
991
+
992
+ expect_component_execution(expected_prompt).twice
993
+ expect(self).to receive(:do_something_on_timeout).once.ordered
994
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '1').ordered
995
+ expect(self).to receive(:do_something_on_failure).never
996
+
997
+ invocation_count = 0
998
+ allow_any_instance_of(Adhearsion::Rayo::Component::Prompt).to receive(:complete_event) do
999
+ invocation_count += 1
1000
+ case invocation_count
1001
+ when 1 then double(reason: reason)
1002
+ when 2 then double(reason: reason2)
1003
+ else raise('Too many attempts')
1004
+ end
1005
+ end
1006
+
1007
+ subject.menu prompts, tries: 3 do
1008
+ match 1, some_controller_class
1009
+
1010
+ timeout { do_something_on_timeout }
1011
+ failure { do_something_on_failure }
1012
+ end
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ context "when the input unambiguously matches a specified match" do
1018
+ let :expected_grxml do
1019
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1020
+ rule id: 'options', scope: 'public' do
1021
+ item do
1022
+ one_of do
1023
+ item do
1024
+ tag { '0' }
1025
+ '2'
1026
+ end
1027
+ item do
1028
+ tag { '1' }
1029
+ '1'
1030
+ end
1031
+ item do
1032
+ tag { '2' }
1033
+ '3'
1034
+ end
1035
+ end
1036
+ end
1037
+ end
1038
+ end
1039
+ end
1040
+
1041
+ let :nlsml do
1042
+ RubySpeech::NLSML.draw do
1043
+ interpretation confidence: 1 do
1044
+ input '3', mode: :dtmf
1045
+ instance '2'
1046
+ end
1047
+ end
1048
+ end
1049
+
1050
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
1051
+
1052
+ context "which specifies a controller class" do
1053
+ it "invokes the specfied controller, with the matched input as the :extension key in its metadata" do
1054
+ some_controller_class = Class.new Adhearsion::CallController
1055
+
1056
+ expect_component_execution expected_prompt
1057
+ expect(self).to receive(:invoke).once.with(some_controller_class, extension: '3')
1058
+
1059
+ subject.menu prompts do
1060
+ match(2) {}
1061
+ match(1) {}
1062
+ match 3, some_controller_class
1063
+ end
1064
+ end
1065
+ end
1066
+
1067
+ context "which specifies a block to be run" do
1068
+ it "invokes the block, passing in the input that matched" do
1069
+ expect_component_execution expected_prompt
1070
+ expect(self).to receive(:do_something_on_match).once.with('3')
1071
+
1072
+ subject.menu prompts do
1073
+ match(2) {}
1074
+ match(1) {}
1075
+ match(3) { |v| do_something_on_match v }
1076
+ end
1077
+ end
1078
+ end
1079
+
1080
+ context "when the match was a set of options" do
1081
+ let :expected_grxml do
1082
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1083
+ rule id: 'options', scope: 'public' do
1084
+ item do
1085
+ one_of do
1086
+ item do
1087
+ tag { '0' }
1088
+ '0'
1089
+ end
1090
+ item do
1091
+ tag { '1' }
1092
+ '1'
1093
+ end
1094
+ item do
1095
+ tag { '2' }
1096
+ one_of do
1097
+ item { '2' }
1098
+ item { '3' }
1099
+ end
1100
+ end
1101
+ end
1102
+ end
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ it "invokes the match payload" do
1108
+ expect_component_execution expected_prompt
1109
+ expect(self).to receive(:do_something_on_match).once.with('3')
1110
+
1111
+ subject.menu prompts do
1112
+ match(0) {}
1113
+ match(1) {}
1114
+ match(2,3) { |v| do_something_on_match v }
1115
+ end
1116
+ end
1117
+ end
1118
+
1119
+ context "when the match was a range" do
1120
+ let :expected_grxml do
1121
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1122
+ rule id: 'options', scope: 'public' do
1123
+ item do
1124
+ one_of do
1125
+ item do
1126
+ tag { '0' }
1127
+ '0'
1128
+ end
1129
+ item do
1130
+ tag { '1' }
1131
+ '1'
1132
+ end
1133
+ item do
1134
+ tag { '2' }
1135
+ one_of do
1136
+ item { '2' }
1137
+ item { '3' }
1138
+ end
1139
+ end
1140
+ end
1141
+ end
1142
+ end
1143
+ end
1144
+ end
1145
+
1146
+ it "invokes the match payload" do
1147
+ expect_component_execution expected_prompt
1148
+ expect(self).to receive(:do_something_on_match).once.with('3')
1149
+
1150
+ subject.menu prompts do
1151
+ match(0) {}
1152
+ match(1) {}
1153
+ match(2..3) { |v| do_something_on_match v }
1154
+ end
1155
+ end
1156
+ end
1157
+
1158
+ context "when the match was an array of options" do
1159
+ let :expected_grxml do
1160
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1161
+ rule id: 'options', scope: 'public' do
1162
+ item do
1163
+ one_of do
1164
+ item do
1165
+ tag { '0' }
1166
+ '0'
1167
+ end
1168
+ item do
1169
+ tag { '1' }
1170
+ '1'
1171
+ end
1172
+ item do
1173
+ tag { '2' }
1174
+ one_of do
1175
+ item { '2' }
1176
+ item { '3' }
1177
+ end
1178
+ end
1179
+ end
1180
+ end
1181
+ end
1182
+ end
1183
+ end
1184
+
1185
+ it "invokes the match payload" do
1186
+ expect_component_execution expected_prompt
1187
+ expect(self).to receive(:do_something_on_match).once.with('3')
1188
+
1189
+ subject.menu prompts do
1190
+ match(0) {}
1191
+ match(1) {}
1192
+ match([2,3]) { |v| do_something_on_match v }
1193
+ end
1194
+ end
1195
+ end
1196
+ end
1197
+
1198
+ context "when the input abmiguously matches multiple specified matches" do
1199
+ let :expected_grxml do
1200
+ RubySpeech::GRXML.draw mode: 'dtmf', root: 'options', tag_format: 'semantics/1.0-literals' do
1201
+ rule id: 'options', scope: 'public' do
1202
+ item do
1203
+ one_of do
1204
+ item do
1205
+ tag { '0' }
1206
+ '1'
1207
+ end
1208
+ item do
1209
+ tag { '1' }
1210
+ '1'
1211
+ end
1212
+ end
1213
+ end
1214
+ end
1215
+ end
1216
+ end
1217
+
1218
+ let :nlsml do
1219
+ RubySpeech::NLSML.draw do
1220
+ interpretation confidence: 1 do
1221
+ input '1', mode: :dtmf
1222
+ instance '0'
1223
+ instance '1'
1224
+ end
1225
+ end
1226
+ end
1227
+
1228
+ let(:reason) { Adhearsion::Rayo::Component::Input::Complete::Match.new nlsml: nlsml }
1229
+
1230
+ it "executes the first successful match" do
1231
+ expect_component_execution expected_prompt
1232
+ expect(self).to receive(:do_something_on_match).once.with('1')
1233
+ expect(self).to receive(:do_otherthing_on_match).never
1234
+
1235
+ subject.menu prompts do
1236
+ match(1) { |v| do_something_on_match v }
1237
+ match(1) { |v| do_otherthing_on_match v }
1238
+ end
1239
+ end
1240
+ end
1241
+ end
1242
+ end
1243
+ end
340
1244
  end
341
1245
  end