punchblock 1.9.4 → 2.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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +1 -0
  6. data/Guardfile +4 -0
  7. data/README.markdown +6 -0
  8. data/Rakefile +16 -0
  9. data/benchmarks/ami_event_name_comparison.rb +14 -0
  10. data/benchmarks/channel.rb +27 -0
  11. data/lib/punchblock/client.rb +2 -6
  12. data/lib/punchblock/command/accept.rb +3 -24
  13. data/lib/punchblock/command/answer.rb +3 -24
  14. data/lib/punchblock/command/dial.rb +24 -76
  15. data/lib/punchblock/command/hangup.rb +3 -19
  16. data/lib/punchblock/command/join.rb +21 -70
  17. data/lib/punchblock/command/mute.rb +3 -3
  18. data/lib/punchblock/command/redirect.rb +6 -39
  19. data/lib/punchblock/command/reject.rb +14 -54
  20. data/lib/punchblock/command/unjoin.rb +8 -40
  21. data/lib/punchblock/command/unmute.rb +3 -3
  22. data/lib/punchblock/command_node.rb +0 -17
  23. data/lib/punchblock/component/asterisk/agi/command.rb +20 -127
  24. data/lib/punchblock/component/asterisk/ami/action.rb +30 -117
  25. data/lib/punchblock/component/component_node.rb +1 -1
  26. data/lib/punchblock/component/input.rb +89 -268
  27. data/lib/punchblock/component/output.rb +106 -154
  28. data/lib/punchblock/component/prompt.rb +51 -0
  29. data/lib/punchblock/component/record.rb +41 -130
  30. data/lib/punchblock/component.rb +1 -0
  31. data/lib/punchblock/connection/asterisk.rb +31 -4
  32. data/lib/punchblock/connection/xmpp.rb +6 -14
  33. data/lib/punchblock/core_ext/blather/stanza.rb +1 -1
  34. data/lib/punchblock/event/active_speaker.rb +2 -10
  35. data/lib/punchblock/event/answered.rb +3 -3
  36. data/lib/punchblock/event/asterisk/ami/event.rb +15 -47
  37. data/lib/punchblock/event/complete.rb +26 -48
  38. data/lib/punchblock/event/dtmf.rb +3 -13
  39. data/lib/punchblock/event/end.rb +10 -11
  40. data/lib/punchblock/event/joined.rb +5 -25
  41. data/lib/punchblock/event/offer.rb +4 -25
  42. data/lib/punchblock/event/ringing.rb +3 -3
  43. data/lib/punchblock/event/unjoined.rb +5 -25
  44. data/lib/punchblock/event.rb +0 -10
  45. data/lib/punchblock/has_headers.rb +20 -26
  46. data/lib/punchblock/rayo_node.rb +46 -23
  47. data/lib/punchblock/ref.rb +39 -18
  48. data/lib/punchblock/translator/asterisk/agi_app.rb +15 -0
  49. data/lib/punchblock/translator/asterisk/agi_command.rb +3 -1
  50. data/lib/punchblock/translator/asterisk/ami_error_converter.rb +20 -0
  51. data/lib/punchblock/translator/asterisk/call.rb +60 -39
  52. data/lib/punchblock/translator/asterisk/channel.rb +41 -0
  53. data/lib/punchblock/translator/asterisk/component/asterisk/agi_command.rb +4 -1
  54. data/lib/punchblock/translator/asterisk/component/asterisk/ami_action.rb +4 -4
  55. data/lib/punchblock/translator/asterisk/component/composed_prompt.rb +62 -0
  56. data/lib/punchblock/translator/asterisk/component/input.rb +1 -0
  57. data/lib/punchblock/translator/asterisk/component/mrcp_native_prompt.rb +56 -0
  58. data/lib/punchblock/translator/asterisk/component/mrcp_prompt.rb +53 -0
  59. data/lib/punchblock/translator/asterisk/component/mrcp_recog_prompt.rb +99 -0
  60. data/lib/punchblock/translator/asterisk/component/output.rb +30 -22
  61. data/lib/punchblock/translator/asterisk/component/record.rb +8 -6
  62. data/lib/punchblock/translator/asterisk/component.rb +6 -5
  63. data/lib/punchblock/translator/asterisk/unimrcp_app.rb +26 -0
  64. data/lib/punchblock/translator/asterisk.rb +24 -28
  65. data/lib/punchblock/translator/dtmf_recognizer.rb +39 -20
  66. data/lib/punchblock/translator/freeswitch/call.rb +15 -14
  67. data/lib/punchblock/translator/freeswitch/component/abstract_output.rb +5 -4
  68. data/lib/punchblock/translator/freeswitch/component/flite_output.rb +1 -1
  69. data/lib/punchblock/translator/freeswitch/component/input.rb +5 -0
  70. data/lib/punchblock/translator/freeswitch/component/output.rb +2 -2
  71. data/lib/punchblock/translator/freeswitch/component/record.rb +19 -13
  72. data/lib/punchblock/translator/freeswitch/component/tts_output.rb +2 -2
  73. data/lib/punchblock/translator/freeswitch/component.rb +2 -5
  74. data/lib/punchblock/translator/freeswitch.rb +2 -2
  75. data/lib/punchblock/translator/input_component.rb +33 -13
  76. data/lib/punchblock/uri_list.rb +21 -0
  77. data/lib/punchblock/version.rb +1 -1
  78. data/lib/punchblock.rb +4 -3
  79. data/punchblock.gemspec +7 -3
  80. data/spec/punchblock/client/component_registry_spec.rb +1 -1
  81. data/spec/punchblock/client_spec.rb +10 -26
  82. data/spec/punchblock/command/accept_spec.rb +41 -7
  83. data/spec/punchblock/command/answer_spec.rb +51 -7
  84. data/spec/punchblock/command/dial_spec.rb +56 -14
  85. data/spec/punchblock/command/hangup_spec.rb +41 -7
  86. data/spec/punchblock/command/join_spec.rb +53 -11
  87. data/spec/punchblock/command/mute_spec.rb +19 -4
  88. data/spec/punchblock/command/redirect_spec.rb +40 -10
  89. data/spec/punchblock/command/reject_spec.rb +43 -11
  90. data/spec/punchblock/command/unjoin_spec.rb +40 -9
  91. data/spec/punchblock/command/unmute_spec.rb +19 -4
  92. data/spec/punchblock/command_node_spec.rb +0 -4
  93. data/spec/punchblock/component/asterisk/agi/command_spec.rb +16 -39
  94. data/spec/punchblock/component/asterisk/ami/action_spec.rb +50 -53
  95. data/spec/punchblock/component/component_node_spec.rb +3 -5
  96. data/spec/punchblock/component/input_spec.rb +194 -61
  97. data/spec/punchblock/component/output_spec.rb +194 -62
  98. data/spec/punchblock/component/prompt_spec.rb +132 -0
  99. data/spec/punchblock/component/record_spec.rb +70 -32
  100. data/spec/punchblock/connection/asterisk_spec.rb +17 -3
  101. data/spec/punchblock/connection/freeswitch_spec.rb +4 -4
  102. data/spec/punchblock/connection/xmpp_spec.rb +20 -38
  103. data/spec/punchblock/event/answered_spec.rb +12 -10
  104. data/spec/punchblock/event/asterisk/ami/event_spec.rb +27 -22
  105. data/spec/punchblock/event/complete_spec.rb +15 -19
  106. data/spec/punchblock/event/dtmf_spec.rb +5 -6
  107. data/spec/punchblock/event/end_spec.rb +20 -10
  108. data/spec/punchblock/event/joined_spec.rb +8 -7
  109. data/spec/punchblock/event/offer_spec.rb +41 -12
  110. data/spec/punchblock/event/ringing_spec.rb +12 -10
  111. data/spec/punchblock/event/started_speaking_spec.rb +5 -6
  112. data/spec/punchblock/event/stopped_speaking_spec.rb +5 -6
  113. data/spec/punchblock/event/unjoined_spec.rb +7 -7
  114. data/spec/punchblock/ref_spec.rb +86 -9
  115. data/spec/punchblock/translator/asterisk/call_spec.rb +317 -154
  116. data/spec/punchblock/translator/asterisk/component/asterisk/agi_command_spec.rb +28 -5
  117. data/spec/punchblock/translator/asterisk/component/asterisk/ami_action_spec.rb +15 -13
  118. data/spec/punchblock/translator/asterisk/component/composed_prompt_spec.rb +237 -0
  119. data/spec/punchblock/translator/asterisk/component/input_spec.rb +171 -14
  120. data/spec/punchblock/translator/asterisk/component/mrcp_native_prompt_spec.rb +652 -0
  121. data/spec/punchblock/translator/asterisk/component/mrcp_prompt_spec.rb +646 -0
  122. data/spec/punchblock/translator/asterisk/component/output_spec.rb +127 -77
  123. data/spec/punchblock/translator/asterisk/component/record_spec.rb +17 -8
  124. data/spec/punchblock/translator/asterisk/component/stop_by_redirect_spec.rb +2 -2
  125. data/spec/punchblock/translator/asterisk/component_spec.rb +3 -7
  126. data/spec/punchblock/translator/asterisk_spec.rb +20 -24
  127. data/spec/punchblock/translator/freeswitch/call_spec.rb +103 -99
  128. data/spec/punchblock/translator/freeswitch/component/flite_output_spec.rb +17 -8
  129. data/spec/punchblock/translator/freeswitch/component/input_spec.rb +26 -14
  130. data/spec/punchblock/translator/freeswitch/component/output_spec.rb +30 -52
  131. data/spec/punchblock/translator/freeswitch/component/record_spec.rb +23 -19
  132. data/spec/punchblock/translator/freeswitch/component/tts_output_spec.rb +18 -8
  133. data/spec/punchblock/translator/freeswitch/component_spec.rb +4 -8
  134. data/spec/punchblock/translator/freeswitch_spec.rb +11 -14
  135. data/spec/punchblock/uri_list_spec.rb +49 -0
  136. data/spec/punchblock_spec.rb +11 -1
  137. data/spec/spec_helper.rb +7 -11
  138. data/spec/support/mock_connection_with_event_handler.rb +1 -1
  139. metadata +104 -24
  140. data/lib/punchblock/header.rb +0 -9
  141. data/lib/punchblock/key_value_pair_node.rb +0 -51
  142. data/spec/punchblock/header_spec.rb +0 -11
