qs 0.3.0 → 0.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.
Files changed (63) hide show
  1. data/bench/config.qs +4 -27
  2. data/bench/dispatcher.qs +24 -0
  3. data/bench/report.rb +80 -10
  4. data/bench/report.txt +10 -3
  5. data/bench/setup.rb +55 -0
  6. data/lib/qs.rb +75 -15
  7. data/lib/qs/client.rb +73 -22
  8. data/lib/qs/daemon.rb +21 -21
  9. data/lib/qs/daemon_data.rb +4 -4
  10. data/lib/qs/dispatch_job.rb +36 -0
  11. data/lib/qs/dispatch_job_handler.rb +79 -0
  12. data/lib/qs/dispatcher_queue.rb +19 -0
  13. data/lib/qs/error_handler.rb +12 -12
  14. data/lib/qs/event.rb +82 -0
  15. data/lib/qs/event_handler.rb +34 -0
  16. data/lib/qs/event_handler_test_helpers.rb +17 -0
  17. data/lib/qs/job.rb +19 -31
  18. data/lib/qs/job_handler.rb +6 -63
  19. data/lib/qs/{test_helpers.rb → job_handler_test_helpers.rb} +2 -2
  20. data/lib/qs/message.rb +29 -0
  21. data/lib/qs/message_handler.rb +84 -0
  22. data/lib/qs/payload.rb +98 -0
  23. data/lib/qs/payload_handler.rb +106 -54
  24. data/lib/qs/queue.rb +39 -6
  25. data/lib/qs/queue_item.rb +33 -0
  26. data/lib/qs/route.rb +7 -7
  27. data/lib/qs/runner.rb +6 -5
  28. data/lib/qs/test_runner.rb +41 -13
  29. data/lib/qs/version.rb +1 -1
  30. data/qs.gemspec +1 -1
  31. data/test/helper.rb +1 -1
  32. data/test/support/app_daemon.rb +77 -11
  33. data/test/support/factory.rb +34 -0
  34. data/test/system/daemon_tests.rb +146 -77
  35. data/test/system/queue_tests.rb +87 -0
  36. data/test/unit/client_tests.rb +184 -45
  37. data/test/unit/daemon_data_tests.rb +4 -4
  38. data/test/unit/daemon_tests.rb +32 -32
  39. data/test/unit/dispatch_job_handler_tests.rb +163 -0
  40. data/test/unit/dispatch_job_tests.rb +75 -0
  41. data/test/unit/dispatcher_queue_tests.rb +42 -0
  42. data/test/unit/error_handler_tests.rb +9 -9
  43. data/test/unit/event_handler_test_helpers_tests.rb +55 -0
  44. data/test/unit/event_handler_tests.rb +63 -0
  45. data/test/unit/event_tests.rb +162 -0
  46. data/test/unit/{test_helper_tests.rb → job_handler_test_helper_tests.rb} +13 -19
  47. data/test/unit/job_handler_tests.rb +17 -210
  48. data/test/unit/job_tests.rb +49 -79
  49. data/test/unit/message_handler_tests.rb +235 -0
  50. data/test/unit/message_tests.rb +64 -0
  51. data/test/unit/payload_handler_tests.rb +285 -86
  52. data/test/unit/payload_tests.rb +139 -0
  53. data/test/unit/qs_runner_tests.rb +6 -6
  54. data/test/unit/qs_tests.rb +167 -28
  55. data/test/unit/queue_item_tests.rb +51 -0
  56. data/test/unit/queue_tests.rb +126 -18
  57. data/test/unit/route_tests.rb +12 -13
  58. data/test/unit/runner_tests.rb +10 -10
  59. data/test/unit/test_runner_tests.rb +117 -24
  60. metadata +51 -21
  61. data/bench/queue.rb +0 -8
  62. data/lib/qs/redis_item.rb +0 -33
  63. data/test/unit/redis_item_tests.rb +0 -49
@@ -11,26 +11,39 @@ module Qs::Daemon
11
11
  Qs.reset!
12
12
  @qs_test_mode = ENV['QS_TEST_MODE']
13
13
  ENV['QS_TEST_MODE'] = nil
14
+ Qs.config.dispatcher.queue_name = 'qs-app-dispatcher'
15
+ Qs.config.event_publisher = 'Daemon System Tests'
14
16
  Qs.init
17
+ AppQueue.sync_subscriptions
15
18
  @orig_config = AppDaemon.configuration.to_hash
16
19
  end
17
20
  teardown do
18
21
  @daemon_runner.stop if @daemon_runner
