qs 0.3.0 → 0.4.0

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