electric_slide 0.2.0 → 0.3.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.
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  class ElectricSlide
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
@@ -6,9 +6,10 @@ describe ElectricSlide::Agent do
6
6
  let(:options) { { id: 1, address: '123@foo.com', presence: :available} }
7
7
 
8
8
  class MyAgent < ElectricSlide::Agent
9
- on_connect do
10
- foo
11
- end
9
+ on_connect { foo }
10
+ on_connection_failed { foo }
11
+ on_disconnect { foo }
12
+ on_presence_change { foo }
12
13
 
13
14
  def foo
14
15
  :bar
@@ -17,7 +18,32 @@ describe ElectricSlide::Agent do
17
18
 
18
19
  subject {MyAgent.new options}
19
20
 
21
+ after do
22
+ [:connect_callback, :disconnect_callback, :connection_failed_callback, :presence_change_callback].each do |callback|
23
+ ElectricSlide::Agent.instance_variable_set "@#{callback}", nil
24
+ end
25
+ end
26
+
20
27
  it 'executes a connect callback' do
21
28
  expect(subject.callback(:connect)).to eql :bar
22
29
  end
30
+
31
+ it 'executes a disconnect callback' do
32
+ expect(subject.callback(:disconnect)).to eql :bar
33
+ end
34
+
35
+ it 'executes a connection failed callback' do
36
+ expect(subject.callback(:connection_failed)).to eql :bar
37
+ end
38
+
39
+ it 'executes a presence change callback' do
40
+ expect(subject.callback(:presence_change, nil, nil, nil, nil)).to eql :bar
41
+ end
42
+
43
+ it 'executes the presence change callback on state change' do
44
+ called = false
45
+ ElectricSlide::Agent.on_presence_change { |queue, agent_call, presence| called = true }
46
+ agent = ElectricSlide::Agent.new presence: :unavailable
47
+ agent.presence = :busy
48
+ end
23
49
  end
@@ -5,11 +5,10 @@ require 'electric_slide/agent_strategy/fixed_priority'
5
5
  require 'ostruct'
6
6
 
7
7
  describe ElectricSlide::AgentStrategy::FixedPriority do
8
- let(:subject) { ElectricSlide::AgentStrategy::FixedPriority.new }
9
8
  it 'should allow adding an agent with a specified priority' do
10
- subject.agent_available?.should be false
9
+ expect(subject.agent_available?).to be false
11
10
  subject << OpenStruct.new({ id: 101, priority: 1 })
12
- subject.agent_available?.should be true
11
+ expect(subject.agent_available?).to be true
13
12
  end
14
13
 
15
14
  it 'should allow adding multiple agents at the same priority' do
@@ -17,7 +16,7 @@ describe ElectricSlide::AgentStrategy::FixedPriority do
17
16
  agent2 = OpenStruct.new({ id: 102, priority: 2 })
18
17
  subject << agent1
19
18
  subject << agent2
20
- subject.checkout_agent.should == agent1
19
+ expect(subject.checkout_agent).to eql(agent1)
21
20
  end
22
21
 
23
22
  it 'should return all agents of a higher priority before returning an agent of a lower priority' do
@@ -27,9 +26,9 @@ describe ElectricSlide::AgentStrategy::FixedPriority do
27
26
  subject << agent1
28
27
  subject << agent2
29
28
  subject << agent3
30
- subject.checkout_agent.should == agent1
31
- subject.checkout_agent.should == agent2
32
- subject.checkout_agent.should == agent3
29
+ expect(subject.checkout_agent).to eql(agent1)
30
+ expect(subject.checkout_agent).to eql(agent2)
31
+ expect(subject.checkout_agent).to eql(agent3)
33
32
  end
34
33
 
35
34
  it 'should detect an agent available if one is available at any priority' do
@@ -38,6 +37,6 @@ describe ElectricSlide::AgentStrategy::FixedPriority do
38
37
  subject << agent1
39
38
  subject << agent2
40
39
  subject.checkout_agent
41
- subject.agent_available?.should == true
40
+ expect(subject.agent_available?).to be true
42
41
  end
43
42
  end
@@ -1,42 +1,453 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
+ require 'electric_slide/agent_strategy/fixed_priority'
3
4
 
4
5
  describe ElectricSlide::CallQueue do
5
6
  let(:queue) { ElectricSlide::CallQueue.new }