19
22
  AppDaemon.configuration.apply(@orig_config) # reset daemon config
20
- Qs.redis.with{ |c| c.del('slow') }
21
- Qs.redis.with{ |c| c.del('last_error') }
23
+ Qs.redis.with do |c|
24
+ keys = c.keys('*qs-app*')
25
+ c.pipelined{ keys.each{ |k| c.del(k) } }
26
+ end
22
27
  Qs.client.clear(AppQueue.redis_key)
28
+ AppQueue.clear_subscriptions
23
29
  Qs.reset!
24
30
  ENV['QS_TEST_MODE'] = @qs_test_mode
25
31
  end
26
32
 
33
+ private
34
+
35
+ def setup_app_and_dispatcher_daemon
36
+ @app_daemon = AppDaemon.new
37
+ @dispatcher_daemon = DispatcherDaemon.new
38
+ @daemon_runner = DaemonRunner.new(@app_daemon, @dispatcher_daemon)
39
+ @app_thread = @daemon_runner.start
40
+ end
41
+
27
42
  end
28
43
 
29
44
  class RunningDaemonSetupTests < SystemTests
30
45
  setup do
31
- @daemon = AppDaemon.new
32
- @daemon_runner = DaemonRunner.new(@daemon)
33
- @thread = @daemon_runner.start
46
+ setup_app_and_dispatcher_daemon
34
47
  end
35
48
 
36
49
  end
@@ -43,11 +56,11 @@ module Qs::Daemon
43
56
  'key' => @key,
44
57
  'value' => @value
45
58
  })
46
- @thread.join 0.5
59
+ @app_thread.join 0.5
47
60
  end
48
61
 
49
62
  should "run the job" do
50
- assert_equal @value, Qs.redis.with{ |c| c.get(@key) }
63
+ assert_equal @value, Qs.redis.with{ |c| c.get("qs-app:#{@key}") }
51
64
  end
52
65
 
53
66
  end
@@ -57,12 +70,12 @@ module Qs::Daemon
57
70
  setup do
58
71
  @error_message = Factory.text
59
72
  AppQueue.add('error', 'error_message' => @error_message)
60
- @thread.join 0.5
73
+ @app_thread.join 0.5
61
74
  end
62
75
 
63
76
  should "run the configured error handler procs" do
64
77
  exp = "RuntimeError: #{@error_message}"
65
- assert_equal exp, Qs.redis.with{ |c| c.get('last_error') }
78
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
66
79
  end
67
80
 
68
81
  end
@@ -71,14 +84,62 @@ module Qs::Daemon
71
84
  desc "with a job that times out"
72
85
  setup do
73
86
  AppQueue.add('timeout')
74
- @thread.join 1 # let the daemon have time to process the job
87
+ @app_thread.join 1 # let the daemon have time to process the job
75
88
  end
76
89
 
77
90
  should "run the configured error handler procs" do
78
91
  handler_class = AppHandlers::Timeout
79
92
  exp = "Qs::TimeoutError: #{handler_class} timed out " \
80
93
  "(#{handler_class.timeout}s)"
81
- assert_equal exp, Qs.redis.with{ |c| c.get('last_error') }
94
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
95
+ end
96
+
97
+ end
98
+
99
+ class BasicEventTests < RunningDaemonSetupTests
100
+ desc "with a basic event added"
101
+ setup do
102
+ @key, @value = [Factory.string, Factory.string]
103
+ Qs.publish('qs-app', 'basic', {
104
+ 'key' => @key,
105
+ 'value' => @value
106
+ })
107
+ @app_thread.join 0.5
108
+ end
109
+
110
+ should "run the event" do
111
+ assert_equal @value, Qs.redis.with{ |c| c.get("qs-app:#{@key}") }
112
+ end
113
+
114
+ end
115
+
116
+ class EventThatErrorsTests < RunningDaemonSetupTests
117
+ desc "with an event that errors"
118
+ setup do
119
+ @error_message = Factory.text
120
+ Qs.publish('qs-app', 'error', 'error_message' => @error_message)
121
+ @app_thread.join 0.5
122
+ end
123
+
124
+ should "run the configured error handler procs" do
125
+ exp = "RuntimeError: #{@error_message}"
126
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
127
+ end
128
+
129
+ end
130
+
131
+ class TimeoutEventTests < RunningDaemonSetupTests
132
+ desc "with an event that times out"
133
+ setup do
134
+ Qs.publish('qs-app', 'timeout')
135
+ @app_thread.join 1 # let the daemon have time to process the job
136
+ end
137
+
138
+ should "run the configured error handler procs" do
139
+ handler_class = AppHandlers::TimeoutEvent
140
+ exp = "Qs::TimeoutError: #{handler_class} timed out " \
141
+ "(#{handler_class.timeout}s)"
142
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
82
143
  end
