punchblock 2.3.1 → 2.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e6afe18610ec58e92290615347d829d84df0f35
4
- data.tar.gz: 67ab1a85edc0cb9603f47739e661a6cd5f13492e
3
+ metadata.gz: b59ef9a626a919b1db35af9603f8d5d73edae6a8
4
+ data.tar.gz: 47f018bf7cc1d9ba4e6d14acb0d4bc18e64622e2
5
5
  SHA512:
6
- metadata.gz: 204717279e74f4f6c892c2a832e2e81ac947473011e2249a7b5fc3cf7847dc133ea3eb7dccf61523898a9c79ef3e913f96562414b48477c2e668d52b67b9b17e
7
- data.tar.gz: a7465ef83df1050746b6eb1c6e47b04ca8d9f68fdf29a51ebc96958940e69b868410a9459535b6e961c4206277764f0fe9bc9cfa903fb7c3dc68f1934cf40c76
6
+ metadata.gz: 8f143c3727b0bf9d205d3c9f8a09eec1eda96ad294af4dcca99b962de39c6aa47cce84ec24080a40ab4ca8ff78e244a444c66fa6571ba0e892871b475798549c
7
+ data.tar.gz: 55b015a502dc38e174c59707701046c0138ccf8e0db6afd318da38b7e7672a4271023cef5fca0f7b90bf22eaf6d2c695b6d5aee1a5acc0223a5147a410c9f77c
@@ -1,5 +1,10 @@
1
1
  # [develop](https://github.com/adhearsion/punchblock)
2
2
 