6
- let(:call_a) { dummy_call }
7
- let(:call_b) { dummy_call }
8
- let(:call_c) { dummy_call }
9
- before :each do
10
- queue.enqueue call_a
11
- queue.enqueue call_b
12
- queue.enqueue call_c
7
+
8
+ context "enqueuing calls" do
9
+ let(:call_a) { dummy_call }
10
+ let(:call_b) { dummy_call }
11
+ let(:call_c) { dummy_call }
12
+ before :each do
13
+ queue.enqueue call_a
14
+ queue.enqueue call_b
15
+ queue.enqueue call_c
16
+ end
17
+
18
+ it "should return callers in the same order they were enqueued" do
19
+ expect(queue.get_next_caller).to be call_a
20
+ expect(queue.get_next_caller).to be call_b
21
+ expect(queue.get_next_caller).to be call_c
22
+ end
23
+
24
+ it "should return a priority caller ahead of the queue" do
25
+ call_d = dummy_call
26
+ queue.priority_enqueue call_d
27
+ expect(queue.get_next_caller).to be call_d
28
+ expect(queue.get_next_caller).to be call_a
29
+ end
30
+
31
+ it "should remove a caller who abandons the queue" do
32
+ queue.enqueue call_a
33
+ queue.enqueue call_b
34
+ call_a << Punchblock::Event::End.new(reason: :hangup)
35
+ expect(queue.get_next_caller).to be call_b
36
+ end
37
+
38
+ it "records the time in the :electric_slide_enqueued_at call variable on the queued call" do
39
+ enqueue_time = DateTime.new(2015, 9, 30, 15, 0, 23)
40
+ Timecop.freeze enqueue_time
41
+ queue.enqueue call_a
42
+ expect(call_a[:electric_slide_enqueued_at]).to eq(enqueue_time)
43
+ end
13
44
  end
14
45
 
15
- it "should return callers in the same order they were enqueued" do
16
- expect(queue.get_next_caller).to be call_a
17
- expect(queue.get_next_caller).to be call_b
18
- expect(queue.get_next_caller).to be call_c
46
+ it "should raise when given an invalid Agent" do
47
+ expect { queue.add_agent nil }.to raise_error(ArgumentError)
19
48
  end
20
49
 