83
144
 
84
145
  end
@@ -87,21 +148,19 @@ module Qs::Daemon
87
148
  desc "when no workers are available"
88
149
  setup do
89
150
  AppDaemon.workers 0 # no workers available, don't do this
90
- @daemon = AppDaemon.new
91
- @daemon_runner = DaemonRunner.new(@daemon)
92
- @thread = @daemon_runner.start
151
+ setup_app_and_dispatcher_daemon
93
152
  end
94
153
 
95
154
  should "shutdown when stopped" do
96
- @daemon.stop
97
- @thread.join 2 # give it time to shutdown, should be faster
98
- assert_false @thread.alive?
155
+ @app_daemon.stop
156
+ @app_thread.join 2 # give it time to shutdown, should be faster
157
+ assert_false @app_thread.alive?
99
158
  end
100
159
 
101
160
  should "shutdown when halted" do
102
- @daemon.halt
103
- @thread.join 2 # give it time to shutdown, should be faster
104
- assert_false @thread.alive?
161
+ @app_daemon.halt
162
+ @app_thread.join 2 # give it time to shutdown, should be faster
163
+ assert_false @app_thread.alive?
105
164
  end
106
165
 
107
166
  end
@@ -110,28 +169,31 @@ module Qs::Daemon
110
169
  desc "without a shutdown timeout"
111
170
  setup do
112
171
  AppDaemon.shutdown_timeout nil # disable shutdown timeout
113
- @daemon = AppDaemon.new
114
- @daemon_runner = DaemonRunner.new(@daemon)
115
- @thread = @daemon_runner.start
172
+ setup_app_and_dispatcher_daemon
116
173
 
117
174
  AppQueue.add('slow')
118
- @thread.join 1 # let the daemon have time to process the job
175
+ Qs.publish('qs-app', 'slow')
176
+ @app_thread.join 1 # let the daemon have time to process the job and event
119
177
  end
120
178
 
121
- should "shutdown and let the job finished" do
122
- @daemon.stop
123
- @thread.join 10 # give it time to shutdown, should be faster
124
- assert_false @thread.alive?
125
- assert_equal 'finished', Qs.redis.with{ |c| c.get('slow') }
179
+ should "shutdown and let the job and event finish" do
180
+ @app_daemon.stop
181
+ @app_thread.join 10 # give it time to shutdown, should be faster
182
+ assert_false @app_thread.alive?
183
+ assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow') }
184
+ assert_equal 'finished', Qs.redis.with{ |c| c.get('qs-app:slow:event') }
126
185
  end
127
186
 
128
- should "shutdown and not let the job finished" do
129
- @daemon.halt
130
- @thread.join 2 # give it time to shutdown, should be faster
131
- assert_false @thread.alive?
132
- assert_nil Qs.redis.with{ |c| c.get('slow') }
187
+ should "shutdown and not let the job or event finish" do
188
+ @app_daemon.halt
189
+ @app_thread.join 2 # give it time to shutdown, should be faster
190
+ assert_false @app_thread.alive?
191
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
192
+ exp = "Qs::ShutdownError"
193
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
194
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
133
195
  exp = "Qs::ShutdownError"
134
- assert_equal exp, Qs.redis.with{ |c| c.get('last_error') }
196
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
135
197
  end
136
198
 
137
199
  end
@@ -140,86 +202,93 @@ module Qs::Daemon
140
202
  desc "with a shutdown timeout"
141
203
  setup do
142
204
  AppDaemon.shutdown_timeout 1
143
- @daemon = AppDaemon.new
144
- @daemon_runner = DaemonRunner.new(@daemon)
145
- @thread = @daemon_runner.start
205
+ setup_app_and_dispatcher_daemon
146
206
 
147
207
  AppQueue.add('slow')
148
- @thread.join 1 # let the daemon have time to process the job
208
+ Qs.publish('qs-app', 'slow')
209
+ @app_thread.join 1 # let the daemon have time to process the job and event
149
210
  end
150
211
 
151
- should "shutdown and not let the job finished" do
152
- @daemon.stop
153
- @thread.join 2 # give it time to shutdown, should be faster
154
- assert_false @thread.alive?
155
- assert_nil Qs.redis.with{ |c| c.get('slow') }
212
+ should "shutdown and not let the job or event finish" do
213
+ @app_daemon.stop
214
+ @app_thread.join 2 # give it time to shutdown, should be faster
215
+ assert_false @app_thread.alive?
216
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
156
217
  exp = "Qs::ShutdownError"