3
+ # [v2.4.0](https://github.com/adhearsion/punchblock/compare/v2.3.1...v2.4.0) - [2014-03-01](https://rubygems.org/gems/punchblock/versions/2.4.0)
4
+ * Feature: Add support for requesting calls with a specific URI
5
+ * Feature: Allow generation of a random call URI for a client
6
+ * Feature: Rayo events should be timestamped with dispatch or receipt time (#213)
7
+
3
8
  # [v2.3.1](https://github.com/adhearsion/punchblock/compare/v2.3.0...v2.3.1) - [2014-02-13](https://rubygems.org/gems/punchblock/versions/2.3.1)
4
9
  * Bugfix: Ensure commands can be associated on the wire even before they're executed
5
10
  * Bugfix: Ensure a command is always transitioned to is requested state prior to receiving a response
@@ -10,7 +10,7 @@ module Punchblock
10
10
 
11
11
  attr_reader :connection, :component_registry
12
12
 
13
- delegate :run, :stop, :send_message, :to => :connection
13
+ delegate :run, :stop, :send_message, :new_call_uri, :to => :connection
14
14
 
15
15
  # @param [Hash] options
16
16
  # @option options [Connection::XMPP] :connection The Punchblock connection to use for this session
@@ -13,6 +13,9 @@ module Punchblock
13
13
  # @return [String] the caller ID
14
14
  attribute :from
15
15
 
16
+ # @return [String] the requested URI for the resulting call
17
+ attribute :uri
18
+
16
19
  # @return [Integer] timeout in milliseconds
17
20
  attribute :timeout, Integer
18
21
 
@@ -27,7 +30,7 @@ module Punchblock
27
30
  end
28
31
 
29
32
  def rayo_attributes
30
- {to: to, from: from, timeout: timeout}
33
+ {to: to, from: from, uri: uri, timeout: timeout}
31
34
  end
32
35
 
33
36
  def rayo_children(root)
@@ -8,6 +8,7 @@ module Punchblock
8
8
  def initialize(*args)
9
9
  super
10
10
  @complete_event_resource = FutureResource.new
11
+ @mutex = Mutex.new
11
12
  register_internal_handlers
12
13
  end
13
14
 
@@ -36,12 +37,14 @@ module Punchblock
36
37
  end
37
38
 
38
39
  def response=(other)
39
- if other.is_a?(Ref)
40
- @component_id = other.component_id
41
- @source_uri = other.uri.to_s
42
- client.register_component self if client
40
+ @mutex.synchronize do
41
+ if other.is_a?(Ref)
42
+ @component_id = other.component_id
43
+ @source_uri = other.uri.to_s
44
+ client.register_component self if client
45
+ end
46
+ super
43
47
  end
44
- super
45
48
  end
46
49
 
47
50
  def complete_event(timeout = nil)
@@ -49,10 +52,12 @@ module Punchblock
49
52
  end
50
53
 
51
54
  def complete_event=(other)
52
- return if @complete_event_resource.set_yet?
53
- client.delete_component_registration self if client
54
- complete!
55
- @complete_event_resource.resource = other
55
+ @mutex.synchronize do
56
+ return if @complete_event_resource.set_yet?
57
+ client.delete_component_registration self if client
58
+ complete!
59
+ @complete_event_resource.resource = other
60
+ end
56
61
  rescue StateMachine::InvalidTransition => e
57
62
  e.message << " for component #{self}"
58
63
  raise e
@@ -45,6 +45,10 @@ module Punchblock
45
45
  ami_client.async.run
46
46
  Celluloid::Actor.join(ami_client)
47
47
  end
48
+
49
+ def new_call_uri
50
+ Punchblock.new_uuid
51
+ end
48
52
  end
49
53
 
50
54
  class RubyAMIStreamProxy
@@ -109,6 +109,10 @@ module Punchblock
109
109
  super
110
110
  end
111
111
 
112
+ def new_call_uri
113
+ "xmpp:#{Punchblock.new_uuid}@#{root_domain}"
114
+ end
115
+
112
116
  private
113
117
 
114
118
  def jid_for_command(command)
@@ -10,7 +10,7 @@ module Blather
10
10
  def rayo_node
11
11
  @rayo_node ||= begin
12
12
  first_child = at_xpath RAYO_NODE_PATH, Punchblock::RAYO_NAMESPACES
13
- Punchblock::RayoNode.from_xml first_child, nil, component_id, "xmpp:#{from}" if first_child
13
+ Punchblock::RayoNode.from_xml first_child, nil, component_id, "xmpp:#{from}", delay_timestamp if first_child
14
14
  end
15
15
  end
16
16
 
@@ -27,5 +27,13 @@ module Blather
27
27
  def component_id
28
28
  from.resource
29
29
  end
30
+
31
+ private
32
+
33
+ def delay_timestamp
34
+ if delay = self.at_xpath('ns:delay', ns: 'urn:xmpp:delay')
35
+ DateTime.parse(delay[:stamp])
36
+ end
37
+ end
30
38
  end
31
39
  end
@@ -17,6 +17,7 @@ module Punchblock
17
17
  attribute :source_uri
18
18
  attribute :domain
19
19
  attribute :transport
20
+ attribute :timestamp, DateTime, default: ->(*) { DateTime.now }
20
21
 
21
22
  attr_accessor :connection, :client, :original_component
22
23
 
@@ -49,7 +50,7 @@ module Punchblock
49
50
  # elements of the XML::Node
50
51
  # @param [XML::Node] node the node to import
51
52
  # @return the appropriate object based on the node name and namespace
52
- def self.from_xml(node, call_id = nil, component_id = nil, uri = nil)
53
+ def self.from_xml(node, call_id = nil, component_id = nil, uri = nil, timestamp = nil)
53
54
  ns = (node.namespace.href if node.namespace)
54
55
  klass = class_from_registration(node.name, ns)
55
56
  if klass && klass != self
@@ -60,6 +61,7 @@ module Punchblock
60
61
  event.target_call_id = call_id
61
62
  event.component_id = component_id
62
63
  event.source_uri = uri
64
+ event.timestamp = timestamp if timestamp
63
65
  end
64
66
  end
65
67
 
@@ -140,9 +140,13 @@ module Punchblock
140
140
  register_component component
141
141
  component.execute
142
142
  when Punchblock::Command::Dial
143
- call = Call.new command.to, current_actor, ami_client, connection
144
- register_call call
145
- call.dial command
143
+ if call = call_with_id(command.uri)
144
+ command.response = ProtocolError.new.setup(:conflict, 'Call ID already in use')
145
+ else
146
+ call = Call.new command.to, current_actor, ami_client, connection, nil, command.uri
147
+ register_call call
148
+ call.dial command
149
+ end
146
150
  else
147
151
  command.response = ProtocolError.new.setup 'command-not-acceptable', "Did not understand command"
148
152
  end
@@ -25,10 +25,11 @@ module Punchblock
25
25
  HANGUP_CAUSE_TO_END_REASON[22] = :reject
26
26
  HANGUP_CAUSE_TO_END_REASON[102] = :timeout
27
27
 
28
- def initialize(channel, translator, ami_client, connection, agi_env = nil)
28
+ def initialize(channel, translator, ami_client, connection, agi_env = nil, id = nil)
29
29
  @channel, @translator, @ami_client, @connection = channel, translator, ami_client, connection
30
30
  @agi_env = agi_env || {}
31
- @id, @components = Punchblock.new_uuid, {}
31
+ @id = id || Punchblock.new_uuid
32
+ @components = {}
32
33
  @answered = false
33
34
  @pending_joins = {}
34
35
  @progress_sent = false
@@ -113,7 +114,7 @@ module Punchblock
113
114
 
114
115
  case ami_event.name
115
116
  when 'Hangup'
116
- handle_hangup_event ami_event['Cause'].to_i
117
+ handle_hangup_event ami_event['Cause'].to_i, ami_event.best_time
117
118
  when 'AsyncAGI'
118
119
  if component = component_with_id(ami_event['CommandID'])
119
120
  component.handle_ami_event ami_event
@@ -121,16 +122,16 @@ module Punchblock
121
122
 
122
123
  if @answered == false && ami_event['SubEvent'] == 'Start'
123
124
  @answered = true
124
- send_pb_event Event::Answered.new
125
+ send_pb_event Event::Answered.new(timestamp: ami_event.best_time)
125
126
  end
126
127
  when 'Newstate'
127
128
  case ami_event['ChannelState']
128
129
  when '5'
129
- send_pb_event Event::Ringing.new
130
+ send_pb_event Event::Ringing.new(timestamp: ami_event.best_time)
130
131
  end
131
132
  when 'OriginateResponse'
132
133
  if ami_event['Response'] == 'Failure' && ami_event['Uniqueid'] == '<null>'
133
- send_end_event :error
134
+ send_end_event :error, nil, ami_event.best_time
134
135
  end
135
136
  when 'BridgeExec'
136
137
  join_command = @pending_joins.delete ami_event['Channel1']
@@ -141,16 +142,16 @@ module Punchblock
141
142
  if other_call = translator.call_for_channel(other_call_channel)
142
143
  event = case ami_event['Bridgestate']
143
144
  when 'Link'
144
- Event::Joined.new call_uri: other_call.id
145
+ Event::Joined.new call_uri: other_call.id, timestamp: ami_event.best_time
145
146
  when 'Unlink'
146
- Event::Unjoined.new call_uri: other_call.id
147
+ Event::Unjoined.new call_uri: other_call.id, timestamp: ami_event.best_time
147
148
  end
148
149
  send_pb_event event
149
150
  end
150
151
  when 'Unlink'
151
152
  other_call_channel = ([ami_event['Channel1'], ami_event['Channel2']] - [channel]).first
152
153
  if other_call = translator.call_for_channel(other_call_channel)
153
- send_pb_event Event::Unjoined.new(call_uri: other_call.id)
154
+ send_pb_event Event::Unjoined.new(call_uri: other_call.id, timestamp: ami_event.best_time)
154
155
  end
155
156
  when 'VarSet'
156
157
  @channel_variables[ami_event['Variable']] = ami_event['Value']
@@ -278,13 +279,14 @@ module Punchblock
278
279
  send_ami_action 'Redirect', redirect_options
279
280
  end
280
281
 
281
- def handle_hangup_event(code = 16)
282
+ def handle_hangup_event(code = nil, timestamp = nil)
283
+ code ||= 16
282
284
  reason = @hangup_cause || HANGUP_CAUSE_TO_END_REASON[code]
283
285
  @block_commands = true
284
286
  @components.each_pair do |id, component|
285
287
  component.call_ended
286
288
  end
287
- send_end_event reason, code
289
+ send_end_event reason, code, timestamp
288
290
  end
289
291
 
290
292
  def after(*args, &block)
@@ -306,8 +308,9 @@ module Punchblock
306
308
  AMIErrorConverter.convert { @ami_client.send_action name, headers }
307
309
  end
308
310
 
309
- def send_end_event(reason, code = nil)
310
- send_pb_event Event::End.new(reason: reason, platform_code: code)
311
+ def send_end_event(reason, code = nil, timestamp = nil)
312
+ end_event = Event::End.new(reason: reason, platform_code: code, timestamp: timestamp)
313
+ send_pb_event end_event
311
314
  translator.deregister_call id, channel
312
315
  end
313
316
 
@@ -23,7 +23,7 @@ module Punchblock
23
23
  if event.name == 'AsyncAGI' && event['SubEvent'] == 'Exec'
24
24
  send_complete_event success_reason(event)
25
25
  if @component_node.name == 'ASYNCAGI BREAK' && @call.channel_var('PUNCHBLOCK_END_ON_ASYNCAGI_BREAK')
26
- @call.handle_hangup_event
26
+ @call.handle_hangup_event nil, event.best_time
27
27
  end
28
28
  end
29
29
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Punchblock
4
- VERSION = "2.3.1"
4
+ VERSION = "2.4.0"
5
5
  end
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  s.add_runtime_dependency %q<future-resource>, ["~> 1.0"]
30
30
  s.add_runtime_dependency %q<has-guarded-handlers>, ["~> 1.5"]
31
31
  s.add_runtime_dependency %q<celluloid>, ["~> 0.14"]
32
- s.add_runtime_dependency %q<ruby_ami>, ["~> 2.0"]
32
+ s.add_runtime_dependency %q<ruby_ami>, ["~> 2.2"]
33
33
  s.add_runtime_dependency %q<ruby_fs>, ["~> 1.1"]
34
34
  s.add_runtime_dependency %q<ruby_speech>, ["~> 2.3"]
35
35
  s.add_runtime_dependency %q<virtus>, ["~> 1.0"]
@@ -40,6 +40,13 @@ module Punchblock
40
40
  end
41
41
  end
42
42
 
43
+ describe '#new_call_uri' do
44
+ it 'should return the connection-specific fresh call ID' do
45
+ stub_uuids 'foobar'
46
+ subject.new_call_uri.should == 'xmpp:foobar@call.rayo.net'
47
+ end
48
+ end
49
+
43
50
  it 'should handle connection events' do
44
51
  subject.should_receive(:handle_event).with(mock_event).once
45
52
  connection.event_handler.call mock_event
@@ -13,10 +13,11 @@ module Punchblock
13
13
  let(:join_params) { {:call_uri => 'abc123'} }
14
14
 
15
15
  describe "when setting options in initializer" do
16
- subject { described_class.new to: 'tel:+14155551212', from: 'tel:+13035551212', timeout: 30000, headers: { 'X-skill' => 'agent', 'X-customer-id' => '8877' }, join: join_params }
16
+ subject { described_class.new to: 'tel:+14155551212', from: 'tel:+13035551212', uri: 'xmpp:foo@bar.com', timeout: 30000, headers: { 'X-skill' => 'agent', 'X-customer-id' => '8877' }, join: join_params }
17
17
 
18
18
  its(:to) { should be == 'tel:+14155551212' }
19
19
  its(:from) { should be == 'tel:+13035551212' }
20
+ its(:uri) { should be == 'xmpp:foo@bar.com' }
20
21
  its(:timeout) { should be == 30000 }
21
22
  its(:join) { should be == Join.new(join_params) }
22
23
  its(:headers) { should be == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
@@ -27,6 +28,7 @@ module Punchblock
27
28
  new_instance.should be_instance_of described_class
28
29
  new_instance.to.should == 'tel:+14155551212'
29
30
  new_instance.from.should == 'tel:+13035551212'
31
+ new_instance.uri.should == 'xmpp:foo@bar.com'
30
32
  new_instance.timeout.should == 30000
31
33
  new_instance.join.should == Join.new(join_params)
32
34
  new_instance.headers.should == { 'X-skill' => 'agent', 'X-customer-id' => '8877' }
@@ -54,7 +56,7 @@ module Punchblock
54
56
  describe "from a stanza" do
55
57
  let :stanza do
56
58
  <<-MESSAGE
57
- <dial to='tel:+14155551212' from='tel:+13035551212' timeout='30000' xmlns='urn:xmpp:rayo:1'>
59
+ <dial to='tel:+14155551212' from='tel:+13035551212' uri='xmpp:foo@bar.com' timeout='30000' xmlns='urn:xmpp:rayo:1'>
58
60
  <join call-uri="abc123" />
59
61
  <header name="X-skill" value="agent" />
60
62
  <header name="X-customer-id" value="8877" />
@@ -68,6 +70,7 @@ module Punchblock
68
70
 
69
71
  its(:to) { should be == 'tel:+14155551212' }
70
72
  its(:from) { should be == 'tel:+13035551212' }
73
+ its(:uri) { should be == 'xmpp:foo@bar.com' }
71
74
  its(:timeout) { should be == 30000 }
72
75
  its(:join) { should be == Join.new(join_params) }
73
76
  its(:headers) { should be == { 'X-skill' => 'agent', 'X-customer-id' => '8877' } }
@@ -90,6 +90,13 @@ module Punchblock
90
90
  subject.handle_event offer
91
91
  end
92
92
  end
93
+
94
+ describe '#new_call_uri' do
95
+ it "should return a random UUID" do
96
+ stub_uuids 'foobar'
97
+ subject.new_call_uri.should == 'foobar'
98
+ end
99
+ end
93
100
  end
94
101
  end
95
102
  end
@@ -17,22 +17,42 @@ module Punchblock
17
17
  subject { connection }
18
18
 
19
19
  describe "rayo domains" do
20
+ before { stub_uuids 'randomcallid' }
21
+
20
22
  context "with no domains specified, and a JID of 1@app.rayo.net" do
21
23
  let(:options) { { :username => '1@app.rayo.net' } }
22
24
 
23
25
  its(:root_domain) { should be == 'app.rayo.net' }
26
+
27
+ describe '#new_call_uri' do
28
+ it "should return an appropriate random call URI" do
29
+ subject.new_call_uri.should == 'xmpp:randomcallid@app.rayo.net'
30
+ end
31
+ end
24
32
  end
25
33
 
26
34
  context "with only a rayo domain set" do
27
35
  let(:options) { { :rayo_domain => 'rayo.org' } }
28
36
 
29
37
  its(:root_domain) { should be == 'rayo.org' }
38
+
39
+ describe '#new_call_uri' do
40
+ it "should return an appropriate random call URI" do
41
+ subject.new_call_uri.should == 'xmpp:randomcallid@rayo.org'
42
+ end
43
+ end
30
44
  end
31
45
 
32
46
  context "with only a root domain set" do
33
47
  let(:options) { { :root_domain => 'rayo.org' } }
34
48
 
35
49
  its(:root_domain) { should be == 'rayo.org' }
50
+
51
+ describe '#new_call_uri' do
52
+ it "should return an appropriate random call URI" do
53
+ subject.new_call_uri.should == 'xmpp:randomcallid@rayo.org'
54
+ end
55
+ end
36
56
  end
37
57
  end
38
58
 
@@ -217,6 +237,11 @@ module Punchblock
217
237
  MSG
218
238
  end
219
239
 
240
+ before do
241
+ @now = DateTime.now
242
+ DateTime.stub now: @now
243
+ end
244
+
220
245
  let(:example_event) { import_stanza offer_xml }
221
246
 
222
247
  it { example_event.should be_a Blather::Stanza::Presence }
@@ -228,9 +253,28 @@ module Punchblock
228
253
  event.source_uri.should be == 'xmpp:9f00061@call.rayo.net'
229
254
  event.domain.should be == 'call.rayo.net'
230
255
  event.transport.should be == 'xmpp'
256
+ event.timestamp.should be == @now
231
257
  end
232
258
  handle_presence
233
259
  end
260
+
261
+ context "with a delayed delivery timestamp" do
262
+ let :offer_xml do
263
+ <<-MSG
264
+ <presence to='16577@app.rayo.net/1' from='9f00061@call.rayo.net'>
265
+ <offer xmlns="urn:xmpp:rayo:1" to="sip:whatever@127.0.0.1" from="sip:ylcaomxb@192.168.1.9"/>
266
+ <delay xmlns='urn:xmpp:delay' stamp='2002-09-10T23:08:25Z'/>
267
+ </presence>
268
+ MSG
269
+ end
270
+
271
+ it 'should stamp that time on the rayo event' do
272
+ mock_event_handler.should_receive(:call).once.with do |event|
273
+ event.timestamp.should be == DateTime.new(2002, 9, 10, 23, 8, 25, 0)
274
+ end
275
+ handle_presence
276
+ end
277
+ end
234
278
  end
235
279
 
236
280
  describe "from something that's not a real event" do
@@ -327,6 +327,27 @@ module Punchblock
327
327
  comp_command.response(0.1).should == ProtocolError.new.setup(:item_not_found, "Could not find a call with ID #{call_id}", call_id)
328
328
  end
329
329
 
330
+ context "when the AMI event has a timestamp" do
331
+ let :ami_event do
332
+ RubyAMI::Event.new 'Hangup',
333
+ 'Uniqueid' => "1320842458.8",
334
+ 'Cause' => cause,
335
+ 'Cause-txt' => cause_txt,
336
+ 'Channel' => "SIP/1234-00000000",
337
+ 'Timestamp' => '1393368380.572575'
338
+ end
339
+
340
+ it "should use the AMI timestamp for the Rayo event" do
341
+ expected_end_event = Punchblock::Event::End.new reason: :hangup,
342
+ platform_code: cause,
343
+ target_call_id: subject.id,
344
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
345
+ translator.should_receive(:handle_pb_event).with expected_end_event
346
+
347
+ subject.process_ami_event ami_event
348
+ end
349
+ end
350
+
330
351
  context "after processing a hangup command" do
331
352
  let(:command) { Command::Hangup.new }
332
353
 
@@ -534,6 +555,24 @@ module Punchblock
534
555
  subject.process_ami_event ami_event
535
556
  end
536
557
  end
558
+
559
+ context "when the AMI event has a timestamp" do
560
+ let :ami_event do
561
+ RubyAMI::Event.new "AsyncAGI",
562
+ "SubEvent" => "Start",
563
+ "Channel" => "SIP/1234-00000000",
564
+ "Env" => "agi_request%3A%20async%0Aagi_channel%3A%20SIP%2Fuserb-00000006%0Aagi_language%3A%20en%0Aagi_type%3A%20SIP%0Aagi_uniqueid%3A%201390303636.6%0Aagi_version%3A%2011.7.0%0Aagi_callerid%3A%20userb%0Aagi_calleridname%3A%20User%20B%0Aagi_callingpres%3A%200%0Aagi_callingani2%3A%200%0Aagi_callington%3A%200%0Aagi_callingtns%3A%200%0Aagi_dnid%3A%20unknown%0Aagi_rdnis%3A%20unknown%0Aagi_context%3A%20adhearsion-redirect%0Aagi_extension%3A%201%0Aagi_priority%3A%201%0Aagi_enhanced%3A%200.0%0Aagi_accountcode%3A%20%0Aagi_threadid%3A%20139696536876800%0A%0A",
565
+ 'Timestamp' => '1393368380.572575'
566
+ end
567
+
568
+ it "should use the AMI timestamp for the Rayo event" do
569
+ expected_answered = Punchblock::Event::Answered.new target_call_id: subject.id,
570
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
571
+ translator.should_receive(:handle_pb_event).with expected_answered
572
+
573
+ subject.process_ami_event ami_event
574
+ end
575
+ end
537
576
  end
538
577
 
539
578
  context 'with a Newstate event' do
@@ -565,6 +604,25 @@ module Punchblock
565
604
  subject.process_ami_event ami_event
566
605
  subject.answered?.should be_false
567
606
  end
607
+
608
+ context "when the AMI event has a timestamp" do
609
+ let :ami_event do
610
+ RubyAMI::Event.new 'Newstate',
611
+ 'Channel' => 'SIP/1234-00000000',
612
+ 'ChannelState' => channel_state,
613
+ 'ChannelStateDesc' => channel_state_desc,
614
+ 'Uniqueid' => '1326194671.0',
615
+ 'Timestamp' => '1393368380.572575'
616
+ end
617
+
618
+ it "should use the AMI timestamp for the Rayo event" do
619
+ expected_ringing = Punchblock::Event::Ringing.new target_call_id: subject.id,
620
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
621
+ translator.should_receive(:handle_pb_event).with expected_ringing
622
+
623
+ subject.process_ami_event ami_event
624
+ end
625
+ end
568
626
  end
569
627
  end
570
628
 
@@ -613,6 +671,32 @@ module Punchblock
613
671
  translator.should_receive(:handle_pb_event).with expected_end_event
614
672
  subject.process_ami_event ami_event
615
673
  end
674
+
675
+ context "when the AMI event has a timestamp" do
676
+ let :ami_event do
677
+ RubyAMI::Event.new 'OriginateResponse',
678
+ 'Privilege' => 'call,all',
679
+ 'ActionID' => '9d0c1aa4-5e3b-4cae-8aef-76a6119e2909',
680
+ 'Response' => response,
681
+ 'Channel' => 'SIP/15557654321',
682
+ 'Context' => '',
683
+ 'Exten' => '',
684
+ 'Reason' => '0',
685
+ 'Uniqueid' => uniqueid,
686
+ 'CallerIDNum' => 'sip:5551234567',
687
+ 'CallerIDName' => 'Bryan 100',
688
+ 'Timestamp' => '1393368380.572575'
689
+ end
690
+
691
+ it "should use the AMI timestamp for the Rayo event" do
692
+ expected_end_event = Punchblock::Event::End.new reason: :error,
693
+ target_call_id: subject.id,
694
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
695
+ translator.should_receive(:handle_pb_event).with expected_end_event
696
+
697
+ subject.process_ami_event ami_event
698
+ end
699
+ end
616
700
  end
617
701
  end
618
702
 
@@ -749,6 +833,54 @@ module Punchblock
749
833
  translator.should_receive(:handle_pb_event).with expected_joined
750
834
  subject.process_ami_event switched_ami_event
751
835
  end
836
+
837
+ context "when the AMI event has a timestamp" do
838
+ let :ami_event do
839
+ RubyAMI::Event.new 'Bridge',
840
+ 'Privilege' => "call,all",
841
+ 'Bridgestate' => state,
842
+ 'Bridgetype' => "core",
843
+ 'Channel1' => channel,
844
+ 'Channel2' => other_channel,
845
+ 'Uniqueid1' => "1319717537.11",
846
+ 'Uniqueid2' => "1319717537.10",
847
+ 'CallerID1' => "1234",
848
+ 'CallerID2' => "5678",
849
+ 'Timestamp' => '1393368380.572575'
850
+ end
851
+
852
+ let :switched_ami_event do
853
+ RubyAMI::Event.new 'Bridge',
854
+ 'Privilege' => "call,all",
855
+ 'Bridgestate' => state,
856
+ 'Bridgetype' => "core",
857
+ 'Channel1' => other_channel,
858
+ 'Channel2' => channel,
859
+ 'Uniqueid1' => "1319717537.11",
860
+ 'Uniqueid2' => "1319717537.10",
861
+ 'CallerID1' => "1234",
862
+ 'CallerID2' => "5678",
863
+ 'Timestamp' => '1393368380.572575'
864
+ end
865
+
866
+ before { expected_joined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
867
+
868
+ context "when the call is the first channel" do
869
+ it "should use the AMI timestamp for the Rayo event" do
870
+ translator.should_receive(:handle_pb_event).with expected_joined
871
+
872
+ subject.process_ami_event ami_event
873
+ end
874
+ end
875
+
876
+ context "when the call is the second channel" do
877
+ it "should use the AMI timestamp for the Rayo event" do
878
+ translator.should_receive(:handle_pb_event).with expected_joined
879
+
880
+ subject.process_ami_event switched_ami_event
881
+ end
882
+ end
883
+ end
752
884
  end
753
885
 
754
886
  context "of state 'Unlink'" do
@@ -768,6 +900,54 @@ module Punchblock
768
900
  translator.should_receive(:handle_pb_event).with expected_unjoined
769
901
  subject.process_ami_event switched_ami_event
770
902
  end
903
+
904
+ context "when the AMI event has a timestamp" do
905
+ let :ami_event do
906
+ RubyAMI::Event.new 'Bridge',
907
+ 'Privilege' => "call,all",
908
+ 'Bridgestate' => state,
909
+ 'Bridgetype' => "core",
910
+ 'Channel1' => channel,
911
+ 'Channel2' => other_channel,
912
+ 'Uniqueid1' => "1319717537.11",
913
+ 'Uniqueid2' => "1319717537.10",
914
+ 'CallerID1' => "1234",
915
+ 'CallerID2' => "5678",
916
+ 'Timestamp' => '1393368380.572575'
917
+ end
918
+
919
+ let :switched_ami_event do
920
+ RubyAMI::Event.new 'Bridge',
921
+ 'Privilege' => "call,all",
922
+ 'Bridgestate' => state,
923
+ 'Bridgetype' => "core",
924
+ 'Channel1' => other_channel,
925
+ 'Channel2' => channel,
926
+ 'Uniqueid1' => "1319717537.11",
927
+ 'Uniqueid2' => "1319717537.10",
928
+ 'CallerID1' => "1234",
929
+ 'CallerID2' => "5678",
930
+ 'Timestamp' => '1393368380.572575'
931
+ end
932
+
933
+ before { expected_unjoined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
934
+
935
+ context "when the call is the first channel" do
936
+ it "should use the AMI timestamp for the Rayo event" do
937
+ translator.should_receive(:handle_pb_event).with expected_unjoined
938
+
939
+ subject.process_ami_event ami_event
940
+ end
941
+ end
942
+
943
+ context "when the call is the second channel" do
944
+ it "should use the AMI timestamp for the Rayo event" do
945
+ translator.should_receive(:handle_pb_event).with expected_unjoined
946
+
947
+ subject.process_ami_event switched_ami_event
948
+ end
949
+ end
950
+ end
771
951
  end
772
952
  end
773
953
 
@@ -820,6 +1000,50 @@ module Punchblock
820
1000
  translator.should_receive(:handle_pb_event).with expected_unjoined
821
1001
  subject.process_ami_event switched_ami_event
822
1002
  end
1003
+
1004
+ context "when the AMI event has a timestamp" do
1005
+ let :ami_event do
1006
+ RubyAMI::Event.new 'Unlink',
1007
+ 'Privilege' => "call,all",
1008
+ 'Channel1' => channel,
1009
+ 'Channel2' => other_channel,
1010
+ 'Uniqueid1' => "1319717537.11",
1011
+ 'Uniqueid2' => "1319717537.10",
1012
+ 'CallerID1' => "1234",
1013
+ 'CallerID2' => "5678",
1014
+ 'Timestamp' => '1393368380.572575'
1015
+ end
1016
+
1017
+ let :switched_ami_event do
1018
+ RubyAMI::Event.new 'Unlink',
1019
+ 'Privilege' => "call,all",
1020
+ 'Channel1' => other_channel,
1021
+ 'Channel2' => channel,
1022
+ 'Uniqueid1' => "1319717537.11",
1023
+ 'Uniqueid2' => "1319717537.10",
1024
+ 'CallerID1' => "1234",
1025
+ 'CallerID2' => "5678",
1026
+ 'Timestamp' => '1393368380.572575'
1027
+ end
1028
+
1029
+ before { expected_unjoined.timestamp = DateTime.new(2014, 2, 25, 22, 46, 20) }
1030
+
1031
+ context "when the call is the first channel" do
1032
+ it "should use the AMI timestamp for the Rayo event" do
1033
+ translator.should_receive(:handle_pb_event).with expected_unjoined
1034
+
1035
+ subject.process_ami_event ami_event
1036
+ end
1037
+ end
1038
+
1039
+ context "when the call is the second channel" do
1040
+ it "should use the AMI timestamp for the Rayo event" do
1041
+ translator.should_receive(:handle_pb_event).with expected_unjoined
1042
+
1043
+ subject.process_ami_event switched_ami_event
1044
+ end
1045
+ end
1046
+ end
823
1047
  end
824
1048
 
825
1049
  context 'with a VarSet event' do
@@ -168,6 +168,29 @@ module Punchblock
168
168
  translator.should_receive(:handle_pb_event).once.with expected_end_event
169
169
  subject.handle_ami_event ami_event
170
170
  end
171
+
172
+ context "when the AMI event has a timestamp" do
173
+ let :ami_event do
174
+ RubyAMI::Event.new 'AsyncAGI',
175
+ "SubEvent" => "Exec",
176
+ "Channel" => channel,
177
+ "CommandId" => component_id,
178
+ "Command" => "EXEC ANSWER",
179
+ "Result" => "200%20result=123%20(timeout)%0A",
180
+ 'Timestamp' => '1393368380.572575'
181
+ end
182
+
183
+ it "should use the AMI timestamp for the Rayo event" do
184
+ expected_end_event = Punchblock::Event::End.new reason: :hangup,
185
+ platform_code: 16,
186
+ target_call_id: mock_call.id,
187
+ timestamp: DateTime.new(2014, 2, 25, 22, 46, 20)
188
+ translator.should_receive(:handle_pb_event).once.with kind_of(Punchblock::Event::Complete)
189
+ translator.should_receive(:handle_pb_event).once.with expected_end_event
190
+
191
+ subject.handle_ami_event ami_event
192
+ end
193
+ end
171
194
  end
172
195
  end
173
196
  end
@@ -7,7 +7,7 @@ module Punchblock
7
7
  module Translator
8
8
  describe Asterisk do
9
9
  let(:ami_client) { double 'RubyAMI::Client' }
10
- let(:connection) { double 'Connection::Asterisk', handle_event: nil }
10
+ let(:connection) { Connection::Asterisk.new }
11
11
 
12
12
  let(:translator) { Asterisk.new ami_client, connection }
13
13
 
@@ -16,6 +16,10 @@ module Punchblock
16
16
  its(:ami_client) { should be ami_client }
17
17
  its(:connection) { should be connection }
18
18
 
19
+ before do
20
+ connection.event_handler = ->(*) {}
21
+ end
22
+
19
23
  after { translator.terminate if translator.alive? }
20
24
 
21
25
  describe '#execute_command' do
@@ -210,6 +214,39 @@ module Punchblock
210
214
  mock_call.should_receive(:dial).once.with command
211
215
  subject.execute_global_command command
212
216
  end
217
+
218
+ context 'when requesting a specific URI' do
219
+ let(:requested_uri) { connection.new_call_uri }
220
+
221
+ before do
222
+ command.uri = requested_uri
223
+ end
224
+
225
+ it "should assign the requested URI to the call" do
226
+ subject.execute_global_command command
227
+ subject.call_with_id(requested_uri).id.should == requested_uri
228
+ end
229
+
230
+ context 'and the requested URI already represents a known call' do
231
+ before do
232
+ earlier_command = Command::Dial.new to: 'SIP/1234', uri: requested_uri
233
+ earlier_command.request!
234
+
235
+ subject.execute_global_command earlier_command
236
+
237
+ @first_call = subject.call_with_id(requested_uri)
238
+ end
239
+
240
+ it "should set the command response to a conflict error" do
241
+ subject.execute_global_command command
242
+ command.response(0.1).should == ProtocolError.new.setup(:conflict, 'Call ID already in use')
243
+ end
244
+
245
+ it "should not replace the original call in the registry" do
246
+ subject.call_with_id(requested_uri).should be @first_call
247
+ end
248
+ end
249
+ end
213
250
  end
214
251
 
215
252
  context 'with an AMI action' do
@@ -5,6 +5,7 @@ require 'countdownlatch'
5
5
  require 'logger'
6
6
  require 'celluloid'
7
7
  require 'coveralls'
8
+ require 'ruby_ami'
8
9
  Coveralls.wear!
9
10
 
10
11
  Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
@@ -27,6 +28,9 @@ RSpec.configure do |config|
27
28
  config.before do
28
29
  @uuid = SecureRandom.uuid
29
30
  Punchblock.stub new_request_id: @uuid
31
+
32
+ @current_datetime = DateTime.now
33
+ DateTime.stub now: @current_datetime
30
34
  end
31
35
 
32
36
  config.after :each do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: punchblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.1
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Goecke
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-02-13 00:00:00.000000000 Z
13
+ date: 2014-03-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: nokogiri
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '2.0'
131
+ version: '2.2'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '2.0'
138
+ version: '2.2'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: ruby_fs
141
141
  requirement: !ruby/object:Gem::Requirement