21
- it "should return a priority caller ahead of the queue" do
22
- call_d = dummy_call
23
- queue.priority_enqueue call_d
24
- expect(queue.get_next_caller).to be call_d
25
- expect(queue.get_next_caller).to be call_a
50
+ describe 'connecting agents to callers' do
51
+ let(:agent_return_method) { :auto }
52
+
53
+ let(:queue) { ElectricSlide::CallQueue.new(connection_type: connection_type, agent_return_method: agent_return_method) }
54
+ let(:agent_id) { '123' }
55
+ let(:agent) { ElectricSlide::Agent.new id: agent_id, address: '123', presence: :available }
56
+ let!(:agent_call) { Adhearsion::OutboundCall.new }
57
+ let(:queued_call) { dummy_call }
58
+ let(:connected_time) { DateTime.now }
59
+
60
+ before do
61
+ allow(Adhearsion::OutboundCall).to receive(:new) { agent_call }
62
+ allow(agent).to receive(:dial_options_for) {
63
+ { confirm: double('ConfirmController') }
64
+ }
65
+
66
+ allow(queued_call).to receive(:active?) { true }
67
+ end
68
+
69
+ context "with connection type :call" do
70
+ let(:connection_type) { :call }
71
+
72
+ before do
73
+ allow(agent_call).to receive(:dial)
74
+ queue.add_agent agent
75
+ queue.enqueue queued_call
76
+ end
77
+
78
+ it "sets the agent's `call` attribute" do
79
+ expect(agent.call).to be agent_call
80
+ end
81
+
82
+ it 'records the agent in the `:agent` call variable on the queued call' do
83
+ expect(queued_call[:agent]).to eq(agent)
84
+ end
85
+
86
+ context 'when the call ends' do
87
+ let(:double_agent) { double(ElectricSlide::Agent, presence: :available).as_null_object }
88
+
89
+ before do
90
+ # add another agent so that it gets selected after the
91
+ # currently-selected agent's call ends; otherwise, the agent just
92
+ # gets returned and is immediately connected to the queued call,
93
+ # causing its state to change before the examples have a chance to
94
+ # check it
95
+ queue.add_agent double_agent
96
+ end
97
+
98
+ it "unsets the agent's `call` attribute" do
99
+ expect {
100
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
101
+ }.to change(agent, :call).from(agent_call).to(nil)
102
+ end
103
+
104
+ context "when the return strategy is :auto" do
105
+ let(:agent_return_method) { :auto }
106
+
107
+ it "makes the agent available for a call" do
108
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
109
+ expect(queue.checkout_agent).to eql(agent)
110
+ end
111
+
112
+ it "sets the agent's presence to :available" do
113
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
114
+ expect(queue.get_agent(agent.id).presence).to eql(:available)
115
+ end
116
+ end
117
+
118
+ context "when the return strategy is :manual" do
119
+ let(:agent_return_method) { :manual }
120
+
121
+ it "does not make the agent available for a call" do
122
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
123
+ expect(queue.checkout_agent).to eql(nil)
124
+ end
125
+
126
+ it "sets the agent's presence to :after_call" do
127
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
128
+ expect(queue.get_agent(agent.id).presence).to eql(:after_call)
129
+ end
130
+ end
131
+
132
+ context "when the agent's and caller's calls are not joined" do
133
+ context 'and the call ends' do
134
+ before do
135
+ queue.remove_agent(double_agent)
136
+
137
+ # prevent the agent from being returned to the queue so the queued
138
+ # call isn't grabbed by the agent again, changing queued call state
139
+ # before the example can check it
140
+ agent.presence = :unavailable
141
+ end
142
+
143
+ it 'unsets the `:agent` call variable on the queued call' do
144
+ expect {
145
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
146
+ }.to change{ queued_call[:agent] }.from(agent).to(nil)
147
+ end
148
+ end
149
+ end
150
+
151
+ context "with callbacks" do
152
+ after do
153
+ [:connect_callback, :disconnect_callback, :connection_failed_callback, :presence_change_callback].each do |callback|
154
+ ElectricSlide::Agent.instance_variable_set "@#{callback}", nil
155
+ end
156
+ end
157
+
158
+ it "invokes the presence change callback" do
159
+ called = false
160
+ ElectricSlide::Agent.on_presence_change { |queue, agent_call, presence| called = true }
161
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
162
+ expect(called).to be true
163
+ end
164
+ end
165
+ end
166
+
167
+ context "when the agent's and caller's calls are joined" do
168
+ before do
169
+ queued_call << Punchblock::Event::Joined.new(timestamp: connected_time)
170
+ agent_call << Punchblock::Event::Joined.new(timestamp: connected_time)
171
+ end
172
+
173
+ it "records the connection time in the :electric_slide_connected_at call variable on the queued call" do
174
+ expect(queued_call[:electric_slide_connected_at]).to eq(connected_time)
175
+ end
176
+ end
177
+ end
178
+
179
+ context "with connection type :bridge" do
180
+ let(:connection_type) { :bridge }
181
+
182
+ before do
183
+ allow(agent_call).to receive(:active?) { true }
184
+ allow(queued_call).to receive(:hangup) { true }
185
+ agent.call = agent_call
186
+ queue.add_agent agent
187
+
188
+ allow(agent_call).to receive(:join) do
189
+ agent_call << Punchblock::Event::Joined.new(timestamp: connected_time)
190
+ queued_call << Punchblock::Event::Joined.new(timestamp: connected_time)
191
+ end
192
+ queue.enqueue queued_call
193
+ end
194
+
195
+ it 'records the agent in the `:agent` call variable on the queued call' do
196
+ expect(queued_call[:agent]).to eq(agent)
197
+ end
198
+
199
+ it "records the connection time in the :electric_slide_connected_at call variable on the queued call" do
200
+ expect(queued_call[:electric_slide_connected_at]).to eq(connected_time)
201
+ end
202
+
203
+ context 'when the call ends' do
204
+ it "unsets the agent's `call` attribute" do
205
+ expect {
206
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
207
+ }.to change(agent, :call).from(agent_call).to(nil)
208
+ end
209
+
210
+ it "marks the agent :unavailable" do
211
+ expect {
212
+ agent_call << Punchblock::Event::End.new(reason: :hangup)
213
+ }.to change(agent, :presence).from(:on_call).to(:unavailable)
214
+ end
215
+
216
+ context "when the return strategy is :auto" do
217
+ let(:agent_return_method) { :auto }
218
+
219
+ it "makes the agent available for a call" do
220
+ agent_call << Punchblock::Event::Unjoined.new
221
+ expect(queue.checkout_agent).to eql(agent)
222
+ end
223
+
224
+ it "sets the agent's presence to :available" do
225
+ agent_call << Punchblock::Event::Unjoined.new
226
+ expect(queue.get_agent(agent.id).presence).to eql(:available)
227
+ end
228
+ end
229
+
230
+ context "when the return strategy is :manual" do
231
+ let(:agent_return_method) { :manual }
232
+
233
+ it "does not make the agent available for a call" do
234
+ agent_call << Punchblock::Event::Unjoined.new
235
+ expect(queue.checkout_agent).to eql(nil)
236
+ end
237
+
238
+ it "sets the agent's presence to :after_call" do
239
+ agent_call << Punchblock::Event::Unjoined.new
240
+ expect(queue.get_agent(agent.id).presence).to eql(:after_call)
241
+ end
242
+ end
243
+ end
244
+ end
26
245
  end