157
- assert_equal exp, Qs.redis.with{ |c| c.get('last_error') }
218
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
219
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
220
+ exp = "Qs::ShutdownError"
221
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
158
222
  end
159
223
 
160
- should "shutdown and not let the job finished" do
161
- @daemon.halt
162
- @thread.join 2 # give it time to shutdown, should be faster
163
- assert_false @thread.alive?
164
- assert_nil Qs.redis.with{ |c| c.get('slow') }
224
+ should "shutdown and not let the job or event finish" do
225
+ @app_daemon.halt
226
+ @app_thread.join 2 # give it time to shutdown, should be faster
227
+ assert_false @app_thread.alive?
228
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow') }
229
+ exp = "Qs::ShutdownError"
230
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_job_error') }
231
+ assert_nil Qs.redis.with{ |c| c.get('qs-app:slow:event') }
165
232
  exp = "Qs::ShutdownError"
166
- assert_equal exp, Qs.redis.with{ |c| c.get('last_error') }
233
+ assert_equal exp, Qs.redis.with{ |c| c.get('qs-app:last_event_error') }
167
234
  end
168
235
 
169
236
  end
170
237
 
171
- class ShutdownWithUnprocessedRedisItemTests < SystemTests
172
- desc "with a redis item that gets picked up but doesn't get processed"
238
+ class ShutdownWithUnprocessedQueueItemTests < SystemTests
239
+ desc "with a queue item that gets picked up but doesn't get processed"
173
240
  setup do
174
241
  Assert.stub(Qs::PayloadHandler, :new){ sleep 5 }
175
242
 
176
243
  AppDaemon.shutdown_timeout 1
177
244
  AppDaemon.workers 2
178
- @daemon = AppDaemon.new
179
- @daemon_runner = DaemonRunner.new(@daemon)
180
- @thread = @daemon_runner.start
245
+ setup_app_and_dispatcher_daemon
181
246
 
182
247
  AppQueue.add('slow')
183
248
  AppQueue.add('slow')
184
249
  AppQueue.add('basic')
185
- @thread.join 1 # let the daemon have time to process jobs
250
+ @app_thread.join 1 # let the daemon have time to process jobs
186
251
  end
187
252
 
188
- should "shutdown and requeue the redis item" do
189
- @daemon.stop
190
- @thread.join 2 # give it time to shutdown, should be faster
191
- assert_false @thread.alive?
192
- # TODO - better way to read whats on a queue
193
- serialized_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
194
- names = serialized_payloads.map{ |sp| Qs::Job.parse(Qs.deserialize(sp)).name }
253
+ should "shutdown and requeue the queue item" do
254
+ @app_daemon.stop
255
+ @app_thread.join 2 # give it time to shutdown, should be faster
256
+ assert_false @app_thread.alive?
257
+ encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
258
+ names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
195
259
  assert_equal ['basic', 'slow', 'slow'], names
196
260
  end
197
261
 
198
- should "shutdown and requeue the redis item" do
199
- @daemon.halt
200
- @thread.join 2 # give it time to shutdown, should be faster
201
- assert_false @thread.alive?
202
- # TODO - better way to read whats on a queue
203
- serialized_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
204
- names = serialized_payloads.map{ |sp| Qs::Job.parse(Qs.deserialize(sp)).name }
262
+ should "shutdown and requeue the queue item" do
263
+ @app_daemon.halt
264
+ @app_thread.join 2 # give it time to shutdown, should be faster
265
+ assert_false @app_thread.alive?
266
+ encoded_payloads = Qs.redis.with{ |c| c.lrange(AppQueue.redis_key, 0, 3) }
267
+ names = encoded_payloads.map{ |sp| Qs::Payload.deserialize(sp).name }
205
268
  assert_equal ['basic', 'slow', 'slow'], names
206
269
  end
207
270
 
208
271
  end
209
272
 
210
273
  class DaemonRunner
211
- def initialize(daemon)
212
- @daemon = daemon
213
- @thread = nil
274
+ def initialize(app_daemon, dispatcher_daemon = nil)
275
+ @app_daemon = app_daemon
276
+ @dispatcher_daemon = dispatcher_daemon
277
+ @app_thread = nil
278
+ @dispatcher_thread = nil
214
279
  end
215
280
 
216
281
  def start
