punchblock 2.3.1 → 2.4.0

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