@@ -8,7 +8,7 @@ module Punchblock
8
8
  private
9
9
 
10
10
  def document
11
- @component_node.ssml.inner_text.to_s
11
+ @component_node.render_documents.first.value.inner_text.to_s
12
12
  end
13
13
  end
14
14
  end
@@ -8,6 +8,11 @@ module Punchblock
8
8
 
9
9
  include InputComponent
10
10
 
11
+ def execute
12
+ super
13
+ @dtmf_handler_id = register_dtmf_event_handler
14
+ end
15
+
11
16
  private
12
17
 
13
18
  def register_dtmf_event_handler
@@ -17,7 +17,7 @@ module Punchblock
17
17
  end
18
18
 
19
19
  def filenames
20
- @filenames ||= @component_node.ssml.children.map do |node|
20
+ @filenames ||= @component_node.render_documents.first.value.children.map do |node|
21
21
  case node
22
22
  when RubySpeech::SSML::Audio
23
23
  node.src
@@ -42,7 +42,7 @@ module Punchblock
42
42
  def complete_reason_for_event(event)
43
43
  case event[:application_response]
44
44
  when 'FILE PLAYED'
45
- success_reason
45
+ finish_reason
46
46
  else
47
47
  Punchblock::Event::Complete::Error.new(:details => "Engine error: #{event[:application_response]}")