217
- @thread = @daemon.start
282
+ @app_thread = @app_daemon.start
283
+ @dispatcher_thread = @dispatcher_daemon.start if @dispatcher_daemon
284
+ @app_thread
218
285
  end
219
286
 
220
287
  def stop
221
- @daemon.halt
222
- @thread.join if @thread
288
+ @app_daemon.halt
289
+ @dispatcher_daemon.halt if @dispatcher_daemon
290
+ @app_thread.join if @app_thread
291
+ @dispatcher_thread.join if @dispatcher_thread
223
292
  end
224
293
  end
225
294
 
@@ -0,0 +1,87 @@
1
+ require 'assert'
2
+ require 'qs'
3
+
4
+ require 'test/support/app_daemon'
5
+
6
+ class Qs::Queue
7
+
8
+ class SystemTests < Assert::Context
9
+ desc "Qs::Queue"
10
+ setup do
11
+ Qs.reset!
12
+ @qs_test_mode = ENV['QS_TEST_MODE']
13
+ ENV['QS_TEST_MODE'] = nil
14
+ Qs.init
15
+
16
+ @event = Qs::Event.new('qs-app', 'basic')
17
+ @other_queue = Qs::Queue.new{ name(Factory.string) }
18
+ @other_queue.event(@event.channel, @event.name, Factory.string)
19
+ end
20
+ teardown do
21
+ Qs.redis.with do |c|
22
+ keys = c.keys('*qs-app*')
23
+ c.pipelined{ keys.each{ |k| c.del(k) } }
24
+ end
25
+ Qs.reset!
26
+ ENV['QS_TEST_MODE'] = @qs_test_mode
27
+ end
28
+
29
+ end
30
+
31
+ class SyncSubscriptionsTests < SystemTests
32
+ desc "sync_subscriptions"
33
+ setup do
34
+ AppQueue.sync_subscriptions
35
+ end
36
+
37
+ should "store subscriptions for the queue in redis" do
38
+ AppQueue.event_route_names.each do |route_name|
39
+ redis_key = Qs::Event::SubscribersRedisKey.new(route_name)
40
+ smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
41
+ assert_includes AppQueue.name, smembers
42
+ end
43
+ end
44
+
45
+ should "allow adding a new queues subscriptions but preserve the existing" do
46
+ @other_queue.sync_subscriptions
47
+
48
+ smembers = Qs.redis.with{ |c| c.smembers(@event.subscribers_redis_key) }
49
+ assert_equal 2, smembers.size
50
+ assert_includes AppQueue.name, smembers
51
+ assert_includes @other_queue.name, smembers
52
+ end
53
+
54
+ should "remove subscriptions if a queue no longer subscribes to the event" do
55
+ route_names = AppQueue.event_route_names.reject{ |n| n == @event.route_name }
56
+ Assert.stub(AppQueue, :event_route_names){ route_names }
57
+ AppQueue.sync_subscriptions
58
+
59
+ redis_key = Qs::Event.new('qs-app', 'basic').subscribers_redis_key
60
+ smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
61
+ assert_not_includes AppQueue.name, smembers
62
+ end
63
+
64
+ end
65
+
66
+ class ClearSubscriptionsTests < SystemTests
67
+ desc "clear_subscriptions"
68
+ setup do
69
+ AppQueue.sync_subscriptions
70
+ @other_queue.sync_subscriptions
71
+ AppQueue.clear_subscriptions
72
+ end
73
+
74
+ should "remove the queue from all of its events subscribers" do
75
+ AppQueue.event_route_names.each do |route_name|
76
+ redis_key = Qs::Event::SubscribersRedisKey.new(route_name)
77
+ smembers = Qs.redis.with{ |c| c.smembers(redis_key) }
78
+ assert_not_includes AppQueue.name, smembers
79
+ end
80
+
81
+ smembers = Qs.redis.with{ |c| c.smembers(@event.subscribers_redis_key) }
82
+ assert_equal [@other_queue.name], smembers
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -48,10 +48,12 @@ module Qs::Client
48
48
  subject{ @client }
49
49
 
50
50
  should have_readers :redis_config, :redis
51
- should have_imeths :enqueue, :push
51
+ should have_imeths :enqueue, :publish, :publish_as, :push
52
52
  should have_imeths :block_dequeue
53
53
  should have_imeths :append, :prepend
54
54
  should have_imeths :clear
55
+ should have_imeths :sync_subscriptions, :clear_subscriptions
56
+ should have_imeths :event_subscribers
55
57
 
56
58
  should "know its redis config" do
