├── .gitignore
├── .rbenv-version
├── .rspec
├── .rvmrc
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.mdown
├── Rakefile
├── lib
├── resque-bus.rb
└── resque_bus
│ ├── adapter.rb
│ ├── compatibility
│ ├── deprecated.rb
│ ├── driver.rb
│ ├── heartbeat.rb
│ ├── publisher.rb
│ ├── rider.rb
│ ├── subscriber.rb
│ └── task_manager.rb
│ ├── server.rb
│ ├── server
│ └── views
│ │ └── bus.erb
│ ├── tasks.rb
│ └── version.rb
├── resque-bus.gemspec
└── spec
├── adapter
├── compatibility_spec.rb
├── integration_spec.rb
├── publish_at_spec.rb
├── retry_spec.rb
└── support.rb
├── adapter_spec.rb
├── application_spec.rb
├── config_spec.rb
├── dispatch_spec.rb
├── driver_spec.rb
├── heartbeat_spec.rb
├── integration_spec.rb
├── matcher_spec.rb
├── publish_spec.rb
├── publisher_spec.rb
├── rider_spec.rb
├── spec_helper.rb
├── subscriber_spec.rb
├── subscription_list_spec.rb
├── subscription_spec.rb
└── worker_spec.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/.rbenv-version:
--------------------------------------------------------------------------------
1 | 1.9.3-p194
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
--------------------------------------------------------------------------------
/.rvmrc:
--------------------------------------------------------------------------------
1 | rvm use 1.9.3-p194@resque-bus --install --create
2 | export PATH=./bin:$PATH
3 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [0.7.0] 2019-07-29
10 |
11 | ### Added
12 | - Adds `QueueBus.has_adapter?` to check whether the adapter is set before setting it to resque. This will allow multiple adapters to be loaded without error.
13 |
14 | ### Changed
15 | - Bump version dependency of queue-bus to at least 0.7
16 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem "rake"
6 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Brian Leonard
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.mdown:
--------------------------------------------------------------------------------
1 | ## Resque Bus
2 |
3 | This gem provides an adapter for Resque for use in the [queue-bus](https://github.com/queue-bus/queue-bus) system.
4 | It uses Redis and the Resque that you are already using to allow simple asynchronous communication between apps.
5 |
6 | ### Install
7 |
8 | To install, include the 'resque-bus' gem and add the following to your Rakefile:
9 |
10 | ```ruby
11 | require "resque_bus/tasks"
12 | ```
13 |
14 |
15 | ### Example
16 |
17 | Application A can publish an event
18 |
19 | ```ruby
20 | # pick an adapter
21 | require 'resque-bus' # (or other adapter)
22 |
23 | # business logic
24 | QueueBus.publish("user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
25 |
26 | # or do it later
27 | QueueBus.publish_at(1.hour.from_now, "user_created", "id" => 42, "first_name" => "John", "last_name" => "Smith")
28 | ```
29 |
30 | Application B is subscribed to events
31 |
32 | ```ruby
33 | # pick an adapter
34 | require 'resque-bus' # (or other adapter)
35 |
36 | # initializer
37 | QueueBus.dispatch("app_b") do
38 | # processes event on app_b_default queue
39 | # subscribe is short-hand to subscribe to your 'default' queue and this block with process events with the name "user_created"
40 | subscribe "user_created" do |attributes|
41 | NameCount.find_or_create_by_name(attributes["last_name"]).increment!
42 | end
43 |
44 | # processes event on app_b_critical queue
45 | # critical is short-hand to subscribe to your 'critical' queue and this block with process events with the name "user_paid"
46 | critical "user_paid" do |attributes|
47 | CreditCard.charge!(attributes)
48 | end
49 |
50 | # you can pass any queue name you would like to process from as well IE: `banana "peeled" do |attributes|`
51 |
52 | # and regexes work as well. note that with the above configuration along with this regex,
53 | # the following as well as the corresponding block above would both be executed
54 | subscribe /^user_/ do |attributes|
55 | Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
56 | end
57 |
58 | # the above all filter on just the event_type, but you can filter on anything
59 | # this would be _any_ event that has a user_id and the page value of homepage regardless of bus_event_type
60 | subscribe "my_key", { "user_id" => :present, "page" => "homepage"} do
61 | Mixpanel.homepage_action!(attributes["action"])
62 | end
63 | end
64 | ```
65 |
66 | Applications can also subscribe within classes using the provided `Subscriber` module.
67 |
68 | ```ruby
69 | class SimpleSubscriber
70 | include QueueBus::Subscriber
71 | subscribe :my_method
72 |
73 | def my_method(attributes)
74 | # heavy lifting
75 | end
76 | end
77 | ```
78 |
79 | The following is equivalent to the original initializer and shows more options:
80 |
81 | ```ruby
82 | class OtherSubscriber
83 | include QueueBus::Subscriber
84 | application :app_b
85 |
86 | subscribe :user_created
87 | subscribe_queue :app_b_critical, :user_paid
88 | subscribe_queue :app_b_default, :user_action, :bus_event_type => /^user_/
89 | subscribe :homepage_method, :user_id => :present, :page => "homepage"
90 |
91 | def user_created(attributes)
92 | NameCount.find_or_create_by_name(attributes["last_name"]).increment!
93 | end
94 |
95 | def user_paid(attributes)
96 | CreditCard.charge!(attributes)
97 | end
98 |
99 | def user_action(attributes)
100 | Metrics.record_user_action(attributes["bus_event_type"], attributes["id"])
101 | end
102 |
103 | def homepage_method
104 | Mixpanel.homepage_action!(attributes["action"])
105 | end
106 | end
107 | ```
108 |
109 | Note: This subscribes when this class is loaded, so it needs to be in your load or otherwise referenced/required during app initialization to work properly.
110 |
111 | ### Commands
112 |
113 | Each app needs to tell Redis about its subscriptions:
114 |
115 | $ rake queuebus:subscribe
116 |
117 | The subscription block is run inside a Resque worker which needs to be started for each app.
118 |
119 | $ rake queuebus:setup resque:work
120 |
121 | The incoming queue also needs to be processed on a dedicated or all the app servers.
122 |
123 | $ rake queuebus:driver resque:work
124 |
125 | If you want retry to work for subscribing apps, you should run resque-scheduler
126 |
127 | $ rake resque:scheduler
128 |
129 |
130 | ### Heartbeat
131 |
132 | We've found it useful to have the bus act like `cron`, triggering timed jobs throughout the system. Resque Bus calls this a heartbeat.
133 | It uses resque-scheduler to trigger the events. You can enable it in your Rakefile.
134 |
135 | ```ruby
136 | # resque.rake
137 | namespace :resque do
138 | task :setup => [:environment] do
139 | QueueBus.heartbeat!
140 | end
141 | end
142 | ```
143 |
144 | Or add it to your `schedule.yml` directly
145 |
146 | ```yaml
147 | resquebus_heartbeat:
148 | cron: "* * * * *"
149 | class: "::QueueBus::Heartbeat"
150 | queue: bus_incoming
151 | description: "I publish a heartbeat_minutes event every minute"
152 | ```
153 |
154 | It is the equivalent of doing this every minute
155 |
156 | ```ruby
157 | seconds = minutes * (60)
158 | hours = minutes / (60)
159 | days = minutes / (60*24)
160 |
161 | now = Time.at(seconds)
162 |
163 | attributes = {}
164 |
165 | now = Time.now
166 | seconds = now.to_i
167 | QueueBus.publish("hearbeat_minutes", {
168 | "epoch_seconds" => seconds,
169 | "epoch_minutes" => seconds / 1.minute,
170 | "epoch_hours" => seconds / 1.hour,
171 | "epoch_days" => seconds / 1.day,
172 | "minute" => now.min
173 | "hour" => now.hour
174 | "day" => now.day
175 | "month" => now.month
176 | "year" => now.year
177 | "yday" => now.yday
178 | "wday" => now.wday
179 | })
180 | ```
181 |
182 | This allows you do something like this:
183 |
184 | ```ruby
185 | QueueBus.dispatch("app_c") do
186 | # runs at 10:20, 11:20, etc
187 | subscribe "once_an_hour", 'bus_event_type' => 'heartbeat_minutes', 'minute' => 20 do |attributes|
188 | Sitemap.generate!
189 | end
190 |
191 | # runs every five minutes
192 | subscribe "every_five_minutes", 'bus_event_type' => 'heartbeat_minutes' do |attributes|
193 | next unless attributes["epoch_minutes"] % 5 == 0
194 | HealthCheck.run!
195 | end
196 |
197 | # runs at 8am on the first of every month
198 | subscribe "new_month_morning", 'bus_event_type' => 'heartbeat_minutes', 'day' => 1, hour' => 8, 'minute' => 0, do |attributes|
199 | next unless attributes["epoch_minutes"] % 5 == 0
200 | Token.old.expire!
201 | end
202 | end
203 | ```
204 |
205 | ### Local Mode
206 |
207 | For development, a local mode is provided and is specified in the configuration.
208 |
209 | ```ruby
210 | # config
211 | QueueBus.local_mode = :standalone
212 | or
213 | QueueBus.local_mode = :inline
214 | ```
215 |
216 | Standalone mode does not require a separate queuebus:driver task to be running to process the
217 | incoming queue. Simply publishing to the bus will distribute the incoming events
218 | to the appropriate application specific queue. A separate queuebus:work task does
219 | still need to be run to process these events
220 |
221 | Inline mode skips queue processing entirely and directly dispatches the
222 | event to the appropriate code block.
223 |
224 | You can also say `QueueBus.local_mode = :suppress` to turn off publishing altogether.
225 | This can be helpful inside some sort of migration, for example.
226 |
227 | ### TODO
228 |
229 | * Replace local modes with adapters
230 | * Make this not freak out in development without Redis or when Redis is down
231 | * We might not actually need to publish in tests
232 | * Add some rspec helpers for the apps to use: should_ post an event_publish or something along those lines
233 | * Allow calling queuebus:setup and queuebus:driver together (append to ENV['QUEUES'], don't replace it)
234 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift 'lib'
2 | require 'resque_bus/tasks'
3 |
--------------------------------------------------------------------------------
/lib/resque-bus.rb:
--------------------------------------------------------------------------------
1 | require "queue-bus"
2 | require "resque_bus/adapter"
3 | require "resque_bus/version"
4 |
5 | module ResqueBus
6 | # TODO: all of this will be removed
7 |
8 | autoload :Deprecated, 'resque_bus/compatibility/deprecated'
9 | autoload :Subscriber, 'resque_bus/compatibility/subscriber'
10 | autoload :TaskManager, 'resque_bus/compatibility/task_manager'
11 | autoload :Driver, 'resque_bus/compatibility/driver'
12 | autoload :Rider, 'resque_bus/compatibility/rider'
13 | autoload :Publisher, 'resque_bus/compatibility/publisher'
14 | autoload :Heartbeat, 'resque_bus/compatibility/heartbeat'
15 |
16 | extend ::ResqueBus::Deprecated
17 | end
18 |
19 | if QueueBus.has_adapter?
20 | warn '[ResqueBus] Not setting adapter on queue-bus because ' \
21 | "#{QueueBus.adapter.class.name} is already the adapter"
22 | else
23 | QueueBus.adapter = QueueBus::Adapters::Resque.new
24 | end
25 |
--------------------------------------------------------------------------------
/lib/resque_bus/adapter.rb:
--------------------------------------------------------------------------------
1 | module QueueBus
2 | module Adapters
3 | class Resque < QueueBus::Adapters::Base
4 | def enabled!
5 | # know we are using it
6 | require 'resque'
7 | require 'resque/scheduler'
8 | require 'resque-retry'
9 |
10 | QueueBus::Worker.extend(::Resque::Plugins::ExponentialBackoff)
11 | QueueBus::Worker.extend(::QueueBus::Adapters::Resque::RetryHandlers)
12 | end
13 |
14 | def redis(&block)
15 | block.call(::Resque.redis)
16 | end
17 |
18 | def enqueue(queue_name, klass, json)
19 | ::Resque.enqueue_to(queue_name, klass, json)
20 | end
21 |
22 | def enqueue_at(epoch_seconds, queue_name, klass, json)
23 | ::Resque.enqueue_at_with_queue(queue_name, epoch_seconds, klass, json)
24 | end
25 |
26 | def setup_heartbeat!(queue_name)
27 | # turn on the heartbeat
28 | # should be down after loading scheduler yml if you do that
29 | # otherwise, anytime
30 | name = 'resquebus_heartbeat'
31 | schedule = { 'class' => '::QueueBus::Worker',
32 | 'args'=>[::QueueBus::Util.encode({'bus_class_proxy' => '::QueueBus::Heartbeat'})],
33 | 'cron' => '* * * * *', # every minute
34 | 'queue' => queue_name,
35 | 'description' => 'I publish a heartbeat_minutes event every minute'
36 | }
37 | if ::Resque::Scheduler.dynamic
38 | ::Resque.set_schedule(name, schedule)
39 | end
40 | ::Resque.schedule[name] = schedule
41 | end
42 |
43 | private
44 |
45 | module RetryHandlers
46 | # @failure_hooks_already_ran on https://github.com/defunkt/resque/tree/1-x-stable
47 | # to prevent running twice
48 | def queue
49 | @my_queue
50 | end
51 |
52 | def on_failure_aaa(exception, *args)
53 | # note: sorted alphabetically
54 | # queue needs to be set for rety to work (know what queue in Requeue.class_to_queue)
55 | hash = ::QueueBus::Util.decode(args[0])
56 | @my_queue = hash["bus_rider_queue"]
57 | end
58 |
59 | def on_failure_zzz(exception, *args)
60 | # note: sorted alphabetically
61 | @my_queue = nil
62 | end
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/deprecated.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | module Deprecated
3 | def show_deprecations=val
4 | @show_deprecations = val
5 | end
6 |
7 | def show_deprecations?
8 | return @show_deprecations if defined?(@show_deprecations)
9 | return true if !ENV['QUEUES'] && !ENV['QUEUE'] # not in background, probably test
10 | return true if ENV['VVERBOSE'] || ENV['LOGGING'] || ENV['VERBOSE']
11 | false
12 | end
13 |
14 | def note_deprecation(message)
15 | @noted_deprecations ||= {}
16 | if @noted_deprecations[message]
17 | @noted_deprecations[message] += 1
18 | else
19 | warn(message) if show_deprecations?
20 | @noted_deprecations[message] = 1
21 | end
22 | end
23 |
24 | def redis
25 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus direct usage is deprecated. Use `QueueBus.redis` instead. Note that it also requires block usage now."
26 | ::Resque.redis
27 | end
28 |
29 | def redis=val
30 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus can no longer set redis directly. It will use Resque's instance of redis."
31 | end
32 |
33 | def method_missing(method_name, *args, &block)
34 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus direct usage is deprecated. Use `QueueBus.#{method_name}` instead."
35 | ::QueueBus.send(method_name, *args, &block)
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/driver.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | class Driver
3 | class << self
4 | def perform(attributes={})
5 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Driver"
6 | ::QueueBus::Driver.perform(attributes)
7 | end
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/heartbeat.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | class Heartbeat
3 | class << self
4 | def perform(attributes={})
5 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Heartbeat"
6 | ::QueueBus::Heartbeat.perform(attributes)
7 | end
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/publisher.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | # publishes on a delay
3 | class Publisher
4 | class << self
5 | def perform(event_type, attributes = {})
6 | attributes["bus_event_type"] = event_type # now using one hash only
7 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Publisher"
8 | ::QueueBus::Publisher.perform(attributes)
9 | end
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/rider.rb:
--------------------------------------------------------------------------------
1 | require 'resque-retry'
2 |
3 | module ResqueBus
4 | class Rider
5 | extend Resque::Plugins::ExponentialBackoff
6 |
7 | class << self
8 | def perform(attributes = {})
9 | ResqueBus.note_deprecation "[MIGRATION] Note: new events will be using QueueBus::Rider"
10 | ::QueueBus::Rider.perform(attributes)
11 | end
12 |
13 | # @failure_hooks_already_ran on https://github.com/defunkt/resque/tree/1-x-stable
14 | # to prevent running twice
15 | def queue
16 | @my_queue
17 | end
18 |
19 | def on_failure_aaa(exception, *args)
20 | # note: sorted alphabetically
21 | # queue needs to be set for rety to work (know what queue in Requeue.class_to_queue)
22 | @my_queue = args[0]["bus_rider_queue"]
23 | end
24 |
25 | def on_failure_zzz(exception, *args)
26 | # note: sorted alphabetically
27 | @my_queue = nil
28 | end
29 |
30 | end
31 | end
32 | end
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/subscriber.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | module Subscriber
3 | def self.included(base)
4 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus::Subscriber is deprecated. Use QueueBus::Subscriber instead."
5 | base.send(:include, ::QueueBus::Subscriber)
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/resque_bus/compatibility/task_manager.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | class TaskManager < ::QueueBus::TaskManager
3 | def initialize(logging)
4 | ResqueBus.note_deprecation "[DEPRECATION] ResqueBus::TaskManager is deprecated. Use QueueBus::TaskManager instead."
5 | super(logging)
6 | end
7 | end
8 | end
--------------------------------------------------------------------------------
/lib/resque_bus/server.rb:
--------------------------------------------------------------------------------
1 | require 'resque-bus'
2 | require 'resque/server'
3 | require 'erb'
4 |
5 | # MIGRATE TODO: move to resque gem
6 | # Extend ::Resque::Server to add tabs.
7 | module ResqueBus
8 | module Server
9 |
10 | def self.included(base)
11 | base.class_eval {
12 |
13 | get "/bus" do
14 | erb File.read(File.join(File.dirname(__FILE__), "server/views/bus.erb"))
15 | end
16 |
17 |
18 | post '/bus/unsubscribe' do
19 | app = ::QueueBus::Application.new(params[:name]).unsubscribe
20 | redirect u('bus')
21 | end
22 |
23 | }
24 | end
25 | end
26 | end
27 |
28 | ::Resque::Server.tabs << 'Bus'
29 | ::Resque::Server.class_eval do
30 | include ::ResqueBus::Server
31 | end
--------------------------------------------------------------------------------
/lib/resque_bus/server/views/bus.erb:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 | <%
16 | app_hash = {}
17 | class_hash = {}
18 | event_hash = {}
19 |
20 | # collect each differently
21 | ::QueueBus::Application.all.each do |app|
22 | app_key = app.app_key
23 |
24 | app_hash[app_key] ||= []
25 | app.event_display_tuples.each do |tuple|
26 | class_name, queue, filters = tuple
27 | if filters["bus_event_type"]
28 | event = filters["bus_event_type"]
29 | else
30 | event = "see filter"
31 | end
32 |
33 | app_hash[app_key] << [event, class_name, queue, filters]
34 |
35 | class_hash[class_name] ||= []
36 | class_hash[class_name] << [app_key, event, queue, filters]
37 |
38 | event_hash[event] ||= []
39 | event_hash[event] << [app_key, class_name, queue, filters]
40 | end
41 | end
42 |
43 | # sort each list item by secondary label
44 | class_hash.each do |_, array|
45 | array.sort!{ |a,b| a.first <=> b.first }
46 | end
47 | event_hash.each do |_, array|
48 | array.sort!{ |a,b| a.first <=> b.first }
49 | end
50 |
51 | # helper to display either
52 | def display_row(name, val, button=nil, first=false)
53 | form = ""
54 | if button
55 | text, url = button
56 | form = "
"
57 | end
58 |
59 | if !val
60 | out = " | | "
61 | else
62 | one, two, queue, filters = val
63 | out = "#{h(one)} | #{h(two)} | #{h(queue)} | "
64 | out << "#{h(::QueueBus::Util.encode(filters).gsub(/\"bus_special_value_(\w+)\"/){ "(#{$1})" }).gsub(" ", " ").gsub('","', '", "')} | "
65 | end
66 |
67 | if first
68 | "#{h(name)}#{form} | #{out}
\n"
69 | else
70 | " | #{out}
\n"
71 | end
72 | end
73 |
74 | def output_hash(hash, action=nil)
75 | out = ""
76 | hash.keys.sort.each do |item|
77 | display = hash[item]
78 | first = display.shift
79 | out << display_row(item, first, action, true)
80 | display.each do |val|
81 | out << display_row(item, val, action)
82 | end
83 | end
84 | out
85 | end
86 | %>
87 |
88 |
89 |
90 | Applications
91 | The apps below have registered the given classes and queues.
92 |
93 |
94 | App Key |
95 | Event Type |
96 | Class Name |
97 | Queue |
98 | Filters |
99 |
100 | <%= output_hash(app_hash, ["Unsubscribe", "bus/unsubscribe"]) %>
101 |
102 |
103 |
104 |
105 | Events
106 | The events below have been registered by the given applications and queues.
107 |
108 |
109 | Event Type |
110 | App Key |
111 | Class Name |
112 | Queue |
113 | Filters |
114 |
115 | <%= output_hash(event_hash, false) %>
116 |
117 |
118 |
119 |
120 |
121 |
122 | Classes
123 | The classes below have been registered by the given applications and queues.
124 |
125 |
126 | Class Name |
127 | App Key |
128 | Event Type |
129 | Queue |
130 | Filters |
131 |
132 | <%= output_hash(class_hash, false) %>
133 |
134 |
--------------------------------------------------------------------------------
/lib/resque_bus/tasks.rb:
--------------------------------------------------------------------------------
1 | # require 'resque_bus/tasks'
2 | # will give you these tasks
3 |
4 | require "queue_bus/tasks"
5 | require "resque/tasks"
6 |
7 | namespace :resquebus do
8 | # deprecated
9 | task :setup => ["queuebus:setup"] do
10 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:setup is deprecated. Use rake queuebus:setup instead."
11 | end
12 |
13 | task :driver => ["queuebus:driver"] do
14 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:driver is deprecated. Use rake queuebus:driver instead."
15 | end
16 |
17 | task :subscribe => ["queuebus:subscribe"] do
18 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:subscribe is deprecated. Use rake queuebus:subscribe instead."
19 | end
20 |
21 | task :unsubsribe => ["queuebus:unsubsribe"] do
22 | ResqueBus.note_deprecation "[DEPRECATION] rake resquebus:driver is deprecated. Use rake queuebus:unsubsribe instead."
23 | end
24 | end
25 |
26 | namespace :queuebus do
27 |
28 | desc "Setup will configure a resque task to run before resque:work"
29 | task :setup => [ :preload ] do
30 |
31 | if ENV['QUEUES'].nil?
32 | manager = ::QueueBus::TaskManager.new(true)
33 | queues = manager.queue_names
34 | ENV['QUEUES'] = queues.join(",")
35 | else
36 | queues = ENV['QUEUES'].split(",")
37 | end
38 |
39 | if queues.size == 1
40 | puts " >> Working Queue : #{queues.first}"
41 | else
42 | puts " >> Working Queues: #{queues.join(", ")}"
43 | end
44 | end
45 |
46 | desc "Sets the queue to work the driver Use: `rake queuebus:driver resque:work`"
47 | task :driver => [ :preload ] do
48 | ENV['QUEUES'] = ::QueueBus.incoming_queue
49 | end
50 |
51 | # Preload app files if this is Rails
52 | task :preload do
53 | require "resque"
54 | require "resque-bus"
55 | require "resque/failure/redis"
56 | require "resque/failure/multiple_with_retry_suppression"
57 |
58 | Resque::Failure::MultipleWithRetrySuppression.classes = [Resque::Failure::Redis]
59 | Resque::Failure.backend = Resque::Failure::MultipleWithRetrySuppression
60 |
61 | Rake::Task["resque:setup"].invoke # loads the environment and such if defined
62 | end
63 |
64 |
65 | # examples to test out the system
66 | namespace :example do
67 | desc "Publishes events to example applications"
68 | task :publish => [ "queuebus:preload", "queuebus:setup" ] do
69 | which = ["one", "two", "three", "other"][rand(4)]
70 | QueueBus.publish("event_#{which}", { "rand" => rand(99999)})
71 | QueueBus.publish("event_all", { "rand" => rand(99999)})
72 | QueueBus.publish("none_subscribed", { "rand" => rand(99999)})
73 | puts "published event_#{which}, event_all, none_subscribed"
74 | end
75 |
76 | desc "Sets up an example config"
77 | task :register => [ "queuebus:preload"] do
78 | QueueBus.dispatch("example") do
79 | subscribe "event_one" do
80 | puts "event1 happened"
81 | end
82 |
83 | subscribe "event_two" do
84 | puts "event2 happened"
85 | end
86 |
87 | high "event_three" do
88 | puts "event3 happened (high)"
89 | end
90 |
91 | low "event_.*" do |attributes|
92 | puts "LOG ALL: #{attributes.inspect}"
93 | end
94 | end
95 | end
96 |
97 | desc "Subscribes this application to QueueBus example events"
98 | task :subscribe => [ :register, "queuebus:subscribe" ]
99 |
100 | desc "Start a QueueBus example worker"
101 | task :work => [ :register, "queuebus:setup", "resque:work" ]
102 |
103 | desc "Start a QueueBus example worker"
104 | task :driver => [ :register, "queuebus:driver", "resque:work" ]
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/lib/resque_bus/version.rb:
--------------------------------------------------------------------------------
1 | module ResqueBus
2 | VERSION = "0.7.0"
3 | end
4 |
--------------------------------------------------------------------------------
/resque-bus.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "resque_bus/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "resque-bus"
7 | s.version = ResqueBus::VERSION
8 | s.authors = ["Brian Leonard"]
9 | s.email = ["brian@bleonard.com"]
10 | s.homepage = "https://github.com/queue-bus/resque-bus"
11 | s.summary = %q{A simple event bus on top of Resque}
12 | s.description = %q{A simple event bus on top of Resque. Publish and subscribe to events as they occur through a queue.}
13 |
14 | s.files = `git ls-files`.split("\n")
15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17 | s.require_paths = ["lib"]
18 |
19 | s.add_dependency('queue-bus', ['>= 0.7', '< 1'])
20 | s.add_dependency('resque', ['>= 1.10.0', '< 2.0'])
21 | s.add_dependency('resque-scheduler', '>= 2.0.1')
22 | s.add_dependency('resque-retry')
23 |
24 | s.add_development_dependency("rspec")
25 | s.add_development_dependency("timecop")
26 | s.add_development_dependency("json_pure")
27 | end
28 |
--------------------------------------------------------------------------------
/spec/adapter/compatibility_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Compatibility with old resque-bus" do
4 | before(:each) do
5 | ResqueBus.show_deprecations = false # expected
6 |
7 | QueueBus.dispatch("r1") do
8 | subscribe "event_name" do |attributes|
9 | QueueBus::Runner1.run(attributes)
10 | end
11 | end
12 |
13 | QueueBus::TaskManager.new(false).subscribe!
14 |
15 | @incoming = Resque::Worker.new(:resquebus_incoming)
16 | @incoming.register_worker
17 |
18 | @new_incoming = Resque::Worker.new(:bus_incoming)
19 | @new_incoming.register_worker
20 |
21 | @rider = Resque::Worker.new(:r1_default)
22 | @rider.register_worker
23 | end
24 |
25 | describe "Publisher" do
26 | it "should still publish as expected" do
27 | val = QueueBus.redis { |redis| redis.lpop("queue:resquebus_incoming") }
28 | val.should == nil
29 |
30 | args = [ "event_name", {"bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12} ]
31 | item = {:class => "ResqueBus::Publisher", :args => args}
32 |
33 | QueueBus.redis { |redis| redis.sadd(:queues, "resquebus_incoming") }
34 | QueueBus.redis { |redis| redis.rpush "queue:resquebus_incoming", Resque.encode(item) }
35 |
36 | QueueBus::Runner1.value.should == 0
37 |
38 | perform_next_job @incoming # publish
39 |
40 | QueueBus::Runner1.value.should == 0
41 |
42 | perform_next_job @new_incoming # drive
43 |
44 | QueueBus::Runner1.value.should == 0
45 |
46 | perform_next_job @rider # ride
47 |
48 | QueueBus::Runner1.value.should == 1
49 |
50 | end
51 | end
52 |
53 | describe "Rider" do
54 | it "should still ride as expected" do
55 | val = QueueBus.redis { |redis| redis.lpop("queue:r1_default") }
56 | val.should == nil
57 |
58 | args = [ {"bus_rider_app_key"=>"r1", "x" => "y", "bus_event_type" => "event_name",
59 | "bus_rider_sub_key"=>"event_name", "bus_rider_queue" => "default",
60 | "bus_rider_class_name"=>"::ResqueBus::Rider"}]
61 | item = {:class => "ResqueBus::Rider", :args => args}
62 |
63 | QueueBus.redis { |redis| redis.sadd(:queues, "r1_default") }
64 | QueueBus.redis { |redis| redis.rpush "queue:r1_default", Resque.encode(item) }
65 |
66 | QueueBus::Runner1.value.should == 0
67 |
68 | perform_next_job @rider
69 |
70 | QueueBus::Runner1.value.should == 1
71 | end
72 | end
73 |
74 | describe "Driver" do
75 | it "should still drive as expected" do
76 | val = QueueBus.redis { |redis| redis.lpop("queue:resquebus_incoming") }
77 | val.should == nil
78 |
79 | args = [ {"bus_event_type" => "event_name", "two"=>"here", "one"=>1, "id" => 12} ]
80 | item = {:class => "ResqueBus::Driver", :args => args}
81 |
82 | QueueBus.redis { |redis| redis.sadd(:queues, "resquebus_incoming") }
83 | QueueBus.redis { |redis| redis.rpush "queue:resquebus_incoming", Resque.encode(item) }
84 |
85 | QueueBus::Runner1.value.should == 0
86 |
87 | perform_next_job @incoming
88 |
89 | QueueBus::Runner1.value.should == 0
90 |
91 | perform_next_job @rider
92 |
93 | QueueBus::Runner1.value.should == 1
94 |
95 | end
96 | end
97 | end
98 |
--------------------------------------------------------------------------------
/spec/adapter/integration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Resque Integration" do
4 | describe "Happy Path" do
5 | before(:each) do
6 | QueueBus.dispatch("r1") do
7 | subscribe "event_name" do |attributes|
8 | QueueBus::Runner1.run(attributes)
9 | end
10 | end
11 |
12 | QueueBus::TaskManager.new(false).subscribe!
13 |
14 | @incoming = Resque::Worker.new(:bus_incoming)
15 | @incoming.register_worker
16 |
17 | @rider = Resque::Worker.new(:r1_default)
18 | @rider.register_worker
19 | end
20 |
21 | it "should publish and receive" do
22 | QueueBus::Runner1.value.should == 0
23 |
24 | QueueBus.publish("event_name", "ok" => true)
25 | QueueBus::Runner1.value.should == 0
26 |
27 | perform_next_job @incoming
28 |
29 | QueueBus::Runner1.value.should == 0
30 |
31 | perform_next_job @rider
32 |
33 | QueueBus::Runner1.value.should == 1
34 | end
35 | end
36 |
37 | describe "Delayed Publishing" do
38 | before(:each) do
39 | Timecop.freeze(now)
40 | QueueBus.stub(:generate_uuid).and_return("idfhlkj")
41 | end
42 | after(:each) do
43 | Timecop.return
44 | end
45 | let(:delayed_attrs) { {"bus_delayed_until" => future.to_i,
46 | "bus_id" => "#{now.to_i}-idfhlkj",
47 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} }
48 |
49 | let(:bus_attrs) { delayed_attrs.merge({"bus_published_at" => worktime.to_i})}
50 | let(:now) { Time.parse("01/01/2013 5:00")}
51 | let(:future) { Time.at(now.to_i + 60) }
52 | let(:worktime) {Time.at(future.to_i + 1)}
53 |
54 | it "should add it to Redis" do
55 | hash = {:one => 1, "two" => "here", "id" => 12 }
56 | event_name = "event_name"
57 | QueueBus.publish_at(future, event_name, hash)
58 |
59 | schedule = QueueBus.redis { |redis| redis.zrange("delayed_queue_schedule", 0, 1) }
60 | schedule.should == [future.to_i.to_s]
61 |
62 | val = QueueBus.redis { |redis| redis.lpop("delayed:#{future.to_i}") }
63 | hash = JSON.parse(val)
64 |
65 | hash["class"].should == "QueueBus::Worker"
66 | hash["args"].size.should == 1
67 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs)
68 | hash["queue"].should == "bus_incoming"
69 | end
70 |
71 | it "should move it to the real queue when processing" do
72 | hash = {:one => 1, "two" => "here", "id" => 12 }
73 | event_name = "event_name"
74 |
75 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
76 | val.should == nil
77 |
78 | QueueBus.publish_at(future, event_name, hash)
79 |
80 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
81 | val.should == nil # nothing really added
82 |
83 | # process sceduler now
84 | Resque::Scheduler.handle_delayed_items
85 |
86 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
87 | val.should == nil # nothing added yet
88 |
89 | # process scheduler in future
90 | Timecop.freeze(worktime) do
91 | Resque::Scheduler.handle_delayed_items
92 |
93 | # added
94 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
95 | hash = JSON.parse(val)
96 | hash["class"].should == "QueueBus::Worker"
97 | hash["args"].size.should == 1
98 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs)
99 |
100 | QueueBus::Publisher.perform(JSON.parse(hash["args"].first))
101 |
102 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
103 | hash = JSON.parse(val)
104 | hash["class"].should == "QueueBus::Worker"
105 | hash["args"].size.should == 1
106 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Driver", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs)
107 | end
108 | end
109 |
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/spec/adapter/publish_at_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Publishing an event in the future" do
4 |
5 | before(:each) do
6 | Timecop.freeze(now)
7 | QueueBus.stub(:generate_uuid).and_return("idfhlkj")
8 | end
9 | after(:each) do
10 | Timecop.return
11 | end
12 | let(:delayed_attrs) { {"bus_delayed_until" => future.to_i,
13 | "bus_id" => "#{now.to_i}-idfhlkj",
14 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} }
15 |
16 | let(:bus_attrs) { delayed_attrs.merge({"bus_published_at" => worktime.to_i})}
17 | let(:now) { Time.parse("01/01/2013 5:00")}
18 | let(:future) { Time.at(now.to_i + 60) }
19 | let(:worktime) {Time.at(future.to_i + 1)}
20 |
21 | it "should add it to Redis then to the real queue" do
22 | hash = {:one => 1, "two" => "here", "id" => 12 }
23 | event_name = "event_name"
24 | QueueBus.publish_at(future, event_name, hash)
25 |
26 | schedule = QueueBus.redis { |redis| redis.zrange("delayed_queue_schedule", 0, 1) }
27 | schedule.should == [future.to_i.to_s]
28 |
29 | val = QueueBus.redis { |redis| redis.lpop("delayed:#{future.to_i}") }
30 | hash = JSON.parse(val)
31 |
32 | hash["class"].should == "QueueBus::Worker"
33 | hash["args"].size.should == 1
34 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Publisher", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(delayed_attrs)
35 | hash["queue"].should == "bus_incoming"
36 |
37 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
38 | val.should == nil # nothing really added
39 |
40 | Timecop.freeze(worktime)
41 | QueueBus::Publisher.perform(JSON.parse(hash["args"].first))
42 |
43 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
44 | hash = JSON.parse(val)
45 | hash["class"].should == "QueueBus::Worker"
46 | hash["args"].size.should == 1
47 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "QueueBus::Driver", "bus_event_type"=>"event_name", "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs)
48 | end
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/spec/adapter/retry_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Retry" do
4 | class RetryTest1
5 | include QueueBus::Subscriber
6 | application :my_thing
7 | subscribe :event_sub
8 | def event_sub(attributes)
9 | QueueBus::Runner1.run(attributes)
10 | end
11 | end
12 |
13 | it "should have the methods" do
14 | ::QueueBus::Worker.methods.should include(:on_failure_aaa)
15 | ::QueueBus::Worker.methods.should include(:on_failure_zzz)
16 | end
17 |
18 | # it "should retry failed riders"
19 |
20 | describe "Failed Jobs" do
21 | before(:each) do
22 | QueueBus.enqueue_to("testing", "QueueBus::Worker", { "bus_class_proxy" => "QueueBus::Rider", "bus_rider_app_key" => "r2", "bus_rider_sub_key" => "event_name", "bus_event_type" => "event_name", "ok" => true, "bus_rider_queue" => "testing" })
23 |
24 | @worker = Resque::Worker.new(:testing)
25 | @worker.register_worker
26 | end
27 |
28 | it "should put it in the failed jobs" do
29 |
30 | QueueBus.dispatch("r2") do
31 | subscribe "event_name" do |attributes|
32 | raise "boo!"
33 | end
34 | end
35 |
36 | perform_next_job @worker
37 | Resque.info[:processed].should == 1
38 | Resque.info[:failed].should == 1
39 | Resque.info[:pending].should == 1 # requeued
40 |
41 | perform_next_job @worker
42 | Resque.info[:processed].should == 2
43 | Resque.info[:failed].should == 2
44 | Resque.info[:pending].should == 0
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/adapter/support.rb:
--------------------------------------------------------------------------------
1 | require 'resque-bus'
2 | require 'resque'
3 | require 'resque/scheduler'
4 |
5 | def reset_test_adapter
6 | QueueBus.send(:reset)
7 | QueueBus.adapter = QueueBus::Adapters::Resque.new
8 | end
9 |
10 | def adapter_under_test_class
11 | QueueBus::Adapters::Resque
12 | end
13 |
14 | def adapter_under_test_symbol
15 | :resque
16 | end
17 |
18 | def perform_next_job(worker, &block)
19 | return unless job = worker.reserve
20 | worker.perform(job, &block)
21 | worker.done_working
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/spec/adapter_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "adapter is set" do
4 | it "should call it's enabled! method on init" do
5 | QueueBus.send(:reset)
6 | adapter_under_test_class.any_instance.should_receive(:enabled!)
7 | instance = adapter_under_test_class.new
8 | QueueBus.send(:reset)
9 | end
10 |
11 | it "should be defaulting to Data from spec_helper" do
12 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/application_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Application do
5 | describe ".all" do
6 | it "should return empty array when none" do
7 | Application.all.should == []
8 | end
9 | it "should return registered applications when there are some" do
10 | Application.new("One").subscribe(test_list(test_sub("fdksjh")))
11 | Application.new("Two").subscribe(test_list(test_sub("fdklhf")))
12 | Application.new("Three").subscribe(test_list(test_sub("fkld")))
13 |
14 | Application.all.collect(&:app_key).should =~ ["one", "two", "three"]
15 |
16 | Application.new("two").unsubscribe
17 | Application.all.collect(&:app_key).should =~ ["one", "three"]
18 | end
19 | end
20 |
21 | describe ".new" do
22 | it "should have a key" do
23 | Application.new("something").app_key.should == "something"
24 |
25 | Application.new("some thing").app_key.should == "some_thing"
26 | Application.new("some-thing").app_key.should == "some_thing"
27 | Application.new("some_thing").app_key.should == "some_thing"
28 | Application.new("Some Thing").app_key.should == "some_thing"
29 | end
30 |
31 | it "should raise an error if not valid" do
32 | lambda {
33 | Application.new("")
34 | }.should raise_error
35 |
36 | lambda {
37 | Application.new(nil)
38 | }.should raise_error
39 |
40 | lambda {
41 | Application.new("/")
42 | }.should raise_error
43 | end
44 | end
45 |
46 | describe "#read_redis_hash" do
47 | it "should handle old and new values" do
48 |
49 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "new_one", QueueBus::Util.encode("queue_name" => "x", "bus_event_type" => "event_name") ) }
50 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "old_one", "oldqueue_name") }
51 | app = Application.new("myapp")
52 | val = app.send(:read_redis_hash)
53 | val.should == {"new_one" => {"queue_name" => "x", "bus_event_type" => "event_name"}, "old_one" => "oldqueue_name"}
54 | end
55 | end
56 |
57 | describe "#subscribe" do
58 | let(:sub1) { test_sub("event_one", "default") }
59 | let(:sub2) { test_sub("event_two", "default") }
60 | let(:sub3) { test_sub("event_three", "other") }
61 | it "should add array to redis" do
62 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
63 | Application.new("myapp").subscribe(test_list(sub1, sub2))
64 |
65 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_two"=>"{\"queue_name\":\"default\",\"key\":\"event_two\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_two\"}}",
66 | "event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}"}
67 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_one", "event_two"]
68 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"]
69 | end
70 | it "should add string to redis" do
71 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
72 | Application.new("myapp").subscribe(test_list(sub1))
73 |
74 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}"}
75 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_one"]
76 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"]
77 | end
78 | it "should multiple queues to redis" do
79 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
80 | Application.new("myapp").subscribe(test_list(sub1, sub2, sub3))
81 | QueueBus.redis { |redis| redis.hgetall("bus_app:myapp") }.should == {"event_two"=>"{\"queue_name\":\"default\",\"key\":\"event_two\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_two\"}}", "event_one"=>"{\"queue_name\":\"default\",\"key\":\"event_one\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_one\"}}",
82 | "event_three"=>"{\"queue_name\":\"other\",\"key\":\"event_three\",\"class\":\"::QueueBus::Rider\",\"matcher\":{\"bus_event_type\":\"event_three\"}}"}
83 | QueueBus.redis { |redis| redis.hkeys("bus_app:myapp") }.should =~ ["event_three", "event_two", "event_one"]
84 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should =~ ["myapp"]
85 | end
86 |
87 | it "should do nothing if nil or empty" do
88 |
89 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
90 |
91 | Application.new("myapp").subscribe(nil)
92 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
93 |
94 | Application.new("myapp").subscribe([])
95 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
96 | end
97 |
98 | it "should call unsubscribe" do
99 | app = Application.new("myapp")
100 | app.should_receive(:unsubscribe)
101 | app.subscribe([])
102 | end
103 | end
104 |
105 | describe "#unsubscribe" do
106 | it "should remove items" do
107 | QueueBus.redis { |redis| redis.sadd("bus_apps", "myapp") }
108 | QueueBus.redis { |redis| redis.sadd("bus_apps", "other") }
109 | QueueBus.redis { |redis| redis.hset("bus_app:myapp", "event_one", "myapp_default") }
110 |
111 | Application.new("myapp").unsubscribe
112 |
113 | QueueBus.redis { |redis| redis.smembers("bus_apps") }.should == ["other"]
114 | QueueBus.redis { |redis| redis.get("bus_app:myapp") }.should be_nil
115 | end
116 | end
117 |
118 | describe "#subscription_matches" do
119 | it "should return if it is there" do
120 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
121 |
122 | subs = test_list(test_sub("one_x"), test_sub("one_y"), test_sub("one"), test_sub("two"))
123 | Application.new("myapp").subscribe(subs)
124 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
125 |
126 | Application.new("myapp").subscription_matches("bus_event_type"=>"two").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "two", "default", "::QueueBus::Rider"]]
127 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one", "default", "::QueueBus::Rider"]]
128 | end
129 |
130 | it "should handle * wildcards" do
131 | subs = test_list(test_sub("one.+"), test_sub("one"), test_sub("one_.*"), test_sub("two"))
132 | Application.new("myapp").subscribe(subs)
133 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
134 |
135 | Application.new("myapp").subscription_matches("bus_event_type"=>"onex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one.+", "default", "::QueueBus::Rider"]]
136 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one", "default", "::QueueBus::Rider"]]
137 | Application.new("myapp").subscription_matches("bus_event_type"=>"one_x").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one.+","default", "::QueueBus::Rider"], ["myapp", "one_.*", "default", "::QueueBus::Rider"]]
138 | end
139 |
140 | it "should handle actual regular expressions" do
141 | subs = test_list(test_sub(/one.+/), test_sub("one"), test_sub(/one_.*/), test_sub("two"))
142 | Application.new("myapp").subscribe(subs)
143 | Application.new("myapp").subscription_matches("bus_event_type"=>"three").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
144 |
145 | Application.new("myapp").subscription_matches("bus_event_type"=>"onex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"]]
146 | Application.new("myapp").subscription_matches("bus_event_type"=>"donex").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"]]
147 | Application.new("myapp").subscription_matches("bus_event_type"=>"one").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "one" ,"default", "::QueueBus::Rider"]]
148 | Application.new("myapp").subscription_matches("bus_event_type"=>"one_x").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["myapp", "(?-mix:one.+)", "default", "::QueueBus::Rider"], ["myapp", "(?-mix:one_.*)", "default", "::QueueBus::Rider"]]
149 | end
150 | end
151 | end
152 | end
153 |
--------------------------------------------------------------------------------
/spec/config_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | module Adapters
5 | class TestOne
6 |
7 | end
8 | end
9 | end
10 |
11 | describe "QueueBus config" do
12 | it "should set the default app key" do
13 | QueueBus.default_app_key.should == nil
14 |
15 | QueueBus.default_app_key = "my_app"
16 | QueueBus.default_app_key.should == "my_app"
17 |
18 | QueueBus.default_app_key = "something here"
19 | QueueBus.default_app_key.should == "something_here"
20 | end
21 |
22 | it "should set the default queue" do
23 | QueueBus.default_queue.should == nil
24 |
25 | QueueBus.default_queue = "my_queue"
26 | QueueBus.default_queue.should == "my_queue"
27 | end
28 |
29 | it "should set the local mode" do
30 | QueueBus.local_mode.should == nil
31 | QueueBus.local_mode = :standalone
32 | QueueBus.local_mode.should == :standalone
33 | end
34 |
35 | it "should set the hostname" do
36 | QueueBus.hostname.should_not == nil
37 | QueueBus.hostname = "whatever"
38 | QueueBus.hostname.should == "whatever"
39 | end
40 |
41 | it "should set before_publish callback" do
42 | QueueBus.before_publish = lambda {|attributes| 42 }
43 | QueueBus.before_publish_callback({}).should == 42
44 | end
45 |
46 |
47 | it "should use the default Redis connection" do
48 | QueueBus.redis { |redis| redis }.should_not eq(nil)
49 | end
50 |
51 | it "should default to given adapter" do
52 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true
53 |
54 | # and should raise if already set
55 | lambda {
56 | QueueBus.adapter = :data
57 | }.should raise_error
58 | end
59 |
60 | context "with a fresh load" do
61 | before(:each) do
62 | QueueBus.send(:reset)
63 | end
64 |
65 | it "should be able to be set to resque" do
66 | QueueBus.adapter = adapter_under_test_symbol
67 | QueueBus.adapter.is_a?(adapter_under_test_class).should == true
68 |
69 | # and should raise if already set
70 | lambda {
71 | QueueBus.adapter = :data
72 | }.should raise_error
73 | end
74 |
75 | it "should be able to be set to something else" do
76 |
77 | QueueBus.adapter = :test_one
78 | QueueBus.adapter.is_a?(QueueBus::Adapters::TestOne).should == true
79 | end
80 | end
81 |
82 |
83 | end
84 |
--------------------------------------------------------------------------------
/spec/dispatch_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Dispatch do
5 | it "should not start with any applications" do
6 | Dispatch.new("d").subscriptions.size.should == 0
7 | end
8 |
9 | it "should register code to run and execute it" do
10 | dispatch = Dispatch.new("d")
11 | dispatch.subscribe("my_event") do |attrs|
12 | Runner1.run(attrs)
13 | end
14 | sub = dispatch.subscriptions.key("my_event")
15 | sub.send(:executor).is_a?(Proc).should == true
16 |
17 | Runner.value.should == 0
18 | dispatch.execute("my_event", {"bus_event_type" => "my_event", "ok" => true})
19 | Runner1.value.should == 1
20 | Runner1.attributes.should == {"bus_event_type" => "my_event", "ok" => true}
21 |
22 | end
23 |
24 | it "should not crash if not there" do
25 | lambda {
26 | Dispatch.new("d").execute("fdkjh", "bus_event_type" => "fdkjh")
27 | }.should_not raise_error
28 | end
29 |
30 | describe "Top Level" do
31 | before(:each) do
32 | QueueBus.dispatch("testit") do
33 | subscribe "event1" do |attributes|
34 | Runner2.run(attributes)
35 | end
36 |
37 | subscribe "event2" do
38 | Runner2.run({})
39 | end
40 |
41 | high "event3" do
42 | Runner2.run({})
43 | end
44 |
45 | low /^patt.+ern/ do
46 | Runner.run({})
47 | end
48 | end
49 | end
50 |
51 | it "should register and run" do
52 | Runner2.value.should == 0
53 | QueueBus.dispatcher_execute("testit", "event2", "bus_event_type" => "event2")
54 | Runner2.value.should == 1
55 | QueueBus.dispatcher_execute("testit", "event1", "bus_event_type" => "event1")
56 | Runner2.value.should == 2
57 | QueueBus.dispatcher_execute("testit", "event1", "bus_event_type" => "event1")
58 | Runner2.value.should == 3
59 | end
60 |
61 | it "should return the subscriptions" do
62 | dispatcher = QueueBus.dispatcher_by_key("testit")
63 | subs = dispatcher.subscriptions.all
64 | tuples = subs.collect{ |sub| [sub.key, sub.queue_name]}
65 | tuples.should =~ [ ["event1", "testit_default"],
66 | ["event2", "testit_default"],
67 | ["event3", "testit_high"],
68 | [ "(?-mix:^patt.+ern)", "testit_low"]
69 | ]
70 | end
71 |
72 | end
73 | end
74 |
75 | end
76 |
77 |
--------------------------------------------------------------------------------
/spec/driver_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Driver do
5 | before(:each) do
6 | Application.new("app1").subscribe(test_list(test_sub("event1"), test_sub("event2"), test_sub("event3")))
7 | Application.new("app2").subscribe(test_list(test_sub("event2","other"), test_sub("event4", "more")))
8 | Application.new("app3").subscribe(test_list(test_sub("event[45]"), test_sub("event5"), test_sub("event6")))
9 | Timecop.freeze
10 | end
11 | after(:each) do
12 | Timecop.return
13 | end
14 |
15 | let(:bus_attrs) { {"bus_driven_at" => Time.now.to_i, "bus_rider_class_name"=>"::QueueBus::Rider", "bus_class_proxy" => "::QueueBus::Rider"} }
16 |
17 | describe ".subscription_matches" do
18 | it "return empty array when none" do
19 | Driver.subscription_matches("bus_event_type" => "else").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
20 | Driver.subscription_matches("bus_event_type" => "event").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should == []
21 | end
22 | it "should return a match" do
23 | Driver.subscription_matches("bus_event_type" => "event1").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app1", "event1", "default", "::QueueBus::Rider"]]
24 | Driver.subscription_matches("bus_event_type" => "event6").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event6", "default", "::QueueBus::Rider"]]
25 | end
26 | it "should match multiple apps" do
27 | Driver.subscription_matches("bus_event_type" => "event2").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app1", "event2", "default", "::QueueBus::Rider"], ["app2", "event2", "other", "::QueueBus::Rider"]]
28 | end
29 | it "should match multiple apps with patterns" do
30 | Driver.subscription_matches("bus_event_type" => "event4").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event[45]", "default", "::QueueBus::Rider"], ["app2", "event4", "more", "::QueueBus::Rider"]]
31 | end
32 | it "should match multiple events in same app" do
33 | Driver.subscription_matches("bus_event_type" => "event5").collect{|s| [s.app_key, s.key, s.queue_name, s.class_name]}.should =~ [["app3", "event[45]", "default", "::QueueBus::Rider"], ["app3", "event5", "default", "::QueueBus::Rider"]]
34 | end
35 | end
36 |
37 | describe ".perform" do
38 | let(:attributes) { {"x" => "y"} }
39 |
40 | before(:each) do
41 | QueueBus.redis { |redis| redis.smembers("queues") }.should == []
42 | QueueBus.redis { |redis| redis.lpop("queue:app1_default") }.should be_nil
43 | QueueBus.redis { |redis| redis.lpop("queue:app2_default") }.should be_nil
44 | QueueBus.redis { |redis| redis.lpop("queue:app3_default") }.should be_nil
45 | end
46 |
47 | it "should do nothing when empty" do
48 | Driver.perform(attributes.merge("bus_event_type" => "else"))
49 | QueueBus.redis { |redis| redis.smembers("queues") }.should == []
50 | end
51 |
52 | it "should queue up the riders in redis" do
53 | QueueBus.redis { |redis| redis.lpop("queue:app1_default") }.should be_nil
54 | Driver.perform(attributes.merge("bus_event_type" => "event1"))
55 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default"]
56 |
57 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") })
58 | hash["class"].should == "QueueBus::Worker"
59 | hash["args"].size.should == 1
60 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app1", "x" => "y", "bus_event_type" => "event1", "bus_rider_sub_key"=>"event1", "bus_rider_queue" => "default"}.merge(bus_attrs)
61 | end
62 |
63 | it "should queue up to multiple" do
64 | Driver.perform(attributes.merge("bus_event_type" => "event4"))
65 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default", "more"]
66 |
67 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:more") })
68 | hash["class"].should == "QueueBus::Worker"
69 | hash["args"].size.should == 1
70 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app2", "x" => "y", "bus_event_type" => "event4", "bus_rider_sub_key"=>"event4", "bus_rider_queue" => "more"}.merge(bus_attrs)
71 |
72 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") })
73 | hash["class"].should == "QueueBus::Worker"
74 | hash["args"].size.should == 1
75 | JSON.parse(hash["args"].first).should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event4", "bus_rider_sub_key"=>"event[45]", "bus_rider_queue" => "default"}.merge(bus_attrs)
76 | end
77 |
78 | it "should queue up to the same" do
79 | Driver.perform(attributes.merge("bus_event_type" => "event5"))
80 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["default"]
81 |
82 | QueueBus.redis { |redis| redis.llen("queue:default") }.should == 2
83 |
84 | pop1 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") })
85 | pop2 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:default") })
86 |
87 | pargs1 = JSON.parse(pop1["args"].first)
88 | pargs2 = JSON.parse(pop2["args"].first)
89 | if pargs1["bus_rider_sub_key"] == "event5"
90 | hash1 = pop1
91 | hash2 = pop2
92 | args1 = pargs1
93 | args2 = pargs2
94 | else
95 | hash1 = pop2
96 | hash2 = pop1
97 | args1 = pargs2
98 | args2 = pargs1
99 | end
100 |
101 | hash1["class"].should == "QueueBus::Worker"
102 | args1.should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event5", "bus_rider_sub_key"=>"event5", "bus_rider_queue" => "default"}.merge(bus_attrs)
103 |
104 | hash2["class"].should == "QueueBus::Worker"
105 | args2.should == {"bus_rider_app_key"=>"app3", "x" => "y", "bus_event_type" => "event5", "bus_rider_sub_key"=>"event[45]", "bus_rider_queue" => "default"}.merge(bus_attrs)
106 | end
107 | end
108 | end
109 | end
110 |
--------------------------------------------------------------------------------
/spec/heartbeat_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Heartbeat do
5 | def now_attributes
6 | {
7 | "epoch_seconds" => (Time.now.to_i / 60) * 60, # rounded
8 | "epoch_minutes" => Time.now.to_i / 60,
9 | "epoch_hours" => Time.now.to_i / (60*60),
10 | "epoch_days" => Time.now.to_i / (60*60*24),
11 | "minute" => Time.now.min,
12 | "hour" => Time.now.hour,
13 | "day" => Time.now.day,
14 | "month" => Time.now.month,
15 | "year" => Time.now.year,
16 | "yday" => Time.now.yday,
17 | "wday" => Time.now.wday
18 | }
19 | end
20 |
21 | it "should publish the current time once" do
22 | Timecop.freeze "12/12/2013 12:01:19" do
23 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes)
24 | Heartbeat.perform
25 | end
26 |
27 | Timecop.freeze "12/12/2013 12:01:40" do
28 | Heartbeat.perform
29 | end
30 | end
31 |
32 | it "should publish a minute later" do
33 | Timecop.freeze "12/12/2013 12:01:19" do
34 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes)
35 | Heartbeat.perform
36 | end
37 |
38 | Timecop.freeze "12/12/2013 12:02:01" do
39 | QueueBus.should_receive(:publish).with("heartbeat_minutes", now_attributes)
40 | Heartbeat.perform
41 | end
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/integration_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe "Integration" do
5 | it "should round trip attributes" do
6 | write1 = Subscription.new("default", "key1", "MyClass1", {"bus_event_type" => "event_one"})
7 | write2 = Subscription.new("else_ok", "key2", "MyClass2", {"bus_event_type" => /^[ab]here/}) #regex
8 |
9 | write1.matches?("bus_event_type" => "event_one").should == true
10 | write1.matches?("bus_event_type" => "event_one1").should == false
11 | write1.matches?("bus_event_type" => "aevent_one").should == false
12 |
13 | write2.matches?("bus_event_type" => "ahere").should == true
14 | write2.matches?("bus_event_type" => "bhere").should == true
15 | write2.matches?("bus_event_type" => "qhere").should == false
16 | write2.matches?("bus_event_type" => "abhere").should == false
17 | write2.matches?("bus_event_type" => "[ab]here").should == false
18 |
19 | write = SubscriptionList.new
20 | write.add(write1)
21 | write.add(write2)
22 |
23 | app = Application.new("test")
24 | app.subscribe(write)
25 |
26 | reset_test_adapter # reset to make sure we read from redis
27 | app = Application.new("test")
28 | read = app.send(:subscriptions)
29 |
30 | read.size.should == 2
31 | read1 = read.key("key1")
32 | read2 = read.key("key2")
33 | read1.should_not be_nil
34 | read2.should_not be_nil
35 |
36 | read1.queue_name.should == "default"
37 | read1.class_name.should == "MyClass1"
38 | read2.queue_name.should == "else_ok"
39 | read2.class_name.should == "MyClass2"
40 |
41 | read1.matches?("bus_event_type" => "event_one").should == true
42 | read1.matches?("bus_event_type" => "event_one1").should == false
43 | read1.matches?("bus_event_type" => "aevent_one").should == false
44 |
45 | read2.matches?("bus_event_type" => "ahere").should == true
46 | read2.matches?("bus_event_type" => "bhere").should == true
47 | read2.matches?("bus_event_type" => "qhere").should == false
48 | read2.matches?("bus_event_type" => "abhere").should == false
49 | read2.matches?("bus_event_type" => "[ab]here").should == false
50 |
51 | end
52 | end
53 | end
--------------------------------------------------------------------------------
/spec/matcher_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Matcher do
5 | it "should already return false on empty filters" do
6 | matcher = Matcher.new({})
7 | matcher.matches?({}).should == false
8 | matcher.matches?(nil).should == false
9 | matcher.matches?("name" => "val").should == false
10 | end
11 |
12 | it "should not crash if nil inputs" do
13 | matcher = Matcher.new("name" => "val")
14 | matcher.matches?(nil).should == false
15 | end
16 |
17 | it "string filter to/from redis" do
18 | matcher = Matcher.new("name" => "val")
19 | matcher.matches?("name" => "val").should == true
20 | matcher.matches?("name" => " val").should == false
21 | matcher.matches?("name" => "zval").should == false
22 | end
23 |
24 | it "regex filter" do
25 | matcher = Matcher.new("name" => /^[cb]a+t/)
26 | matcher.matches?("name" => "cat").should == true
27 | matcher.matches?("name" => "bat").should == true
28 | matcher.matches?("name" => "caaaaat").should == true
29 | matcher.matches?("name" => "ct").should == false
30 | matcher.matches?("name" => "bcat").should == false
31 | end
32 |
33 | it "present filter" do
34 | matcher = Matcher.new("name" => :present)
35 | matcher.matches?("name" => "").should == false
36 | matcher.matches?("name" => "cat").should == true
37 | matcher.matches?("name" => "bear").should == true
38 | matcher.matches?("other" => "bear").should == false
39 | end
40 |
41 | it "blank filter" do
42 | matcher = Matcher.new("name" => :blank)
43 | matcher.matches?("name" => nil).should == true
44 | matcher.matches?("other" => "bear").should == true
45 | matcher.matches?("name" => "").should == true
46 | matcher.matches?("name" => " ").should == true
47 | matcher.matches?("name" => "bear").should == false
48 | matcher.matches?("name" => " s ").should == false
49 | end
50 |
51 | it "nil filter" do
52 | matcher = Matcher.new("name" => :nil)
53 | matcher.matches?("name" => nil).should == true
54 | matcher.matches?("other" => "bear").should == true
55 | matcher.matches?("name" => "").should == false
56 | matcher.matches?("name" => " ").should == false
57 | matcher.matches?("name" => "bear").should == false
58 | end
59 |
60 | it "key filter" do
61 | matcher = Matcher.new("name" => :key)
62 | matcher.matches?("name" => nil).should == true
63 | matcher.matches?("other" => "bear").should == false
64 | matcher.matches?("name" => "").should == true
65 | matcher.matches?("name" => " ").should == true
66 | matcher.matches?("name" => "bear").should == true
67 | end
68 |
69 | it "empty filter" do
70 | matcher = Matcher.new("name" => :empty)
71 | matcher.matches?("name" => nil).should == false
72 | matcher.matches?("other" => "bear").should == false
73 | matcher.matches?("name" => "").should == true
74 | matcher.matches?("name" => " ").should == false
75 | matcher.matches?("name" => "bear").should == false
76 | matcher.matches?("name" => " s ").should == false
77 | end
78 |
79 | it "value filter" do
80 | matcher = Matcher.new("name" => :value)
81 | matcher.matches?("name" => nil).should == false
82 | matcher.matches?("other" => "bear").should == false
83 | matcher.matches?("name" => "").should == true
84 | matcher.matches?("name" => " ").should == true
85 | matcher.matches?("name" => "bear").should == true
86 | matcher.matches?("name" => " s ").should == true
87 | end
88 |
89 | it "multiple filters" do
90 | matcher = Matcher.new("name" => /^[cb]a+t/, "state" => "sleeping")
91 | matcher.matches?("state" => "sleeping", "name" => "cat").should == true
92 | matcher.matches?("state" => "awake", "name" => "cat").should == false
93 | matcher.matches?("state" => "sleeping", "name" => "bat").should == true
94 | matcher.matches?("state" => "sleeping", "name" => "bear").should == false
95 | matcher.matches?("state" => "awake", "name" => "bear").should == false
96 | end
97 |
98 | it "regex should go back and forth into redis" do
99 | matcher = Matcher.new("name" => /^[cb]a+t/)
100 | matcher.matches?("name" => "cat").should == true
101 | matcher.matches?("name" => "bat").should == true
102 | matcher.matches?("name" => "caaaaat").should == true
103 | matcher.matches?("name" => "ct").should == false
104 | matcher.matches?("name" => "bcat").should == false
105 |
106 | QueueBus.redis { |redis| redis.set("temp1", QueueBus::Util.encode(matcher.to_redis) ) }
107 | redis = QueueBus.redis { |redis| redis.get("temp1") }
108 | matcher = Matcher.new(QueueBus::Util.decode(redis))
109 | matcher.matches?("name" => "cat").should == true
110 | matcher.matches?("name" => "bat").should == true
111 | matcher.matches?("name" => "caaaaat").should == true
112 | matcher.matches?("name" => "ct").should == false
113 | matcher.matches?("name" => "bcat").should == false
114 |
115 | QueueBus.redis { |redis| redis.set("temp2", QueueBus::Util.encode(matcher.to_redis) ) }
116 | redis = QueueBus.redis { |redis| redis.get("temp2") }
117 | matcher = Matcher.new(QueueBus::Util.decode(redis))
118 | matcher.matches?("name" => "cat").should == true
119 | matcher.matches?("name" => "bat").should == true
120 | matcher.matches?("name" => "caaaaat").should == true
121 | matcher.matches?("name" => "ct").should == false
122 | matcher.matches?("name" => "bcat").should == false
123 | end
124 |
125 | it "special value should go back and forth into redis" do
126 | matcher = Matcher.new("name" => :blank)
127 | matcher.matches?("name" => "cat").should == false
128 | matcher.matches?("name" => "").should == true
129 |
130 | QueueBus.redis { |redis| redis.set("temp1", QueueBus::Util.encode(matcher.to_redis) ) }
131 | redis= QueueBus.redis { |redis| redis.get("temp1") }
132 | matcher = Matcher.new(QueueBus::Util.decode(redis))
133 | matcher.matches?("name" => "cat").should == false
134 | matcher.matches?("name" => "").should == true
135 |
136 | QueueBus.redis { |redis| redis.set("temp2", QueueBus::Util.encode(matcher.to_redis) ) }
137 | redis= QueueBus.redis { |redis| redis.get("temp2") }
138 | matcher = Matcher.new(QueueBus::Util.decode(redis))
139 | matcher.matches?("name" => "cat").should == false
140 | matcher.matches?("name" => "").should == true
141 | end
142 | end
143 | end
144 |
--------------------------------------------------------------------------------
/spec/publish_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Publishing an event" do
4 |
5 | before(:each) do
6 | Timecop.freeze
7 | QueueBus.stub(:generate_uuid).and_return("idfhlkj")
8 | end
9 | after(:each) do
10 | Timecop.return
11 | end
12 | let(:bus_attrs) { {"bus_class_proxy"=>"QueueBus::Driver",
13 | "bus_published_at" => Time.now.to_i,
14 | "bus_id"=>"#{Time.now.to_i}-idfhlkj",
15 | "bus_app_hostname" => `hostname 2>&1`.strip.sub(/.local/,'')} }
16 |
17 | it "should add it to Redis" do
18 | hash = {:one => 1, "two" => "here", "id" => 12 }
19 | event_name = "event_name"
20 |
21 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
22 | val.should == nil
23 |
24 | QueueBus.publish(event_name, hash)
25 |
26 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
27 | hash = JSON.parse(val)
28 | hash["class"].should == "QueueBus::Worker"
29 | hash["args"].size.should == 1
30 | JSON.parse(hash["args"].first).should == {"bus_event_type" => event_name, "two"=>"here", "one"=>1, "id" => 12}.merge(bus_attrs)
31 |
32 | end
33 |
34 | it "should use the id if given" do
35 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" }
36 | event_name = "event_name"
37 |
38 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
39 | val.should == nil
40 |
41 | QueueBus.publish(event_name, hash)
42 |
43 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
44 | hash = JSON.parse(val)
45 | hash["class"].should == "QueueBus::Worker"
46 | hash["args"].size.should == 1
47 | JSON.parse(hash["args"].first).should == {"bus_event_type" => event_name, "two"=>"here", "one"=>1}.merge(bus_attrs).merge("bus_id" => 'app-given')
48 | end
49 |
50 | it "should add metadata via callback" do
51 | myval = 0
52 | QueueBus.before_publish = lambda { |att|
53 | att["mine"] = 4
54 | myval += 1
55 | }
56 |
57 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" }
58 | event_name = "event_name"
59 |
60 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
61 | val.should == nil
62 |
63 | QueueBus.publish(event_name, hash)
64 |
65 |
66 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
67 | hash = JSON.parse(val)
68 | att = JSON.parse(hash["args"].first)
69 | att["mine"].should == 4
70 | myval.should == 1
71 | end
72 |
73 | it "should set the timezone and locale if available" do
74 | defined?(I18n).should be_nil
75 | Time.respond_to?(:zone).should eq(false)
76 |
77 | stub_const("I18n", Class.new)
78 | I18n.stub(:locale).and_return("jp")
79 |
80 | Time.stub(:zone).and_return(double('zone', :name => "EST"))
81 |
82 | hash = {:one => 1, "two" => "here", "bus_id" => "app-given" }
83 | event_name = "event_name"
84 |
85 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
86 | val.should == nil
87 |
88 | QueueBus.publish(event_name, hash)
89 |
90 | val = QueueBus.redis { |redis| redis.lpop("queue:bus_incoming") }
91 | hash = JSON.parse(val)
92 | hash["class"].should == "QueueBus::Worker"
93 | att = JSON.parse(hash["args"].first)
94 | att["bus_locale"].should == "jp"
95 | att["bus_timezone"].should == "EST"
96 | end
97 |
98 | end
99 |
--------------------------------------------------------------------------------
/spec/publisher_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Publisher do
5 | it "should call publish as expected"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/rider_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Rider do
5 | it "should call execute" do
6 | QueueBus.should_receive(:dispatcher_execute)
7 | Rider.perform("bus_rider_app_key" => "app", "bus_rider_sub_key" => "sub", "ok" => true, "bus_event_type" => "event_name")
8 | end
9 |
10 | it "should change the value" do
11 | QueueBus.dispatch("r1") do
12 | subscribe "event_name" do |attributes|
13 | Runner1.run(attributes)
14 | end
15 | end
16 | Runner1.value.should == 0
17 | Rider.perform("bus_locale" => "en", "bus_timezone" => "PST", "bus_rider_app_key" => "r1", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name")
18 | Rider.perform("bus_rider_app_key" => "other", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name")
19 | Runner1.value.should == 1
20 | end
21 |
22 | it "should set the timezone and locale if present" do
23 | QueueBus.dispatch("r1") do
24 | subscribe "event_name" do |attributes|
25 | Runner1.run(attributes)
26 | end
27 | end
28 |
29 | defined?(I18n).should be_nil
30 | Time.respond_to?(:zone).should eq(false)
31 |
32 | stub_const("I18n", Class.new)
33 | I18n.should_receive(:locale=).with("en")
34 | Time.should_receive(:zone=).with("PST")
35 |
36 | Rider.perform("bus_locale" => "en", "bus_timezone" => "PST", "bus_rider_app_key" => "r1", "bus_rider_sub_key" => "event_name", "ok" => true, "bus_event_type" => "event_name")
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'timecop'
2 | require 'queue-bus'
3 | require 'adapter/support'
4 |
5 | reset_test_adapter
6 |
7 | module QueueBus
8 | class Runner
9 | def self.value
10 | @value ||= 0
11 | end
12 |
13 | def self.attributes
14 | @attributes
15 | end
16 |
17 | def self.run(attrs)
18 | @value ||= 0
19 | @value += 1
20 | @attributes = attrs
21 | end
22 |
23 | def self.reset
24 | @value = nil
25 | @attributes = nil
26 | end
27 | end
28 |
29 | class Runner1 < Runner
30 | end
31 |
32 | class Runner2 < Runner
33 | end
34 | end
35 |
36 | def test_sub(event_name, queue="default")
37 | matcher = {"bus_event_type" => event_name}
38 | QueueBus::Subscription.new(queue, event_name, "::QueueBus::Rider", matcher, nil)
39 | end
40 |
41 | def test_list(*args)
42 | out = QueueBus::SubscriptionList.new
43 | args.each do |sub|
44 | out.add(sub)
45 | end
46 | out
47 | end
48 |
49 | RSpec.configure do |config|
50 | config.mock_with :rspec do |c|
51 | c.syntax = :should
52 | end
53 | config.expect_with :rspec do |c|
54 | c.syntax = :should
55 | end
56 |
57 | config.before(:each) do
58 | reset_test_adapter
59 | ResqueBus.show_deprecations = true
60 | end
61 | config.after(:each) do
62 | begin
63 | QueueBus.redis { |redis| redis.flushall }
64 | rescue
65 | end
66 | QueueBus.send(:reset)
67 | QueueBus::Runner1.reset
68 | QueueBus::Runner2.reset
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/subscriber_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe QueueBus::Subscriber do
4 | let(:attributes) { {"x" => "y"} }
5 | let(:bus_attrs) { {"bus_driven_at" => Time.now.to_i} }
6 |
7 | before(:each) do
8 | class SubscriberTest1
9 | include QueueBus::Subscriber
10 | @queue = "myqueue"
11 |
12 | application :my_thing
13 | subscribe :thing_filter, :x => "y"
14 | subscribe :event_sub
15 |
16 | def event_sub(attributes)
17 | QueueBus::Runner1.run(attributes)
18 | end
19 |
20 | def thing_filter(attributes)
21 | QueueBus::Runner2.run(attributes)
22 | end
23 | end
24 |
25 | class SubscriberTest2
26 | include QueueBus::Subscriber
27 | application :test2
28 | subscribe :test2, "value" => :present
29 | transform :make_an_int
30 |
31 | def self.make_an_int(attributes)
32 | attributes["value"].to_s.length
33 | end
34 |
35 | def test2(int)
36 | QueueBus::Runner1.run("transformed"=>int)
37 | end
38 | end
39 |
40 | module SubModule
41 | class SubscriberTest3
42 | include QueueBus::Subscriber
43 |
44 | subscribe_queue :sub_queue1, :test3, :bus_event_type => "the_event"
45 | subscribe_queue :sub_queue2, :the_event
46 | subscribe :other, :bus_event_type => "other_event"
47 |
48 | def test3(attributes)
49 | QueueBus::Runner1.run(attributes)
50 | end
51 |
52 | def the_event(attributes)
53 | QueueBus::Runner2.run(attributes)
54 | end
55 | end
56 |
57 | class SubscriberTest4
58 | include QueueBus::Subscriber
59 |
60 | subscribe_queue :sub_queue1, :test4
61 | end
62 | end
63 |
64 | Timecop.freeze
65 | QueueBus::TaskManager.new(false).subscribe!
66 | end
67 |
68 | after(:each) do
69 | Timecop.return
70 | end
71 |
72 | it "should have the application" do
73 | SubscriberTest1.app_key.should == "my_thing"
74 | SubModule::SubscriberTest3.app_key.should == "sub_module"
75 | SubModule::SubscriberTest4.app_key.should == "sub_module"
76 | end
77 |
78 | it "should be able to transform the attributes" do
79 | dispatcher = QueueBus.dispatcher_by_key("test2")
80 | all = dispatcher.subscriptions.all
81 | all.size.should == 1
82 |
83 | sub = all.first
84 | sub.queue_name.should == "test2_default"
85 | sub.class_name.should == "SubscriberTest2"
86 | sub.key.should == "SubscriberTest2.test2"
87 | sub.matcher.filters.should == {"value"=>"bus_special_value_present"}
88 |
89 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "something2", "value"=>"nice"))
90 |
91 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:test2_default") })
92 | hash["class"].should == "QueueBus::Worker"
93 | hash["args"].size.should == 1
94 | JSON.parse(hash["args"].first).should eq({"bus_class_proxy" => "SubscriberTest2", "bus_rider_app_key"=>"test2", "bus_rider_sub_key"=>"SubscriberTest2.test2", "bus_rider_queue" => "test2_default", "bus_rider_class_name"=>"SubscriberTest2",
95 | "bus_event_type" => "something2", "value"=>"nice", "x"=>"y"}.merge(bus_attrs))
96 |
97 | QueueBus::Runner1.value.should == 0
98 | QueueBus::Runner2.value.should == 0
99 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"])
100 | QueueBus::Runner1.value.should == 1
101 | QueueBus::Runner2.value.should == 0
102 |
103 | QueueBus::Runner1.attributes.should == {"transformed" => 4}
104 |
105 |
106 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "something2", "value"=>"12"))
107 |
108 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:test2_default") })
109 | hash["class"].should == "QueueBus::Worker"
110 | hash["args"].size.should == 1
111 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubscriberTest2", "bus_rider_app_key"=>"test2", "bus_rider_sub_key"=>"SubscriberTest2.test2", "bus_rider_queue" => "test2_default", "bus_rider_class_name"=>"SubscriberTest2",
112 | "bus_event_type" => "something2", "value"=>"12", "x"=>"y"}.merge(bus_attrs)
113 |
114 | QueueBus::Runner1.value.should == 1
115 | QueueBus::Runner2.value.should == 0
116 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"])
117 | QueueBus::Runner1.value.should == 2
118 | QueueBus::Runner2.value.should == 0
119 |
120 | QueueBus::Runner1.attributes.should == {"transformed" => 2}
121 | end
122 |
123 |
124 | it "should put in a different queue" do
125 | dispatcher = QueueBus.dispatcher_by_key("sub_module")
126 | all = dispatcher.subscriptions.all
127 | all.size.should == 4
128 |
129 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.test3"}.first
130 | sub.queue_name.should == "sub_queue1"
131 | sub.class_name.should == "SubModule::SubscriberTest3"
132 | sub.key.should == "SubModule::SubscriberTest3.test3"
133 | sub.matcher.filters.should == {"bus_event_type"=>"the_event"}
134 |
135 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.the_event"}.first
136 | sub.queue_name.should == "sub_queue2"
137 | sub.class_name.should == "SubModule::SubscriberTest3"
138 | sub.key.should == "SubModule::SubscriberTest3.the_event"
139 | sub.matcher.filters.should == {"bus_event_type"=>"the_event"}
140 |
141 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest3.other"}.first
142 | sub.queue_name.should == "sub_module_default"
143 | sub.class_name.should == "SubModule::SubscriberTest3"
144 | sub.key.should == "SubModule::SubscriberTest3.other"
145 | sub.matcher.filters.should == {"bus_event_type"=>"other_event"}
146 |
147 | sub = all.select{ |s| s.key == "SubModule::SubscriberTest4.test4"}.first
148 | sub.queue_name.should == "sub_queue1"
149 | sub.class_name.should == "SubModule::SubscriberTest4"
150 | sub.key.should == "SubModule::SubscriberTest4.test4"
151 | sub.matcher.filters.should == {"bus_event_type"=>"test4"}
152 |
153 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "the_event"))
154 |
155 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:sub_queue1") })
156 | hash["class"].should == "QueueBus::Worker"
157 | hash["args"].size.should == 1
158 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubModule::SubscriberTest3", "bus_rider_app_key"=>"sub_module", "bus_rider_sub_key"=>"SubModule::SubscriberTest3.test3", "bus_rider_queue" => "sub_queue1", "bus_rider_class_name"=>"SubModule::SubscriberTest3",
159 | "bus_event_type" => "the_event", "x" => "y"}.merge(bus_attrs)
160 |
161 | QueueBus::Runner1.value.should == 0
162 | QueueBus::Runner2.value.should == 0
163 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"])
164 | QueueBus::Runner1.value.should == 1
165 | QueueBus::Runner2.value.should == 0
166 |
167 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:sub_queue2") })
168 | hash["class"].should == "QueueBus::Worker"
169 | hash["args"].size.should == 1
170 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubModule::SubscriberTest3", "bus_rider_app_key"=>"sub_module", "bus_rider_sub_key"=>"SubModule::SubscriberTest3.the_event", "bus_rider_queue" => "sub_queue2", "bus_rider_class_name"=>"SubModule::SubscriberTest3",
171 | "bus_event_type" => "the_event", "x" => "y"}.merge(bus_attrs)
172 |
173 | QueueBus::Runner1.value.should == 1
174 | QueueBus::Runner2.value.should == 0
175 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"])
176 | QueueBus::Runner1.value.should == 1
177 | QueueBus::Runner2.value.should == 1
178 | end
179 |
180 | it "should subscribe to default and attributes" do
181 | dispatcher = QueueBus.dispatcher_by_key("my_thing")
182 | all = dispatcher.subscriptions.all
183 |
184 | sub = all.select{ |s| s.key == "SubscriberTest1.event_sub"}.first
185 | sub.queue_name.should == "myqueue"
186 | sub.class_name.should == "SubscriberTest1"
187 | sub.key.should == "SubscriberTest1.event_sub"
188 | sub.matcher.filters.should == {"bus_event_type"=>"event_sub"}
189 |
190 | sub = all.select{ |s| s.key == "SubscriberTest1.thing_filter"}.first
191 | sub.queue_name.should == "myqueue"
192 | sub.class_name.should == "SubscriberTest1"
193 | sub.key.should == "SubscriberTest1.thing_filter"
194 | sub.matcher.filters.should == {"x"=>"y"}
195 |
196 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "event_sub"))
197 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"]
198 |
199 | pop1 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") })
200 | pop2 = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") })
201 |
202 | if JSON.parse(pop1["args"].first)["bus_rider_sub_key"] == "SubscriberTest1.thing_filter"
203 | hash1 = pop1
204 | hash2 = pop2
205 | else
206 | hash1 = pop2
207 | hash2 = pop1
208 | end
209 |
210 | hash1["class"].should == "QueueBus::Worker"
211 | JSON.parse(hash1["args"].first).should eq({"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.thing_filter", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1",
212 | "bus_event_type" => "event_sub", "x" => "y"}.merge(bus_attrs))
213 |
214 | QueueBus::Runner1.value.should == 0
215 | QueueBus::Runner2.value.should == 0
216 | QueueBus::Util.constantize(hash1["class"]).perform(*hash1["args"])
217 | QueueBus::Runner1.value.should == 0
218 | QueueBus::Runner2.value.should == 1
219 |
220 | hash2["class"].should == "QueueBus::Worker"
221 | hash2["args"].size.should == 1
222 | JSON.parse(hash2["args"].first).should == {"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.event_sub", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1",
223 | "bus_event_type" => "event_sub", "x" => "y"}.merge(bus_attrs)
224 |
225 | QueueBus::Runner1.value.should == 0
226 | QueueBus::Runner2.value.should == 1
227 | QueueBus::Util.constantize(hash2["class"]).perform(*hash2["args"])
228 | QueueBus::Runner1.value.should == 1
229 | QueueBus::Runner2.value.should == 1
230 |
231 | QueueBus::Driver.perform(attributes.merge("bus_event_type" => "event_sub_other"))
232 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"]
233 |
234 | hash = JSON.parse(QueueBus.redis { |redis| redis.lpop("queue:myqueue") })
235 | hash["class"].should == "QueueBus::Worker"
236 | hash["args"].size.should == 1
237 | JSON.parse(hash["args"].first).should == {"bus_class_proxy" => "SubscriberTest1", "bus_rider_app_key"=>"my_thing", "bus_rider_sub_key"=>"SubscriberTest1.thing_filter", "bus_rider_queue" => "myqueue", "bus_rider_class_name"=>"SubscriberTest1",
238 | "bus_event_type" => "event_sub_other", "x" => "y"}.merge(bus_attrs)
239 |
240 | QueueBus::Runner1.value.should == 1
241 | QueueBus::Runner2.value.should == 1
242 | QueueBus::Util.constantize(hash["class"]).perform(*hash["args"])
243 | QueueBus::Runner1.value.should == 1
244 | QueueBus::Runner2.value.should == 2
245 |
246 | QueueBus::Driver.perform({"x"=>"z"}.merge("bus_event_type" => "event_sub_other"))
247 | QueueBus.redis { |redis| redis.smembers("queues") }.should =~ ["myqueue"]
248 |
249 | QueueBus.redis { |redis| redis.lpop("queue:myqueue") }.should be_nil
250 | end
251 |
252 | describe ".perform" do
253 | let(:attributes) { {"bus_rider_sub_key"=>"SubscriberTest1.event_sub", "bus_locale" => "en", "bus_timezone" => "PST"} }
254 | it "should call the method based on key" do
255 | SubscriberTest1.any_instance.should_receive(:event_sub)
256 | SubscriberTest1.perform(attributes)
257 | end
258 | it "should set the timezone and locale if present" do
259 | defined?(I18n).should be_nil
260 | Time.respond_to?(:zone).should eq(false)
261 |
262 | stub_const("I18n", Class.new)
263 | I18n.should_receive(:locale=).with("en")
264 | Time.should_receive(:zone=).with("PST")
265 |
266 | SubscriberTest1.any_instance.should_receive(:event_sub)
267 | SubscriberTest1.perform(attributes)
268 | end
269 | end
270 | end
271 |
--------------------------------------------------------------------------------
/spec/subscription_list_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe SubscriptionList do
5 | describe ".from_redis" do
6 | it "should return from attributes" do
7 | mult = {"event_one" => {"class" => "MyClass", "queue_name" => "default", "key" => "event_one", "matcher" => {"bus_event_type" => "event_one"}},
8 | "event_two" => {"class" => "MyClass", "queue_name" => "else", "key" => "event_two", "matcher" => {"bus_event_type" => "event_two"}}}
9 |
10 | list = SubscriptionList.from_redis(mult)
11 | list.size.should == 2
12 | one = list.key("event_one")
13 | two = list.key("event_two")
14 |
15 | one.key.should == "event_one"
16 | one.key.should == "event_one"
17 | one.queue_name.should == "default"
18 | one.class_name.should == "MyClass"
19 | one.matcher.filters.should == {"bus_event_type" => "event_one"}
20 |
21 | two.key.should == "event_two"
22 | two.key.should == "event_two"
23 | two.queue_name.should == "else"
24 | two.class_name.should == "MyClass"
25 | two.matcher.filters.should == {"bus_event_type" => "event_two"}
26 | end
27 | end
28 |
29 | describe "#to_redis" do
30 | it "should generate what to store" do
31 | list = SubscriptionList.new
32 | list.add(Subscription.new("default", "key1", "MyClass", {"bus_event_type" => "event_one"}))
33 | list.add(Subscription.new("else_ok", "key2", "MyClass", {"bus_event_type" => "event_two"}))
34 |
35 | hash = list.to_redis
36 | hash.should == { "key1" => {"queue_name" => "default", "key" => "key1", "class" => "MyClass", "matcher" => {"bus_event_type" => "event_one"}},
37 | "key2" => {"queue_name" => "else_ok", "key" => "key2", "class" => "MyClass", "matcher" => {"bus_event_type" => "event_two"}}
38 | }
39 |
40 | end
41 | end
42 | end
43 | end
--------------------------------------------------------------------------------
/spec/subscription_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Subscription do
5 | it "should normalize the queue name" do
6 | Subscription.new("test", "my_event", "MyClass", {}, nil).queue_name.should == "test"
7 | Subscription.new("tes t", "my_event", "MyClass", {}, nil).queue_name.should == "tes_t"
8 | Subscription.new("t%s", "my_event", "MyClass", {}, nil).queue_name.should == "t_s"
9 | end
10 |
11 | describe ".register" do
12 | it "should take in args from dispatcher" do
13 | executor = Proc.new { |attributes| }
14 | sub = Subscription.register("queue_name", "mykey", "MyClass", {"bus_event_type" => "my_event"}, executor)
15 | sub.send(:executor).should == executor
16 | sub.matcher.filters.should == {"bus_event_type" => "my_event"}
17 | sub.queue_name.should == "queue_name"
18 | sub.key.should == "mykey"
19 | sub.class_name.should == "MyClass"
20 | end
21 | end
22 |
23 | describe "#execute!" do
24 | it "should call the executor with the attributes" do
25 | exec = Object.new
26 | exec.should_receive(:call)
27 |
28 | sub = Subscription.new("x", "y", "ClassName", {}, exec)
29 | sub.execute!({"ok" => true})
30 | end
31 | end
32 |
33 | describe "#to_redis" do
34 | it "should return what to store for this subscription" do
35 | sub = Subscription.new("queue_one", "xyz", "ClassName", {"bus_event_type" => "my_event"}, nil)
36 | sub.to_redis.should == {"queue_name" => "queue_one", "key" => "xyz", "class" => "ClassName", "matcher" => {"bus_event_type" => "my_event"}}
37 | end
38 | end
39 |
40 | describe "#matches?" do
41 | it "should do pattern stuff" do
42 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one"}).matches?("bus_event_type" => "one").should == true
43 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one"}).matches?("bus_event_type" => "onex").should == false
44 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "^one.*$"}).matches?("bus_event_type" => "onex").should == true
45 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.*"}).matches?("bus_event_type" => "onex").should == true
46 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.?"}).matches?("bus_event_type" => "onex").should == true
47 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "one.?"}).matches?("bus_event_type" => "one").should == true
48 | Subscription.new("x", "id", "ClassName", {"bus_event_type" => "\\"}).matches?("bus_event_type" => "one").should == false
49 | end
50 | end
51 |
52 | end
53 | end
--------------------------------------------------------------------------------
/spec/worker_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module QueueBus
4 | describe Worker do
5 | it "should proxy to given class" do
6 | hash = {"bus_class_proxy" => "QueueBus::Driver", "ok" => true}
7 | QueueBus::Driver.should_receive(:perform).with(hash)
8 | QueueBus::Worker.perform(JSON.generate(hash))
9 | end
10 |
11 | it "should use instance" do
12 | hash = {"bus_class_proxy" => "QueueBus::Rider", "ok" => true}
13 | QueueBus::Rider.should_receive(:perform).with(hash)
14 | QueueBus::Worker.new.perform(JSON.generate(hash))
15 | end
16 |
17 | it "should not freak out if class not there anymore" do
18 | hash = {"bus_class_proxy" => "QueueBus::BadClass", "ok" => true}
19 | lambda {
20 | QueueBus::Worker.perform(JSON.generate(hash))
21 | }.should_not raise_error
22 | end
23 |
24 | it "should raise error if proxy raises error" do
25 | hash = {"bus_class_proxy" => "QueueBus::Rider", "ok" => true}
26 | QueueBus::Rider.should_receive(:perform).with(hash).and_raise("rider crash")
27 | lambda {
28 | QueueBus::Worker.perform(JSON.generate(hash))
29 | }.should raise_error
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------