48
48
  end
@@ -13,11 +13,11 @@ module Punchblock
13
13
 
14
14
  def execute
15
15
  max_duration = @component_node.max_duration || -1
16
+ initial_timeout = @component_node.initial_timeout || -1
17
+ final_timeout = @component_node.final_timeout || -1
16
18
 
17
19
  raise OptionError, 'A start-beep value of true is unsupported.' if @component_node.start_beep
18
20
  raise OptionError, 'A start-paused value of true is unsupported.' if @component_node.start_paused
19
- raise OptionError, 'An initial-timeout value is unsupported.' if @component_node.initial_timeout && @component_node.initial_timeout != -1
20
- raise OptionError, 'A final-timeout value is unsupported.' if @component_node.final_timeout && @component_node.final_timeout != -1
21
21
  raise OptionError, 'A max-duration value that is negative (and not -1) is invalid.' unless max_duration >= -1
22
22
 
23
23
  @format = @component_node.format || 'wav'
@@ -29,16 +29,18 @@ module Punchblock
29
29
 
30
30
  record_args = ['start', filename]
31
31
  record_args << max_duration/1000 unless max_duration == -1
32
- case @component_node.direction
33
- when :send
34
- call.uuid_foo :setvar, "RECORD_WRITE_ONLY true"
35
- when :recv
36
- call.uuid_foo :setvar, "RECORD_READ_ONLY true"
37
- else
38
- call.uuid_foo :setvar, "RECORD_STEREO true"
32
+
33
+ direction = case @component_node.direction
34
+ when :send then :RECORD_WRITE_ONLY
35
+ when :recv then :RECORD_READ_ONLY
36
+ else :RECORD_STEREO
39
37
  end
40
- call.uuid_foo :record, record_args.join(' ')
38
+ setvar direction, true
39
+
40
+ setvar :RECORD_INITIAL_TIMEOUT_MS, initial_timeout > -1 ? initial_timeout : 0
41
+ setvar :RECORD_FINAL_TIMEOUT_MS, final_timeout > -1 ? final_timeout : 0
41
42
 