57
59
  assert_equal @redis_config, subject.redis_config
@@ -63,16 +65,51 @@ module Qs::Client
63
65
 
64
66
  should "build a job, enqueue it and return it using `enqueue`" do
65
67
  result = subject.enqueue(@queue, @job_name, @job_params)
66
- enqueued_job = subject.enqueued_jobs.last
67
- assert_equal @job_name, enqueued_job.name
68
- assert_equal @job_params, enqueued_job.params
69
- assert_equal enqueued_job, result
70
- end
71
-
72
- should "default the job's params to an empty hash using `enqueue`" do
73
- subject.enqueue(@queue, @job_name)
74
- enqueued_job = subject.enqueued_jobs.last
75
- assert_equal({}, enqueued_job.params)
68
+ call = subject.enqueue_calls.last
69
+ assert_equal @queue, call.queue
70
+ assert_equal @job_name, call.job.name
71
+ assert_equal @job_params, call.job.params
72
+ assert_equal call.job, result
73
+ end
74
+
75
+ should "enqueue a dispatch job and return its event using `publish`" do
76
+ event_channel = Factory.string
77
+ event_name = Factory.string
78
+ event_params = @job_params
79
+ result = subject.publish(event_channel, event_name, event_params)
80
+
81
+ call = subject.enqueue_calls.last
82
+ assert_equal Qs.dispatcher_queue, call.queue
83
+
84
+ dispatch_job = Factory.dispatch_job({
85
+ :event_channel => event_channel,
86
+ :event_name => event_name,
87
+ :event_params => event_params
88
+ })
89
+ assert_equal dispatch_job.name, call.job.name
90
+ assert_equal dispatch_job.params, call.job.params
91
+ assert_equal call.job.event, result
92
+ end
93
+
94
+ should "enqueue a dispatch job with a custom publisher using `publish_as`" do
95
+ publisher = Factory.string
96
+ channel = Factory.string
97
+ name = Factory.string
98
+ params = @job_params
99
+ result = subject.publish_as(publisher, channel, name, params)
100
+
101
+ call = subject.enqueue_calls.last
102
+ assert_equal Qs.dispatcher_queue, call.queue
103
+
104
+ dispatch_job = Factory.dispatch_job({
105
+ :event_channel => channel,
106
+ :event_name => name,
107
+ :event_params => params,
108
+ :event_publisher => publisher
109
+ })
110
+ assert_equal dispatch_job.name, call.job.name
111
+ assert_equal dispatch_job.params, call.job.params
112
+ assert_equal call.job.event, result
76
113
  end
77
114
 
78
115
  should "raise a not implemented error using `push`" do
@@ -88,8 +125,8 @@ module Qs::Client
88
125
  @connection_spy = HellaRedis::ConnectionSpy.new(@client.redis_config)
89
126
  Assert.stub(@client, :redis){ @connection_spy }
90
127
 
91
- @queue_redis_key = Factory.string
92
- @serialized_payload = Factory.string
128
+ @queue_redis_key = Factory.string
129
+ @encoded_payload = Factory.string
93
130
  end
94
131
 
95
132
  should "block pop from the front of a list using `block_dequeue`" do
@@ -101,22 +138,22 @@ module Qs::Client
101
138
  assert_equal args, call.args
102
139
  end
103
140
 
104
- should "add a serialized payload to the end of a list using `append`" do
105
- subject.append(@queue_redis_key, @serialized_payload)
141
+ should "add a encoded payload to the end of a list using `append`" do
142
+ subject.append(@queue_redis_key, @encoded_payload)
106
143
 
107
144
  call = @connection_spy.redis_calls.last
108
- assert_equal :lpush, call.command
109
- assert_equal @queue_redis_key, call.args.first
110
- assert_equal @serialized_payload, call.args.last
145
+ assert_equal :lpush, call.command
146
+ assert_equal @queue_redis_key, call.args.first
147
+ assert_equal @encoded_payload, call.args.last
111
148
  end
112
149
 
113
- should "add a serialized payload to the front of a list using `prepend`" do
114
- subject.prepend(@queue_redis_key, @serialized_payload)
150
+ should "add a encoded payload to the front of a list using `prepend`" do
151
+ subject.prepend(@queue_redis_key, @encoded_payload)
115
152
 
116
153
  call = @connection_spy.redis_calls.last
117
- assert_equal :rpush, call.command
118
- assert_equal @queue_redis_key, call.args.first
119
- assert_equal @serialized_payload, call.args.last
154
+ assert_equal :rpush, call.command
155
+ assert_equal @queue_redis_key, call.args.first
156
+ assert_equal @encoded_payload, call.args.last
120
157
  end
