pq-wsm 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +86 -0
  3. data/Rakefile +38 -0
  4. data/lib/generators/pc_queues/install_generator.rb +9 -0
  5. data/lib/generators/pc_queues/migration_generator.rb +15 -0
  6. data/lib/generators/pc_queues/templates/README +15 -0
  7. data/lib/generators/pc_queues/templates/initializer.rb +6 -0
  8. data/lib/generators/pc_queues/templates/migration.rb +36 -0
  9. data/lib/pc_queues.rb +43 -0
  10. data/lib/pc_queues/acts_as_enqueable.rb +31 -0
  11. data/lib/pc_queues/acts_as_queue_owner.rb +30 -0
  12. data/lib/pc_queues/priority_queue_item.rb +13 -0
  13. data/lib/pc_queues/queue.rb +312 -0
  14. data/lib/pc_queues/queue_item.rb +58 -0
  15. data/lib/pc_queues/queue_rule.rb +32 -0
  16. data/lib/pc_queues/queue_rule_set.rb +40 -0
  17. data/lib/pc_queues/queue_rules/boolean_queue_rule.rb +34 -0
  18. data/lib/pc_queues/queue_rules/numeric_queue_rule.rb +42 -0
  19. data/lib/pc_queues/queue_rules/sample_queue_rule.rb +36 -0
  20. data/lib/pc_queues/queue_rules/string_match_queue_rule.rb +44 -0
  21. data/lib/pc_queues/railtie.rb +10 -0
  22. data/lib/pc_queues/version.rb +3 -0
  23. data/lib/tasks/pc_queues_tasks.rake +4 -0
  24. data/spec/boolean_queue_rule_spec.rb +34 -0
  25. data/spec/numeric_queue_rule_spec.rb +123 -0
  26. data/spec/queue_spec.rb +494 -0
  27. data/spec/sample_queue_rule_spec.rb +34 -0
  28. data/spec/spec_helper.rb +30 -0
  29. data/spec/string_match_rule_spec.rb +76 -0
  30. data/spec/support/active_record.rb +42 -0
  31. data/spec/support/application.rb +122 -0
  32. data/spec/support/queue_helpers.rb +16 -0
  33. data/spec/support/redis_helpers.rb +32 -0
  34. data/spec/support/time.rb +23 -0
  35. metadata +131 -0