43
+ call.uuid_foo :record, record_args.join(' ')
42
44
  send_ref
43
45
  rescue OptionError => e
44
46
  with_error 'option error', e.message
@@ -56,11 +58,15 @@ module Punchblock
56
58
  end
57
59
 
58
60
  def finished
59
- send_complete_event(@complete_reason || success_reason)
61
+ send_complete_event(@complete_reason || max_duration_reason)
60
62
  end
61
63
 
62
64
  private
63
65
 
66
+ def setvar(key, value)
67
+ call.uuid_foo :setvar, "#{key} #{value}"
68
+ end
69
+
64
70
  def filename
65
71
  File.join RECORDING_BASE_PATH, [id, @format].join('.')
66
72
  end
@@ -73,8 +79,8 @@ module Punchblock
73
79
  Punchblock::Event::Complete::Stop.new
74
80
  end
75
81
 
76
- def success_reason
77
- Punchblock::Component::Record::Complete::Success.new
82
+ def max_duration_reason
83
+ Punchblock::Component::Record::Complete::MaxDuration.new
78
84
  end
79
85
 
80
86
  def send_complete_event(reason)
@@ -9,14 +9,14 @@ module Punchblock
9
9
 
10
10
  def do_output(engine, default_voice = nil)
11
11
  register_handler :es, :event_name => 'CHANNEL_EXECUTE_COMPLETE' do |event|
12
- send_complete_event success_reason
12
+ send_complete_event finish_reason
13
13
  end
14
14
  voice = @component_node.voice || default_voice || 'kal'
15
15
  application :speak, [engine, voice, document].join('|')
16
16
  end
17
17
 
18
18
  def document
19
- @component_node.ssml.to_s
19
+ @component_node.render_documents.first.value.to_s
20
20
  end
21
21
  end
22
22
  end
@@ -45,10 +45,7 @@ module Punchblock
45
45
  def send_complete_event(reason, recording = nil)
46
46
  return if @complete
47
47
  @complete = true
48
- event = Punchblock::Event::Complete.new.tap do |c|
49
- c.reason = reason
50
- c << recording if recording
51
- end
48
+ event = Punchblock::Event::Complete.new reason: reason, recording: recording
52
49
  send_event event
53
50
  terminate
54
51
  end
@@ -82,7 +79,7 @@ module Punchblock
82
79
  end
83
80
 
84
81
  def send_ref
85
- set_node_response Ref.new :id => id
82
+ set_node_response Ref.new uri: id
86
83
  end
87
84
 
88
85
  def with_error(name, text)
@@ -34,8 +34,8 @@ module Punchblock
34
34
  @calls[call.id] ||= call
35
35
  end
36
36
 
37
- def deregister_call(call)
38
- @calls.delete call.id
37
+ def deregister_call(id)
38
+ @calls.delete id
39
39
  end
40
40
 
41
41
  def call_with_id(call_id)
@@ -5,15 +5,9 @@ module Punchblock
5
5
  module InputComponent
6
6
  def execute
7
7
  validate
8
-
9
- @recognizer = DTMFRecognizer.new self,
10
- @component_node.grammar.value,
11
- (@component_node.initial_timeout || -1),
12
- (@component_node.inter_digit_timeout || -1)
13
-
8
+ setup_dtmf_recognizer
14
9
  send_ref
15
-
16
- @dtmf_handler_id = register_dtmf_event_handler
10
+ start_timers
17
11
  rescue OptionError => e
18
12
  with_error 'option error', e.message
19
13
  end
@@ -32,8 +26,8 @@ module Punchblock
32
26
  end
33
27
  end
34
28
 
35
- def match(mode, confidence, utterance, interpretation)
36
- complete Punchblock::Component::Input::Complete::Success.new(:mode => mode, :confidence => confidence, :utterance => utterance, :interpretation => interpretation)
29
+ def match(match)
30
+ complete success_reason(match)
37
31
  end
38
32
 
39
33
  def nomatch
@@ -46,14 +40,40 @@ module Punchblock
46
40
 
47
41
  private
48
42
 
43
+ def input_node
44
+ @component_node
45
+ end
46
+
49
47
  def validate