27
246
 
28
- it "should remove a caller who abandons the queue" do
29
- queue.enqueue call_a
30
- queue.enqueue call_b
31
- queue.abandon call_a
32
- expect(queue.get_next_caller).to be call_b
247
+ describe '#add_agent' do
248
+ let(:queue) { ElectricSlide::CallQueue.new }
249
+ let(:agent) { ElectricSlide::Agent.new(id: '1', address: 'agent@example.com') }
250
+
251
+ before do
252
+ allow(agent).to receive(:call) { Adhearsion::OutboundCall.new }
253
+ end
254
+
255
+ it "associates the agent with in the queue" do
256
+ expect {
257
+ queue.add_agent agent
258
+ }.to change(queue, :get_agents).from([]).to([agent])
259
+ end
260
+
261
+ it "makes the agent available to take calls" do
262
+ expect {
263
+ queue.add_agent agent
264
+ }.to change(queue, :checkout_agent).from(nil).to(agent)
265
+ end
266
+
267
+ it "connects the agent to waiting queued calls"
268
+
269
+ context 'when given an agent already in the queue' do
270
+ before do
271
+ queue.add_agent(agent)
272
+ end
273
+
274
+ it 'should raise an error' do
275
+ expect{
276
+ queue.add_agent(agent)
277
+ }.to raise_error(ElectricSlide::CallQueue::DuplicateAgentError)
278
+ end
279
+ end
33
280
  end
34
281
 
35
- it "should raise when given an invalid connection type" do
36
- expect { ElectricSlide::CallQueue.new connection_type: :blah }.to raise_error
282
+ describe '#return_agent' do
283
+ let(:queue) { ElectricSlide::CallQueue.new }
284
+ let(:agent) { ElectricSlide::Agent.new(id: '1', address: 'agent@example.com', presence: :on_call) }
285
+
286
+ before do
287
+ allow(agent).to receive(:call) { Adhearsion::OutboundCall.new }
288
+ end
289
+
290
+ context 'when the agent is a member of the queue' do
291
+ before do
292
+ queue.add_agent agent
293
+ end
294
+
295
+ it "sets the agent presence available" do
296
+ expect {
297
+ queue.return_agent agent
298
+ }.to change(agent, :presence).from(:on_call).to(:available)
299
+ end
300
+
301
+ it "makes the agent available to take calls" do
302
+ expect {
303
+ queue.return_agent agent
304
+ }.to change(queue, :checkout_agent).from(nil).to(agent)
305
+ end
306
+
307
+ context "when returned with some presence other than available" do
308
+ it "reflects that status on the agent" do
309
+ expect {
310
+ queue.return_agent agent, :after_call
311
+ }.to change(agent, :presence).from(:on_call).to(:after_call)
312
+ end
313
+
314
+ it "does not make the agent available to take calls" do
315
+ expect {
316
+ queue.return_agent agent, :after_call
317
+ }.to_not change { queue.checkout_agent }
318
+ end
319
+ end
320
+ end
321
+
322
+ context 'when given an agent not in the queue' do
323
+ it 'should cleanly return false' do
324
+ expect(queue.return_agent(agent)).to be(false)
325
+ end
326
+
327
+ context 'when called with a bang' do
328
+ it 'should raise an error' do
329
+ expect{
330
+ queue.return_agent!(agent)
331
+ }.to raise_error(ElectricSlide::CallQueue::MissingAgentError)
332
+ end
333
+ end
334
+ end
37
335
  end
38
336
 