@@ -0,0 +1,494 @@
1
+ require './spec/spec_helper'
2
+ require './spec/support/redis_helpers'
3
+ require './spec/support/time'
4
+ require './spec/support/queue_helpers'
5
+
6
+ describe PcQueues::Queue do
7
+
8
+ before :each do
9
+ PcQueues.redis { |r| r.flushall }
10
+
11
+ @company = Company.create :name => "ProctorCam"
12
+ @company.create_hr_queue
13
+ @company.it_queues.create
14
+ @company.it_queues.create
15
+
16
+ @people = {
17
+ :john => Person.create(:name => "John", :age => 42, :is_female => false),
18
+ :sally => Person.create(:name => "Sally", :age => 29, :is_female => true),
19
+ :mildred => Person.create(:name => "Mildred", :age => 67, :is_female => true),
20
+ :lars => Person.create(:name => "Lars", :age => 34, :is_female => false)
21
+ }
22
+ end
23
+
24
+ after :each do
25
+ Time.reset # reset any stubbed time values
26
+
27
+ @company.hr_queue.destroy
28
+ @company.it_queues.destroy
29
+ @company.destroy
30
+ end
31
+
32
+ describe "when not empty" do
33
+
34
+ before :each do
35
+ @company.hr_queue.enqueue @people[:john]
36
+ @company.hr_queue.enqueue @people[:sally]
37
+ end
38
+
39
+ it "should have an accurately reflected length in the database" do
40
+ expect(@company.hr_queue.length).to eq(2)
41
+ end
42
+
43
+ it "should have an accurately reflected length in Redis" do
44
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
45
+ expect(length).to eq(2)
46
+ end
47
+
48
+ it "should not allow the same enqueable item to be enqueued multiple times" do
49
+ result = @company.hr_queue.enqueue @people[:john]
50
+ expect(result).to eq(nil)
51
+ expect(@company.hr_queue.length).to eq(2)
52
+ end
53
+
54
+ end
55
+
56
+ describe "when empty" do
57
+
58
+ it "should have an accurately reflected length in the database" do
59
+ expect(@company.hr_queue.length).to eq(0)
60
+ end
61
+
62
+ it "should have an accurately reflected length in Redis" do
63
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
64
+ expect(length).to eq(0)
65
+ end
66
+
67
+ it "should dequeue nothing" do
68
+ expect(@company.hr_queue.dequeue).to eq(nil)
69
+ end
70
+
71
+ end
72
+
73
+ describe "when enqueuing" do
74
+
75
+ it "should publish accurate updates to any redis subscribers" do
76
+ # seed 2 values
77
+ Time.travel_to 1.minute.ago
78
+ @company.hr_queue.enqueue @people[:john]
79
+ @company.hr_queue.enqueue @people[:sally]
80
+
81
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
82
+
83
+ on_msg = Proc.new { |message|
84
+ data = JSON.parse message
85
+ expect(data['longest_queue_wait']).to eq(60)
86
+ expect(data['rate']).to eq(0) # 0 queue slots per second (nothing has been dequeued yet)
87
+ expect(data['length']).to eq(3)
88
+ }
89
+
90
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
91
+ Time.reset
92
+ @company.hr_queue.enqueue @people[:mildred]
93
+ end
94
+ end
95
+
96
+ it "should increase the length of the queue by one in the database" do
97
+ expect(@company.hr_queue.length).to eq(0)
98
+ @company.hr_queue.enqueue @people[:john]
99
+ expect(@company.hr_queue.length).to eq(1)
100
+ end
101
+
102
+ it "should increase the length of the queue by one in Redis" do
103
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
104
+ expect(length).to eq(0)
105
+
106
+ @company.hr_queue.enqueue @people[:john]
107
+
108
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
109
+ expect(length).to eq(1)
110
+ end
111
+
112
+ end
113
+
114
+ describe "when dequeuing" do
115
+
116
+ it "should publish accurate updates to any redis subscribers" do
117
+ Time.travel_to 1.minute.ago
118
+
119
+ # seed 2 values
120
+ @company.hr_queue.enqueue @people[:john]
121
+ @company.hr_queue.enqueue @people[:sally]
122
+
123
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
124
+
125
+ on_msg = Proc.new { |message|
126
+ data = JSON.parse message
127
+ expect(data['longest_queue_wait']).to eq(60)
128
+ expect(data['rate']).to be_within(1.0/120).of(1.0/60) # 1 queue slot per minute (± 0.5 %)
129
+ expect(data['length']).to eq(1)
130
+ }
131
+
132
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
133
+ Time.reset
134
+ @company.hr_queue.dequeue
135
+ end
136
+ end
137
+
138
+ it "should decrease the length of the queue by one in the database" do
139
+ @company.hr_queue.enqueue @people[:john]
140
+ expect(@company.hr_queue.length).to eq(1)
141
+ @company.hr_queue.dequeue
142
+ expect(@company.hr_queue.length).to eq(0)
143
+ end
144
+
145
+ it "should increase the length of the queue by one in Redis" do
146
+ @company.hr_queue.enqueue @people[:john]
147
+
148
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
149
+ expect(length).to eq(1)
150
+
151
+ @company.hr_queue.dequeue
152
+
153
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
154
+ expect(length).to eq(0)
155
+ end
156
+
157
+ it "should dequeue the element first element added (FIFO) when there are no priority queue elements" do
158
+ @company.hr_queue.enqueue @people[:john]
159
+ @company.hr_queue.enqueue @people[:mildred]
160
+
161
+ expect(@company.hr_queue.dequeue).to eq(@people[:john])
162
+ end
163
+
164
+ it "should give preferential treatment to those priority queue elements if they exist" do
165
+ @company.hr_queue.enqueue @people[:john]
166
+ @company.hr_queue.priority_enqueue @people[:mildred]
167
+
168
+ expect(@company.hr_queue.dequeue).to eq(@people[:mildred])
169
+ end
170
+
171
+ it "should dequeue priority queue elements in FIFO order" do
172
+ @company.hr_queue.priority_enqueue @people[:john]
173
+ @company.hr_queue.priority_enqueue @people[:mildred]
174
+
175
+ expect(@company.hr_queue.dequeue).to eq(@people[:john])
176
+ end
177
+
178
+ end
179
+
180
+ describe "analytics behavior" do
181
+
182
+ it "should accurately describe rate (Ruby)" do
183
+ Time.travel_to 1.minute.ago
184
+ @company.hr_queue.enqueue @people[:john]
185
+ @company.hr_queue.enqueue @people[:mildred]
186
+
187
+ Time.reset
188
+
189
+ @company.hr_queue.dequeue
190
+ expect(@company.hr_queue.rate).to be_within(1.0/120).of(1.0/60)
191
+ end
192
+
193
+ it "should accurately describe rate (Redis)" do
194
+ Time.travel_to 1.minute.ago
195
+ @company.hr_queue.enqueue @people[:john]
196
+ @company.hr_queue.enqueue @people[:mildred]
197
+
198
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
199
+
200
+ on_msg = Proc.new { |message|
201
+ data = JSON.parse message
202
+ expect(data['rate']).to be_within(1.0/120).of(1.0/60) # 1 queue slot per second
203
+ }
204
+
205
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
206
+ Time.reset
207
+ @company.hr_queue.dequeue
208
+ end
209
+ end
210
+
211
+ it "should accurately describe wait_time (Ruby) for queues with non-zero length" do
212
+ Time.travel_to 1.minute.ago
213
+ @company.hr_queue.enqueue @people[:john]
214
+ @company.hr_queue.enqueue @people[:mildred]
215
+
216
+ Time.reset
217
+ expect(@company.hr_queue.wait_time).to eq(60)
218
+ end
219
+
220
+ it "should accurately describe longest_queue_wait (Redis) for queues with non-zero length" do
221
+ Time.travel_to 1.minute.ago
222
+ @company.hr_queue.enqueue @people[:john]
223
+ @company.hr_queue.enqueue @people[:mildred]
224
+
225
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
226
+
227
+ on_msg = Proc.new { |message|
228
+ data = JSON.parse message
229
+ expect(data['longest_queue_wait']).to eq(60) # 1 queue slot per 60 seconds
230
+ }
231
+
232
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
233
+ Time.reset
234
+ @company.hr_queue.dequeue
235
+ end
236
+ end
237
+
238
+ it "should cache stats" do
239
+ Time.travel_to 1.hour.ago
240
+ @company.hr_queue.enqueue @people[:mildred]
241
+ @company.hr_queue.enqueue @people[:john]
242
+ @company.hr_queue.dequeue
243
+ stats = @company.hr_queue.stats
244
+
245
+ Time.reset # move back to now -- shows that its getting the value
246
+ # from Redis, not recalculating it on the Ruby side
247
+
248
+ expect(@company.hr_queue.stats).to eq(stats)
249
+
250
+ end
251
+
252
+ end
253
+
254
+ describe "when using queue rules" do
255
+
256
+ describe "with is_any queue rule set to true" do
257
+
258
+ before :each do
259
+ rule_set = @company.hr_queue.queue_rule_sets.create :is_any => true
260
+ rule_set.queue_rules.create(IsOverAgeQueueRule.options({
261
+ :value => 30
262
+ })
263
+ )
264
+
265
+ rule_set.queue_rules.create(NameMatchQueueRule.options({
266
+ :name => 'john'
267
+ })
268
+ )
269
+ end
270
+
271
+ it "should enqueue when all rules in the set pass" do
272
+ @company.hr_queue.enqueue_if_passes @people[:john]
273
+ expect(@company.hr_queue.length).to eq(1)
274
+ end
275
+
276
+ it "should enqueue when any rule in the set passes" do
277
+ @company.hr_queue.enqueue_if_passes @people[:mildred]
278
+ expect(@company.hr_queue.length).to eq(1)
279
+ end
280
+
281
+ it "should not enqueue when all rules in the set fail" do
282
+ @company.hr_queue.enqueue_if_passes @people[:sally]
283
+ expect(@company.hr_queue.length).to eq(0)
284
+ end
285
+
286
+ end
287
+
288
+ describe "with is_any queue rule set to false (is all)" do
289
+
290
+ before :each do
291
+ rule_set = @company.hr_queue.queue_rule_sets.create :is_any => false
292
+ rule_set.queue_rules.create(IsOverAgeQueueRule.options({
293
+ :value => 30
294
+ })
295
+ )
296
+
297
+ rule_set.queue_rules.create(NameMatchQueueRule.options({
298
+ :name => 'john'
299
+ })
300
+ )
301
+ end
302
+
303
+ it "should enqueue when all rules in the set pass" do
304
+ @company.hr_queue.enqueue_if_passes @people[:john]
305
+ expect(@company.hr_queue.length).to eq(1)
306
+ end
307
+
308
+ it "should not enqueue when any rule in the set fails" do
309
+ @company.hr_queue.enqueue_if_passes @people[:mildred]
310
+ expect(@company.hr_queue.length).to eq(0)
311
+ end
312
+
313
+ it "should not enqueue when all rules in the set fail" do
314
+ @company.hr_queue.enqueue_if_passes @people[:sally]
315
+ expect(@company.hr_queue.length).to eq(0)
316
+ end
317
+
318
+ end
319
+
320
+ describe "with multiple queue rule sets" do
321
+
322
+ before :each do
323
+ rule_set = @company.hr_queue.queue_rule_sets.create
324
+ rule_set.queue_rules.create(IsOverAgeQueueRule.options({
325
+ :value => 30
326
+ })
327
+ )
328
+
329
+ rule_set = @company.hr_queue.queue_rule_sets.create
330
+ rule_set.queue_rules.create(NameMatchQueueRule.options({
331
+ :name => 'john'
332
+ })
333
+ )
334
+ end
335
+
336
+ it "should enqueue when all rule sets pass" do
337
+ @company.hr_queue.enqueue_if_passes @people[:john]
338
+ expect(@company.hr_queue.length).to eq(1)
339
+ end
340
+
341
+ it "should not enqueue if any of the rule sets fail" do
342
+ @company.hr_queue.enqueue_if_passes @people[:mildred]
343
+ expect(@company.hr_queue.length).to eq(0)
344
+ end
345
+
346
+ it "should not enqueue if none of the rule sets pass" do
347
+ @company.hr_queue.enqueue_if_passes @people[:sally]
348
+ expect(@company.hr_queue.length).to eq(0)
349
+ end
350
+
351
+ end
352
+
353
+
354
+ end
355
+
356
+ describe "when working with active queues" do
357
+
358
+ it "publishes active queues (non-zero length with subscribers)" do
359
+ @company.hr_queue.enqueue @people[:john]
360
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
361
+
362
+ on_msg = Proc.new { |message|
363
+ data = JSON.parse message
364
+ expect(data['length']).to eq(1)
365
+ }
366
+
367
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
368
+ PcQueues::Queue.publish_active_queues
369
+ end
370
+ end
371
+
372
+ it "does not publish inactive queues (zero length)" do
373
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}"
374
+
375
+ fake_data = {'published' => false}
376
+
377
+ on_msg = Proc.new { |message|
378
+ data = JSON.parse message
379
+ expect(data).to eq(fake_data)
380
+ }
381
+
382
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
383
+ t = Thread.new {
384
+ sleep 1 # assumption: 1 second is long enough for redis to have published what it was supposed to
385
+ PcQueues.redis{|r| r.publish "#{PcQueues.namespace}:#{channel}", fake_data.to_json }
386
+ }
387
+ PcQueues::Queue.publish_active_queues
388
+ t.join
389
+ end
390
+ end
391
+
392
+ end
393
+
394
+ describe "when publishing position updates" do
395
+
396
+ it "serializes and publishes a package for each item in the queue" do
397
+ Time.travel_to 1.minute.ago
398
+ @company.hr_queue.enqueue @people[:john]
399
+ @company.hr_queue.enqueue @people[:mildred]
400
+
401
+ Time.reset
402
+ @company.hr_queue.dequeue
403
+
404
+ channel = "#{@company.hr_queue.class.name}.#{@company.hr_queue.id}.#{@people[:mildred].id}"
405
+
406
+ on_msg = Proc.new { |message|
407
+ data = JSON.parse message
408
+ expect(data['position']).to eq(1)
409
+ expect(data['estimated_time_left']).to eq(60)
410
+ }
411
+
412
+ RedisHelpers::Subscription.subscribe_to_and_publish channel, on_msg do |subscriptions|
413
+ @company.hr_queue.publish_positions
414
+ end
415
+ end
416
+
417
+ end
418
+
419
+ describe "cold-starting" do
420
+
421
+ before :each do
422
+ @company.hr_queue.enqueue @people[:john]
423
+ @company.hr_queue.enqueue @people[:mildred]
424
+ end
425
+
426
+ it "clears Redis and repopulates it with every queue item" do
427
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
428
+ expect(length).to eq(2)
429
+
430
+ @company.hr_queue.cold_start
431
+
432
+ expect(length).to eq(2)
433
+
434
+ redis_queue_items = QueueHelpers.get_queue_items(@company.hr_queue)
435
+ expect(redis_queue_items).to eq(@company.hr_queue.enqueables)
436
+ end
437
+
438
+ it "cold starts whenever redis gets out of sync on an enqueue event" do
439
+ # fake a redis dequeue -- puts this out of sync
440
+ Wolverine.queues.dequeue [], [ Time.now.to_i, @company.hr_queue.class.name, @company.hr_queue.id ]
441
+
442
+ @company.hr_queue.enqueue @people[:sally]
443
+
444
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
445
+ expect(length).to eq(3)
446
+ end
447
+
448
+ it "cold starts whenever redis gets out of sync on a priority_enqueue event" do
449
+ # fake a redis dequeue -- puts this out of sync
450
+ Wolverine.queues.dequeue [], [ Time.now.to_i, @company.hr_queue.class.name, @company.hr_queue.id ]
451
+
452
+ @company.hr_queue.priority_enqueue @people[:sally]
453
+
454
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
455
+ expect(length).to eq(3)
456
+ end
457
+
458
+ it "cold starts whenever redis gets out of sync on a dequeue event" do
459
+ # fake a redis dequeue -- puts this out of sync
460
+ Wolverine.queues.dequeue [], [ Time.now.to_i, @company.hr_queue.class.name, @company.hr_queue.id ]
461
+
462
+ @company.hr_queue.dequeue
463
+
464
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
465
+ expect(length).to eq(2)
466
+ end
467
+
468
+ it "cold starts whenever redis gets out of sync on a remove event" do
469
+ # fake a redis dequeue -- puts this out of sync
470
+ Wolverine.queues.dequeue [], [ Time.now.to_i, @company.hr_queue.class.name, @company.hr_queue.id ]
471
+
472
+ @company.hr_queue.remove @people[:mildred]
473
+
474
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
475
+ expect(length).to eq(2)
476
+ end
477
+
478
+ end
479
+
480
+ describe "after clearing redis" do
481
+
482
+ it "should have nothing left in it (redis)" do
483
+ @company.hr_queue.enqueue @people[:john]
484
+ @company.hr_queue.enqueue @people[:mildred]
485
+
486
+ @company.hr_queue.clear
487
+
488
+ length = QueueHelpers.get_redis_length(@company.hr_queue)
489
+ expect(length).to eq(0)
490
+ end
491
+
492
+ end
493
+
494
+ end