121
158
 
122
159
  should "del a list using `clear`" do
@@ -134,6 +171,96 @@ module Qs::Client
134
171
  assert_equal :ping, call.command
135
172
  end
136
173
 
174
+ should "return the events subscriber set using `event_subscribers`" do
175
+ smembers_key = nil
176
+ smembers = Factory.integer(3).times.map{ Factory.string }
177
+ Assert.stub(@connection_spy.redis_spy, :smembers) do |key|
178
+ smembers_key = key
179
+ smembers
180
+ end
181
+
182
+ event = Factory.event
183
+ result = subject.event_subscribers(event)
184
+
185
+ assert_equal event.subscribers_redis_key, smembers_key
186
+ assert_equal smembers, result
187
+ end
188
+
189
+ end
190
+
191
+ class SubscriptionsSetupTests < RedisCallTests
192
+ setup do
193
+ @event_subs_keys = Factory.integer(3).times.map{ Factory.string }
194
+ @keys_pattern = nil
195
+ Assert.stub(@connection_spy.redis_spy, :keys) do |pattern|
196
+ @keys_pattern = pattern
197
+ @event_subs_keys
198
+ end
199
+ end
200
+
201
+ end
202
+
203
+ class SyncSubscriptionsTests < SubscriptionsSetupTests
204
+ desc "sync_subscriptions"
205
+ setup do
206
+ Factory.integer(3).times.map{ @queue.event_route_names << Factory.string }
207
+ subject.sync_subscriptions(@queue)
208
+ end
209
+
210
+ should "run in a pipelined transaction" do
211
+ calls = @connection_spy.redis_calls[0, 2]
212
+ assert_equal [:pipelined, :multi], calls.map(&:command)
213
+ end
214
+
215
+ should "find all event subscribers keys" do
216
+ assert_equal Qs::Event::SubscribersRedisKey.new('*'), @keys_pattern
217
+ end
218
+
219
+ should "remove the queue from all events subscribers first" do
220
+ calls = @connection_spy.redis_calls[2, @event_subs_keys.size]
221
+ assert_equal @event_subs_keys.size, calls.size
222
+ assert_equal [:srem], calls.map(&:command).uniq
223
+ exp = @event_subs_keys.map{ |key| [key, @queue.name] }
224
+ assert_equal exp, calls.map(&:args)
225
+ end
226
+
227
+ should "remove and add the queue from events subscribers" do
228
+ start_at = 2 + @event_subs_keys.size
229
+ calls = @connection_spy.redis_calls[start_at..-1]
230
+ exp = @queue.event_route_names.size
231
+ assert_equal exp, calls.size
232
+ assert_equal [:sadd], calls.map(&:command).uniq
233
+ exp = @queue.event_route_names.map do |name|
234
+ [Qs::Event::SubscribersRedisKey.new(name), @queue.name]
235
+ end
236
+ assert_equal exp, calls.map(&:args)
237
+ end
238
+
239
+ end
240
+
241
+ class ClearSubscriptionsTests < SubscriptionsSetupTests
242
+ desc "clear_subscriptions"
243
+ setup do
244
+ subject.clear_subscriptions(@queue)
245
+ end
246
+
247
+ should "run in a pipelined transaction" do
248
+ calls = @connection_spy.redis_calls[0, 2]
249
+ assert_equal [:pipelined, :multi], calls.map(&:command)
250
+ end
251
+
252
+ should "find all event subscribers keys" do
253
+ assert_equal Qs::Event::SubscribersRedisKey.new('*'), @keys_pattern
254
+ end
255
+
256
+ should "remove the queue from all events subscribers" do
257
+ calls = @connection_spy.redis_calls[2..-1]
258
+ assert_equal @event_subs_keys.size, calls.size
259
+ assert_equal [:srem], calls.map(&:command).uniq
260
+ exp = @event_subs_keys.map{ |key| [key, @queue.name] }
261
+ assert_equal exp, calls.map(&:args)
262
+ end
263
+
137
264
  end
138
265
 
139
266
  class QsClientTests < UnitTests
@@ -167,25 +294,25 @@ module Qs::Client
167
294
  assert_equal @connection_spy, subject.redis
168
295
  end
169
296
 
170
- should "add jobs to the queue's redis list using `enqueue`" do
297
+ should "add jobs to the queues redis list using `enqueue`" do
171
298
  subject.enqueue(@queue, @job_name, @job_params)
172
299
 