39
- it "should raise when given an invalid Agent" do
40
- expect { queue.add_agent nil }.to raise_error
337
+ describe '#remove_agent' do
338
+ let(:queue) { ElectricSlide::CallQueue.new }
339
+ let(:agent) { ElectricSlide::Agent.new(id: '1', address: 'agent@example.com', presence: :available) }
340
+
341
+ before do
342
+ queue.add_agent agent
343
+ end
344
+
345
+ it 'sets the agent presence to `:unavailable`' do
346
+ expect {
347
+ queue.remove_agent agent
348
+ }.to change(agent, :presence).from(:available).to(:unavailable)
349
+ end
350
+
351
+ it 'invokes the presence change callback' do
352
+ called = false
353
+ ElectricSlide::Agent.on_presence_change { |queue, agent_call, presence| called = true }
354
+ queue.remove_agent agent
355
+ expect(called).to be
356
+ end
357
+
358
+ it 'takes the agent out of the call rotation' do
359
+ expect {
360
+ queue.remove_agent agent
361
+ }.to change(queue, :checkout_agent).from(agent).to(nil)
362
+ end
363
+
364
+ it 'removes the agent from the queue' do
365
+ queue.remove_agent agent
366
+ expect(queue.get_agents).to_not include(agent)
367
+ end
368
+ end
369
+
370
+ describe '#update' do
371
+ let(:queue) {
372
+ ElectricSlide::CallQueue.new(
373
+ agent_strategy: ElectricSlide::AgentStrategy::LongestIdle,
374
+ connection_type: :call,
375
+ agent_return_method: :auto
376
+ )
377
+ }
378
+
379
+ it 'updates all the given attributes' do
380
+ queue.update(
381
+ agent_strategy: ElectricSlide::AgentStrategy::FixedPriority,
382
+ connection_type: :bridge,
383
+ agent_return_method: :manual
384
+ )
385
+ expect(queue.agent_strategy).to eq(ElectricSlide::AgentStrategy::FixedPriority)
386
+ expect(queue.connection_type).to eq(:bridge)
387
+ expect(queue.agent_return_method).to eq(:manual)
388
+ end
389
+
390
+ context 'when given an unrecognized attribute' do
391
+ it 'does not raise an error' do
392
+ expect{
393
+ queue.update(foo: :bar)
394
+ }.to_not raise_exception
395
+ end
396
+ end
397
+
398
+ context 'when given `nil`' do
399
+ it 'does not raise an error' do
400
+ expect{
401
+ queue.update(foo: :bar)
402
+ }.to_not raise_exception
403
+ end
404
+ end
405
+ end
406
+
407
+ describe '#agent_strategy=' do
408
+ let(:queue) {
409
+ ElectricSlide::CallQueue.new(
410
+ agent_strategy: ElectricSlide::AgentStrategy::LongestIdle,
411
+ connection_type: :call,
412
+ agent_return_method: :auto
413
+ )
414
+ }
415
+
416
+ it 'returns the given strategy class' do
417
+ expect(queue.agent_strategy = ElectricSlide::AgentStrategy::FixedPriority).to eq(ElectricSlide::AgentStrategy::FixedPriority)
418
+ end
419
+
420
+ it 'assigns a new strategy' do
421
+ expect(ElectricSlide::AgentStrategy::FixedPriority).to receive(:new)
422
+ queue.agent_strategy = ElectricSlide::AgentStrategy::FixedPriority
423
+ end
424
+
425
+ it 'returns all agents to the queue (strategy)' do
426
+ agent = double(ElectricSlide::Agent, address: '100', presence: :available, priority: 100).as_null_object
427
+ queue.add_agent(agent)
428
+
429
+ queue.agent_strategy = ElectricSlide::AgentStrategy::FixedPriority
430
+ expect(queue.available_agent_summary).to eq({ total: 1, priorities: { 100 => 1 }})
431
+ end
432
+ end
433
+
434
+ describe '#connection_type=' do
435
+ context 'when given an invalid connection type' do
436
+ it 'raises an `InvalidConnectionType` exception' do
437
+ expect{
438
+ queue.connection_type = :party_line
439
+ }.to raise_exception(ElectricSlide::CallQueue::InvalidConnectionType)
440
+ end
441
+ end
442
+ end
443
+
444
+ describe '#agent_return_method=' do
445
+ context 'when given an invalid agent return method' do
446
+ it 'raises an `InvalidRequeueMethod` exception' do
447
+ expect{
448
+ queue.agent_return_method = :semiauto
449
+ }.to raise_exception(ElectricSlide::CallQueue::InvalidRequeueMethod)
450
+ end
451
+ end
41
452
  end
42
453
  end