50
- raise OptionError, 'A grammar document is required.' unless @component_node.grammar
51
- raise OptionError, 'A mode value other than DTMF is unsupported.' unless @component_node.mode == :dtmf
48
+ raise OptionError, 'A grammar document is required.' unless input_node.grammars.first
49
+ raise OptionError, 'Only a single grammar is supported.' unless input_node.grammars.size == 1
50
+ raise OptionError, 'A mode value other than DTMF is unsupported.' unless input_node.mode == :dtmf
51
+ end
52
+
53
+ def setup_dtmf_recognizer
54
+ @recognizer = DTMFRecognizer.new self,
55
+ input_node.grammars.first.value,
56
+ (input_node.initial_timeout || -1),
57
+ (input_node.inter_digit_timeout || -1),
58
+ input_node.terminator
59
+ end
60
+
61
+ def start_timers
62
+ @recognizer.start_timers
63
+ end
64
+
65
+ def success_reason(match)
66
+ nlsml = RubySpeech::NLSML.draw do
67
+ interpretation confidence: match.confidence do
68
+ instance match.interpretation
69
+ input match.utterance, mode: match.mode
70
+ end
71
+ end
72
+ Punchblock::Component::Input::Complete::Match.new :nlsml => nlsml
52
73
  end
53
74
 
54
75
  def complete(reason)
55
76
  unregister_dtmf_event_handler
56
- @recognizer.finalize if @recognizer
57
77
  send_complete_event reason
58
78
  end
59
79
  end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ module Punchblock
4
+ class URIList < SimpleDelegator
5
+ def self.import(string)
6
+ new string.strip.split("\n").map(&:strip)
7
+ end
8
+
9
+ def initialize(*list)
10
+ super list.flatten
11
+ end
12
+
13
+ def to_s
14
+ join("\n")
15
+ end
16
+
17
+ def ==(other)
18
+ self.__getobj__ == other.to_ary
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Punchblock
4
- VERSION = "1.9.4"
4
+ VERSION = "2.0.0.beta1"
5
5
  end
data/lib/punchblock.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  active_support/dependencies/autoload
5
5
  active_support/core_ext/object/blank
6
6
  active_support/core_ext/module/delegation
7
+ active_support/inflector
7
8
  future-resource
8
9
  has_guarded_handlers
9
10
  ruby_speech
@@ -22,11 +23,11 @@ module Punchblock
22
23
  autoload :DeadActorSafety
23
24
  autoload :DisconnectedError
24
25
  autoload :HasHeaders
25
- autoload :Header
26
26
  autoload :MediaNode
27
27
  autoload :ProtocolError
28
28
  autoload :RayoNode
29
29
  autoload :Translator
30
+ autoload :URIList
30
31
 
31
32
  class << self
32
33
  def logger
@@ -50,7 +51,7 @@ module Punchblock
50
51
  # @return [Punchblock::Client] a punchblock client object
51
52
  #
52
53
  def client_with_connection(type, options)
53
- connection = Connection.const_get(type.to_s.classify).new options
54
+ connection = Connection.const_get(type == :xmpp ? 'XMPP' : type.to_s.classify).new options
54
55
  Client.new :connection => connection
55
56
  rescue NameError
56
57
  raise ArgumentError, "Connection type #{type.inspect} is not valid."
@@ -74,7 +75,7 @@ module Punchblock
74
75
  RAYO_VERSION = '1'
75
76
  RAYO_NAMESPACES = {:core => [BASE_RAYO_NAMESPACE, RAYO_VERSION].compact.join(':')}
76
77
 
77
- [:ext, :record, :output, :input].each do |ns|
78
+ [:ext, :record, :output, :input, :prompt].each do |ns|
78
79
  RAYO_NAMESPACES[ns] = [BASE_RAYO_NAMESPACE, ns.to_s, RAYO_VERSION].compact.join(':')
79
80
  RAYO_NAMESPACES[:"#{ns}_complete"] = [BASE_RAYO_NAMESPACE, ns.to_s, 'complete', RAYO_VERSION].compact.join(':')
80
81
  end
data/punchblock.gemspec CHANGED
@@ -22,17 +22,18 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.required_rubygems_version = Gem::Requirement.new(">= 1.3.7") if s.respond_to? :required_rubygems_version=
24
24
 
25
- s.add_runtime_dependency %q<niceogiri>, ["~> 1.1"]
26
25
  s.add_runtime_dependency %q<nokogiri>, ["~> 1.5", ">= 1.5.6"]
27
26
  s.add_runtime_dependency %q<blather>, [">= 0.7.0"]
28
- s.add_runtime_dependency %q<activesupport>, ["~> 3.0"]
27
+ s.add_runtime_dependency %q<activesupport>, [">= 3.0.0", "< 5.0.0"]
29
28
  s.add_runtime_dependency %q<state_machine>, ["~> 1.0"]
30
29
  s.add_runtime_dependency %q<future-resource>, ["~> 1.0"]
31
30
  s.add_runtime_dependency %q<has-guarded-handlers>, ["~> 1.5"]
32
31
  s.add_runtime_dependency %q<celluloid>, ["~> 0.14"]