173
300
  call = @connection_spy.redis_calls.last
174
301
  assert_equal :lpush, call.command
175
302
  assert_equal @queue.redis_key, call.args.first
176
- payload = Qs.deserialize(call.args.last)
177
- assert_equal @job_name, payload['name']
178
- assert_equal @job_params, payload['params']
303
+ job = Qs::Payload.deserialize(call.args.last)
304
+ assert_equal @job_name, job.name
305
+ assert_equal @job_params, job.params
179
306
  end
180
307
 
181
- should "add payloads to the queue's redis list using `push`" do
182
- job = Qs::Job.new(@job_name, @job_params)
183
- subject.push(@queue.name, job.to_payload)
308
+ should "add a payload hash to the queues redis list using `push`" do
309
+ payload_hash = { Factory.string => Factory.string }
310
+ subject.push(@queue.name, payload_hash)
184
311
 
185
312
  call = @connection_spy.redis_calls.last
186
313
  assert_equal :lpush, call.command
187
314
  assert_equal @queue.redis_key, call.args.first
188
- assert_equal Qs.serialize(job.to_payload), call.args.last
315
+ assert_equal Qs.encode(payload_hash), call.args.last
189
316
  end
190
317
 
191
318
  end
@@ -206,10 +333,10 @@ module Qs::Client
206
333
  class TestClientInitTests < TestClientTests
207
334
  desc "when init"
208
335
  setup do
209
- @serialized_payload = nil
210
- Assert.stub(Qs, :serialize){ |payload| @serialized_payload = payload }
211
-
212
- @job = Qs::Job.new(@job_name, @job_params)
336
+ @encoded_hash = nil
337
+ Assert.stub(Qs, :encode){ |hash| @encoded_hash = hash }
338
+ @serialized_job = nil
339
+ Assert.stub(Qs::Payload, :serialize){ |job| @serialized_job = job }
213
340
 
214
341
  @client = @client_class.new(@redis_config)
215
342
  end
@@ -237,24 +364,34 @@ module Qs::Client
237
364
  assert_equal job, result
238
365
  end
239
366
 
240
- should "serialize the jobs when enqueueing" do
367
+ should "serialize the job when enqueueing" do
241
368
  subject.enqueue(@queue, @job_name, @job_params)
242
369
 
243
370
  job = @queue.enqueued_jobs.last
244
- assert_equal job.to_payload, @serialized_payload
371
+ assert_equal job, @serialized_job
245
372
  end
246
373
 
247
- should "track all the payloads pushed onto a queue" do
248
- subject.push(@queue.name, @job.to_payload)
374
+ should "track all the payload hashes pushed onto a queue" do
375
+ payload_hash = { Factory.string => Factory.string }
376
+ subject.push(@queue.name, payload_hash)
249
377
 
250
378
  pushed_item = subject.pushed_items.last
251
379
  assert_instance_of Qs::TestClient::PushedItem, pushed_item
252
- assert_equal @queue.name, pushed_item.queue_name
253
- assert_equal @job.to_payload, pushed_item.payload
380
+ assert_equal @queue.name, pushed_item.queue_name
381
+ assert_equal payload_hash, pushed_item.payload_hash
382
+ end
383
+
384
+ should "encode the payload hashes when pushing them onto a queue" do
385
+ payload_hash = { Factory.string => Factory.string }
386
+ subject.push(@queue.name, payload_hash)
387
+
388
+ pushed_item = subject.pushed_items.last
389
+ assert_equal pushed_item.payload_hash, @encoded_hash
254
390
  end
255
391
 
256
392
  should "clear its pushed items when reset" do
257
- subject.push(@queue.name, @job.to_payload)
393
+ payload_hash = { Factory.string => Factory.string }
394
+ subject.push(@queue.name, payload_hash)
258
395
  assert_not_empty subject.pushed_items
259
396
  subject.reset!
260
397
  assert_empty subject.pushed_items
@@ -265,12 +402,14 @@ module Qs::Client
265
402
  class FakeClient
266
403
  include Qs::Client
267
404
 
268
- attr_reader :enqueued_jobs
405
+ attr_reader :enqueue_calls
269
406
 
270
407
  def enqueue!(queue, job)
271
- @enqueued_jobs ||= []
272
- @enqueued_jobs << job
408
+ @enqueue_calls ||= []
409
+ EnqueueCall.new(queue, job).tap{ |c| @enqueue_calls << c }
273
410
  end
411
+
412
+ EnqueueCall = Struct.new(:queue, :job)
274
413
  end
275
414
 
276
415
  end