punchblock 1.9.4 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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