33
32
  s.add_runtime_dependency %q<ruby_ami>, ["~> 2.0"]
34
33
  s.add_runtime_dependency %q<ruby_fs>, ["~> 1.1"]
35
- s.add_runtime_dependency %q<ruby_speech>, ["~> 1.0"]
34
+ s.add_runtime_dependency %q<ruby_speech>, ["~> 2.0"]
35
+ s.add_runtime_dependency %q<virtus>
36
+ s.add_runtime_dependency %q<ruby_jid>, ["~> 1.0"]
36
37
 
37
38
  s.add_development_dependency %q<bundler>, ["~> 1.0"]
38
39
  s.add_development_dependency %q<rspec>, ["~> 2.7"]
@@ -43,4 +44,7 @@ Gem::Specification.new do |s|
43
44
  s.add_development_dependency %q<countdownlatch>, [">= 0"]
44
45
  s.add_development_dependency %q<guard-rspec>
45
46
  s.add_development_dependency %q<rb-fsevent>, ['~> 0.9']
47
+ s.add_development_dependency %q<coveralls>, ['>= 0']
48
+ s.add_development_dependency %q<guard-rake>
49
+ s.add_development_dependency %q<benchmark_suite>
46
50
  end
@@ -6,7 +6,7 @@ module Punchblock
6
6
  class Client
7
7
  describe ComponentRegistry do
8
8
  let(:component_id) { 'abc123' }
9
- let(:component) { stub 'Component', :component_id => component_id }
9
+ let(:component) { double 'Component', :component_id => component_id }
10
10
 
11
11
  it 'should store components and allow lookup by ID' do
12
12
  subject << component
@@ -8,15 +8,14 @@ module Punchblock
8
8
 
9
9
  subject { Client.new :connection => connection }
10
10
 
11
- its(:event_queue) { should be_a Queue }
12
11
  its(:connection) { should be connection }
13
12
  its(:component_registry) { should be_a Client::ComponentRegistry }
14
13
 
15
14
  let(:call_id) { 'abc123' }
16
- let(:mock_event) { stub('Event').as_null_object }
15
+ let(:mock_event) { double('Event').as_null_object }
17
16
  let(:component_id) { 'abc123' }
18
- let(:mock_component) { stub 'Component', :component_id => component_id }
19
- let(:mock_command) { stub 'Command' }
17
+ let(:mock_component) { double 'Component', :component_id => component_id }
18
+ let(:mock_command) { double 'Command' }
20
19
 
21
20
  describe '#run' do
22
21
  it 'should start up the connection' do
@@ -50,13 +49,8 @@ module Punchblock
50
49
  mock_component.should_receive(:add_event).with mock_event
51
50
  end
52
51
 
53
- it 'should not queue up the event' do
54
- subject.handle_event mock_event
55
- subject.event_queue.should be_empty
56
- end
57
-
58
52
  it 'should not call event handlers' do
59
- handler = mock 'handler'
53
+ handler = double 'handler'
60
54
  handler.should_receive(:call).never
61
55
  subject.register_event_handler do |event|
62
56
  handler.call event
@@ -70,23 +64,13 @@ module Punchblock
70
64
  mock_event.stub :source => nil
71
65
  end
72
66
 
73
- context 'if event handlers have been set' do
74
- it 'should call the event handler and not queue up the event' do
75
- handler = mock 'handler'
76
- handler.should_receive(:call).once.with mock_event
77
- subject.register_event_handler do |event|
78
- handler.call event
79
- end
80
- subject.handle_event mock_event
81
- subject.event_queue.should be_empty
82
- end
83
- end
84
-
85
- context 'if event handlers have not been set' do
86
- it 'should queue up the event' do
87
- subject.handle_event mock_event
88
- subject.event_queue.pop(true).should be == mock_event
67
+ it 'should call registered event handlers' do
68
+ handler = double 'handler'
69
+ handler.should_receive(:call).once.with mock_event
70
+ subject.register_event_handler do |event|
71
+ handler.call event
89
72
  end
73
+ subject.handle_event mock_event
90
74
  end
91
75
  end
92
76
  end
@@ -6,18 +6,52 @@ module Punchblock
6
6
  module Command
7
7
  describe Accept do
8
8
  it 'registers itself' do
9
- RayoNode.class_from_registration(:accept, 'urn:xmpp:rayo:1').should be == Accept
9
+ RayoNode.class_from_registration(:accept, 'urn:xmpp:rayo:1').should be == described_class
10
10
  end
11
11
 
12
- it_should_behave_like 'command_headers'
13
-
14
12
  describe "from a stanza" do
15
- let(:stanza) { '<accept xmlns="urn:xmpp:rayo:1"/>' }
13
+ let(:stanza) do
14
+ <<-STANZA
15
+ <accept xmlns="urn:xmpp:rayo:1">
16
+ <header name="X-skill" value="agent" />
17
+ <header name="X-customer-id" value="8877" />
18
+ </accept>
19
+ STANZA
20
+ end
21
+
22
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
23
+
24
+ it { should be_instance_of described_class }
25
+ its(:headers) { should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
26
+
27
+ context "with no headers provided" do
28
+ let(:stanza) { '<accept xmlns="urn:xmpp:rayo:1"/>' }
29
+
30
+ its(:headers) { should == {} }
31
+ end
32
+ end
33
+
34
+ describe "when setting options in initializer" do
35
+ subject { described_class.new headers: { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
36
+
37
+ its(:headers) { should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
16
38
 
17
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
39
+ describe "exporting to Rayo" do
40
+ it "should export to XML that can be understood by its parser" do
41
+ new_instance = RayoNode.from_xml subject.to_rayo
42
+ new_instance.should be_instance_of described_class
43
+ new_instance.headers.should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' }
44
+ end
18
45
 
19
- it { should be_instance_of Accept }
46
+ it "should render to a parent node if supplied" do
47
+ doc = Nokogiri::XML::Document.new
48
+ parent = Nokogiri::XML::Node.new 'foo', doc
49
+ doc.root = parent
50
+ rayo_doc = subject.to_rayo(parent)
51
+ rayo_doc.should == parent
52
+ end
53
+ end
20
54
  end
21
55
  end
22
56
  end
23
- end # Punchblock
57
+ end
@@ -6,18 +6,62 @@ module Punchblock
6
6
  module Command
7
7
  describe Answer do
8
8
  it 'registers itself' do
9
- RayoNode.class_from_registration(:answer, 'urn:xmpp:rayo:1').should be == Answer
9
+ RayoNode.class_from_registration(:answer, 'urn:xmpp:rayo:1').should be == described_class
10
10
  end
11
11
 
12
- it_should_behave_like 'command_headers'
13
-
14
12
  describe "from a stanza" do
15
- let(:stanza) { '<answer xmlns="urn:xmpp:rayo:1"/>' }
13
+ let(:stanza) do
14
+ <<-STANZA
15
+ <answer xmlns="urn:xmpp:rayo:1">
16
+ <header name="X-skill" value="agent" />
17
+ <header name="X-customer-id" value="8877" />
18
+ </answer>
19
+ STANZA
20
+ end
21
+
22
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
23
+
24
+ it { should be_instance_of described_class }
25
+ its(:headers) { should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
26
+
27
+ context "with no headers provided" do
28
+ let(:stanza) { '<answer xmlns="urn:xmpp:rayo:1"/>' }
29
+
30
+ its(:headers) { should == {} }
31
+ end
32
+ end
33
+
34
+ describe "when setting options in initializer" do
35
+ subject { described_class.new headers: { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
36
+
37
+ its(:headers) { should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
38
+
39
+ describe "exporting to Rayo" do
40
+ it "should export to XML that can be understood by its parser" do
41
+ new_instance = RayoNode.from_xml subject.to_rayo
42
+ new_instance.should be_instance_of described_class
43
+ new_instance.headers.should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' }
44
+ end
45
+
46
+ it "should render to a parent node if supplied" do
47
+ doc = Nokogiri::XML::Document.new
48
+ parent = Nokogiri::XML::Node.new 'foo', doc
49
+ doc.root = parent
50
+ rayo_doc = subject.to_rayo(parent)
51
+ rayo_doc.should == parent
52
+ end
16
53
 
17
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
54
+ context "with multiple headers of the same name" do
55
+ subject { described_class.new headers: { 'X-skill' => ['sales', 'complaints'] } }
18
56
 
19
- it { should be_instance_of Answer }
57
+ it "should export to XML that can be understood by its parser" do
58
+ new_instance = RayoNode.from_xml subject.to_rayo
59
+ new_instance.should be_instance_of described_class
60
+ new_instance.headers.should == { 'X-skill' => ['sales', 'complaints'] }
61
+ end
62
+ end
63
+ end
20
64
  end
21
65
  end
22
66
  end
23
- end # Punchblock
67
+ end
@@ -7,61 +7,103 @@ module Punchblock
7
7
  describe Dial do
8
8
 
9
9
  it 'registers itself' do
10
- RayoNode.class_from_registration(:dial, 'urn:xmpp:rayo:1').should be == Dial
10
+ RayoNode.class_from_registration(:dial, 'urn:xmpp:rayo:1').should be == described_class
11
11
  end
12
12
 
13
- let(:join_params) { {:call_id => 'abc123'} }
13
+ let(:join_params) { {:call_uri => 'abc123'} }
14
14
 
15
15
  describe "when setting options in initializer" do
16
- subject { Dial.new :to => 'tel:+14155551212', :from => 'tel:+13035551212', :timeout => 30000, :headers => { :x_skill => 'agent', :x_customer_id => 8877 }, :join => join_params }
17
-
18
- it_should_behave_like 'command_headers'
16
+ subject { described_class.new to: 'tel:+14155551212', from: 'tel:+13035551212', timeout: 30000, headers: { 'X-skill' => 'agent', 'X-customer-id' => '8877' }, join: join_params }
19
17
 
20
18
  its(:to) { should be == 'tel:+14155551212' }
21
19
  its(:from) { should be == 'tel:+13035551212' }
22
20
  its(:timeout) { should be == 30000 }
23
21
  its(:join) { should be == Join.new(join_params) }
22
+ its(:headers) { should be == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
23
+
24
+ describe "exporting to Rayo" do
25
+ it "should export to XML that can be understood by its parser" do
26
+ new_instance = RayoNode.from_xml subject.to_rayo
27
+ new_instance.should be_instance_of described_class
28
+ new_instance.to.should == 'tel:+14155551212'
29
+ new_instance.from.should == 'tel:+13035551212'
30
+ new_instance.timeout.should == 30000
31
+ new_instance.join.should == Join.new(join_params)
32
+ new_instance.headers.should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' }
33
+ end
34
+
35
+ it "should render to a parent node if supplied" do
36
+ doc = Nokogiri::XML::Document.new
37
+ parent = Nokogiri::XML::Node.new 'foo', doc
38
+ doc.root = parent
39
+ rayo_doc = subject.to_rayo(parent)
40
+ rayo_doc.should == parent
41
+ end
42
+
43
+ context "when attributes are not set" do
44
+ subject { described_class.new to: 'abc123' }
45
+
46
+ it "should not include them in the XML representation" do
47
+ subject.to_rayo['to'].should == 'abc123'
48
+ subject.to_rayo['from'].should be_nil
49
+ end
50
+ end
51
+ end
24
52
  end
25
53
 
26
54
  describe "from a stanza" do
27
55
  let :stanza do
28
56
  <<-MESSAGE
29
57
  <dial to='tel:+14155551212' from='tel:+13035551212' timeout='30000' xmlns='urn:xmpp:rayo:1'>
30
- <join call-id="abc123" />
58
+ <join call-uri="abc123" />
31
59
  <header name="X-skill" value="agent" />
32
60
  <header name="X-customer-id" value="8877" />
33
61
  </dial>
34
62
  MESSAGE
35
63
  end
36
64
 
37
- subject { RayoNode.import parse_stanza(stanza).root, '9f00061', '1' }
65
+ subject { RayoNode.from_xml parse_stanza(stanza).root, '9f00061', '1' }
38
66
 
39
- it { should be_instance_of Dial }
40
-
41
- it_should_behave_like 'event_headers'
67
+ it { should be_instance_of described_class }
42
68
 
43
69
  its(:to) { should be == 'tel:+14155551212' }
44
70
  its(:from) { should be == 'tel:+13035551212' }
45
71
  its(:timeout) { should be == 30000 }
46
72
  its(:join) { should be == Join.new(join_params) }
73
+ its(:headers) { should be == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
74
+
75
+ context "with no headers provided" do
76
+ let(:stanza) { '<dial xmlns="urn:xmpp:rayo:1"/>' }
77
+
78
+ its(:headers) { should == {} }
79
+ end
47
80
  end
48
81
 
49
82
  describe "#response=" do
50
83
  before { subject.request! }
51
84
 
52
85
  let(:call_id) { 'abc123' }
86
+ let(:domain) { 'rayo.net' }
53
87
 
54
88
  let :ref do
55
- Ref.new.tap do |ref|
56
- ref.id = call_id
57
- end
89
+ Ref.new uri: "xmpp:#{call_id}@#{domain}"
90
+ end
91
+
92
+ it "should set the transport from the ref" do
93
+ subject.response = ref
94
+ subject.transport.should be == 'xmpp'
58
95
  end
59
96
 
60
97
  it "should set the call ID from the ref" do
61
98
  subject.response = ref
62
99
  subject.target_call_id.should be == call_id
63
100
  end
101
+
102
+ it "should set the domain from the ref" do
103
+ subject.response = ref
104
+ subject.domain.should be == domain
105
+ end
64
106
  end
65
107
  end
66
108
  end
67
- end # Punchblock
109
+ end