zulu 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/zulu/challenge.rb +2 -2
- data/lib/zulu/http.rb +21 -0
- data/lib/zulu/keeper.rb +24 -0
- data/lib/zulu/subscription.rb +11 -0
- data/lib/zulu/subscription_request.rb +1 -3
- data/lib/zulu/topic.rb +74 -0
- data/lib/zulu/topic_distribution.rb +54 -0
- data/lib/zulu/topic_distribution_processor.rb +33 -0
- data/lib/zulu/version.rb +1 -1
- data/lib/zulu.rb +26 -4
- data/test/minitest_helper.rb +0 -1
- data/test/test_challenge.rb +1 -1
- data/test/test_http.rb +20 -0
- data/test/test_keeper.rb +32 -0
- data/test/test_subscription.rb +39 -0
- data/test/test_subscription_request.rb +20 -16
- data/test/test_subscription_request_processor.rb +0 -1
- data/test/test_topic.rb +143 -0
- data/test/test_topic_distribution.rb +81 -0
- data/test/test_topic_distribution_processor.rb +24 -0
- data/test/test_zulu.rb +9 -0
- data/zulu.gemspec +1 -1
- metadata +31 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ec4fc2ba4cf0c2764d457ed2063945de083073c
|
4
|
+
data.tar.gz: 911a784bda238a5d20280795b9002c8980969107
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e082fe6f5c99731a436c02dddf7cd7794438cadcb154917fa91038d5acb0523dd0a82f9e1293391a6576d45108bb8600b9e95f1f7fdb98a2ad2202af3efb8b8
|
7
|
+
data.tar.gz: 0a07fa35295615de3ab65be016ef3b08937213e1fc04e0cbbd4e0426b1d1c1cd543b7a5a02065185e7da889b02d251f61ae4452ebc698c1eb6a1f63e4cf43706
|
data/lib/zulu/challenge.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Zulu
|
2
2
|
class Challenge < String
|
3
3
|
|
4
|
-
CHARS = [(0..9),('a'..'z'),('A'..'Z')].inject([]) {|a,r| a +=r.to_a; a}
|
4
|
+
CHARS = [('0'..'9'),('a'..'z'),('A'..'Z')].inject([]) {|a,r| a +=r.to_a; a}
|
5
5
|
|
6
6
|
def initialize(len=24)
|
7
|
-
self << CHARS
|
7
|
+
self << CHARS.sample until length == len
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/zulu/http.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "celluloid/io"
|
2
|
+
require "net/http"
|
3
|
+
require "addressable/uri"
|
4
|
+
|
5
|
+
module Zulu
|
6
|
+
module Http
|
7
|
+
|
8
|
+
def self.post(url, options={})
|
9
|
+
uri = Addressable::URI.parse(url)
|
10
|
+
params = options.delete(:params) || {}
|
11
|
+
Net::HTTP.post_form(uri, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.get(url, options={})
|
15
|
+
uri = Addressable::URI.parse(url)
|
16
|
+
params = options.delete(:params)
|
17
|
+
uri.query = URI.encode_www_form(params) if params
|
18
|
+
Net::HTTP.get_response(uri)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/zulu/keeper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Zulu
|
2
|
+
class Keeper
|
3
|
+
include Celluloid::IO
|
4
|
+
include Celluloid::Logger
|
5
|
+
|
6
|
+
def tick(now=Time.now)
|
7
|
+
debug "Looking for topics at: #{now} (#{now.to_i})"
|
8
|
+
count = 0
|
9
|
+
Topic.happening(now).each do |topic_id|
|
10
|
+
topic = Topic.new(id: topic_id)
|
11
|
+
debug "Found topic: #{topic_id}"
|
12
|
+
distribution = TopicDistribution.new(id: topic_id, now: now)
|
13
|
+
distribution.save
|
14
|
+
topic.reset_next(now)
|
15
|
+
count += 1
|
16
|
+
end
|
17
|
+
debug "Finished tick: (#{Time.now - now} seconds for #{count} topics)"
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
every(Zulu.options[:interval]) { tick }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/zulu/subscription.rb
CHANGED
@@ -11,6 +11,10 @@ module Zulu
|
|
11
11
|
@callback = options[:callback]
|
12
12
|
end
|
13
13
|
|
14
|
+
def ==(other)
|
15
|
+
id == other.id
|
16
|
+
end
|
17
|
+
|
14
18
|
def id
|
15
19
|
@id ||= Digest::MD5.hexdigest [@topic, @callback].join(':')
|
16
20
|
end
|
@@ -19,12 +23,19 @@ module Zulu
|
|
19
23
|
@callback ||= Zulu.redis.get "#{KEY_PREFIX}:#{id}:callback"
|
20
24
|
end
|
21
25
|
|
26
|
+
def topic
|
27
|
+
@topic_object ||= Topic.new(topic: @topic)
|
28
|
+
end
|
29
|
+
|
22
30
|
def save
|
23
31
|
Zulu.redis.set "#{KEY_PREFIX}:#{id}:callback", callback
|
32
|
+
topic.save
|
33
|
+
topic.subscribe(id)
|
24
34
|
end
|
25
35
|
|
26
36
|
def destroy
|
27
37
|
Zulu.redis.del "#{KEY_PREFIX}:#{id}:callback"
|
38
|
+
topic.unsubscribe(id)
|
28
39
|
end
|
29
40
|
|
30
41
|
end
|
@@ -62,9 +62,7 @@ module Zulu
|
|
62
62
|
challenge = Challenge.new
|
63
63
|
params = to_hash
|
64
64
|
params['hub.challenge'] = challenge
|
65
|
-
|
66
|
-
uri.query = URI.encode_www_form(params)
|
67
|
-
response = Net::HTTP.get_response(uri)
|
65
|
+
response = Http.get(@callback, params: params)
|
68
66
|
response.code == "200" && response.body == challenge
|
69
67
|
end
|
70
68
|
|
data/lib/zulu/topic.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "rufus/sc/cronline"
|
3
|
+
|
4
|
+
module Zulu
|
5
|
+
class Topic
|
6
|
+
|
7
|
+
KEY_PREFIX = "topic".freeze
|
8
|
+
UPCOMING_KEY = "#{KEY_PREFIX}:upcoming".freeze
|
9
|
+
|
10
|
+
def self.happening(now=Time.now)
|
11
|
+
Zulu.redis.zrangebyscore(UPCOMING_KEY, 0, now.to_i)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
@id = options[:id]
|
16
|
+
@topic = options[:topic]
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
id == other.id
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
@id ||= Digest::MD5.hexdigest @topic
|
25
|
+
end
|
26
|
+
|
27
|
+
def topic
|
28
|
+
@topic ||= Zulu.redis.get "#{KEY_PREFIX}:#{id}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def subscriptions_count
|
32
|
+
Zulu.redis.scard("#{KEY_PREFIX}:#{id}:subscriptions")
|
33
|
+
end
|
34
|
+
|
35
|
+
def subscriptions
|
36
|
+
Zulu.redis.smembers("#{KEY_PREFIX}:#{id}:subscriptions")
|
37
|
+
end
|
38
|
+
|
39
|
+
def subscribe(subscription_id)
|
40
|
+
Zulu.redis.sadd("#{KEY_PREFIX}:#{id}:subscriptions", subscription_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def unsubscribe(subscription_id)
|
44
|
+
Zulu.redis.srem("#{KEY_PREFIX}:#{id}:subscriptions", subscription_id)
|
45
|
+
destroy if subscriptions_count == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def parser
|
49
|
+
@parser ||= Rufus::CronLine.new(topic)
|
50
|
+
end
|
51
|
+
|
52
|
+
def next_time(now=Time.now)
|
53
|
+
parser.next_time(now)
|
54
|
+
end
|
55
|
+
|
56
|
+
def reset_next(now=Time.now)
|
57
|
+
Zulu.redis.zadd(UPCOMING_KEY, next_time(now).to_i, id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def save
|
61
|
+
Zulu.redis.multi do
|
62
|
+
Zulu.redis.set "#{KEY_PREFIX}:#{id}", topic
|
63
|
+
reset_next
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def destroy
|
68
|
+
Zulu.redis.multi do
|
69
|
+
Zulu.redis.del "#{KEY_PREFIX}:#{id}"
|
70
|
+
Zulu.redis.zrem(UPCOMING_KEY, id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Zulu
|
2
|
+
class TopicDistribution
|
3
|
+
LIST = "topic_distributions".freeze
|
4
|
+
|
5
|
+
def self.push(distribution)
|
6
|
+
Zulu.redis.lpush(LIST, distribution.to_json)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.pop(timeout=nil)
|
10
|
+
_, options = Zulu.redis.brpop(LIST, timeout: timeout)
|
11
|
+
options and new(Oj.load(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
@id = options[:id]
|
16
|
+
@now = options[:now]
|
17
|
+
end
|
18
|
+
|
19
|
+
def now
|
20
|
+
@now
|
21
|
+
end
|
22
|
+
|
23
|
+
def topic
|
24
|
+
@topic ||= Topic.new(id: @id)
|
25
|
+
end
|
26
|
+
|
27
|
+
def subscriptions
|
28
|
+
topic.subscriptions.map {|sid| Subscription.new(id: sid) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
to_hash == other.to_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
{id: @id, now: @now}
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json
|
40
|
+
Oj.dump(to_hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def save
|
44
|
+
self.class.push(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def process
|
48
|
+
subscriptions.each do |subscription|
|
49
|
+
Http.post(subscription.callback, form: {now: now})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "celluloid/io"
|
2
|
+
|
3
|
+
module Zulu
|
4
|
+
class TopicDistributionProcessor
|
5
|
+
include Celluloid::IO
|
6
|
+
include Celluloid::Logger
|
7
|
+
|
8
|
+
finalizer :shutdown
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
debug "Distribution Processor starting up"
|
12
|
+
end
|
13
|
+
|
14
|
+
def process
|
15
|
+
debug "Looking for a topic distribution"
|
16
|
+
distribution = TopicDistribution.pop(1)
|
17
|
+
if distribution
|
18
|
+
debug "Distribution found. Processing..."
|
19
|
+
distribution.process
|
20
|
+
end
|
21
|
+
async.reprocess
|
22
|
+
end
|
23
|
+
|
24
|
+
def reprocess
|
25
|
+
debug "Distribution Reprocessing..."
|
26
|
+
after(0) { process }
|
27
|
+
end
|
28
|
+
|
29
|
+
def shutdown
|
30
|
+
debug "Distribution Processor shutting down"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/zulu/version.rb
CHANGED
data/lib/zulu.rb
CHANGED
@@ -7,11 +7,15 @@ require "logger"
|
|
7
7
|
|
8
8
|
require "zulu/version"
|
9
9
|
require "zulu/challenge"
|
10
|
+
require "zulu/http"
|
11
|
+
require "zulu/keeper"
|
10
12
|
require "zulu/server"
|
11
13
|
require "zulu/subscription"
|
12
14
|
require "zulu/subscription_request"
|
13
15
|
require "zulu/subscription_request_processor"
|
14
|
-
|
16
|
+
require "zulu/topic"
|
17
|
+
require "zulu/topic_distribution"
|
18
|
+
require "zulu/topic_distribution_processor"
|
15
19
|
|
16
20
|
module Zulu
|
17
21
|
DEFAULTS = {
|
@@ -20,7 +24,8 @@ module Zulu
|
|
20
24
|
servers: 0,
|
21
25
|
workers: 5,
|
22
26
|
database: "redis://127.0.0.1:6379",
|
23
|
-
keeper: false
|
27
|
+
keeper: false,
|
28
|
+
interval: 5
|
24
29
|
}.freeze
|
25
30
|
|
26
31
|
def self.options
|
@@ -44,17 +49,20 @@ module Zulu
|
|
44
49
|
on :w, :workers, 'Run WORKERS background workers (default: 5)', argument: true, as: Integer
|
45
50
|
on :d, :database, "Connect to DATABASE (default: redis://127.0.0.1:6379)", argument: true
|
46
51
|
on :k, :keeper, "Run a keeper worker (default: false)"
|
52
|
+
on :i, :interval, 'Check time every INTERVAL seconds (default: 5)', argument: true, as: Integer
|
47
53
|
end
|
48
54
|
opts.each do |option|
|
49
55
|
next unless option.value
|
50
|
-
|
51
|
-
|
56
|
+
value = option.value
|
57
|
+
value = value.strip if value.is_a?(String)
|
58
|
+
options[option.key.to_sym] = value
|
52
59
|
end
|
53
60
|
rescue Slop::Error => e
|
54
61
|
abort "ERROR: #{e.message}"
|
55
62
|
end
|
56
63
|
|
57
64
|
def self.run
|
65
|
+
run_keeper if options[:keeper]
|
58
66
|
run_workers if options[:workers] > 0
|
59
67
|
run_servers if options[:servers] > 0
|
60
68
|
sleep
|
@@ -63,11 +71,21 @@ module Zulu
|
|
63
71
|
end
|
64
72
|
|
65
73
|
def self.stop
|
74
|
+
stop_keeper if options[:keeper]
|
66
75
|
stop_servers if options[:servers] > 0
|
67
76
|
stop_workers if options[:workers] > 0
|
68
77
|
exit
|
69
78
|
end
|
70
79
|
|
80
|
+
def self.run_keeper
|
81
|
+
Keeper.supervise_as :keeper
|
82
|
+
Celluloid::Actor[:keeper].async.start
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.stop_keeper
|
86
|
+
Celluloid::Actor[:keeper].terminate if Celluloid::Actor[:worker_supervisor]
|
87
|
+
end
|
88
|
+
|
71
89
|
def self.run_servers
|
72
90
|
Rack::Handler::Reel.run Zulu::Server, port: options[:port],
|
73
91
|
host: options[:host],
|
@@ -84,7 +102,11 @@ module Zulu
|
|
84
102
|
Celluloid::Actor[:worker_supervisor].pool(SubscriptionRequestProcessor,
|
85
103
|
as: :request_processors,
|
86
104
|
size: options[:workers])
|
105
|
+
Celluloid::Actor[:worker_supervisor].pool(TopicDistributionProcessor,
|
106
|
+
as: :distribution_processors,
|
107
|
+
size: options[:workers])
|
87
108
|
Celluloid::Actor[:request_processors].async.process
|
109
|
+
Celluloid::Actor[:distribution_processors].async.process
|
88
110
|
end
|
89
111
|
|
90
112
|
def self.stop_workers
|
data/test/minitest_helper.rb
CHANGED
data/test/test_challenge.rb
CHANGED
data/test/test_http.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestHttp < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def test_it_returns_response_for_get
|
7
|
+
response = Object.new
|
8
|
+
Net::HTTP.stub(:get_response, response) do
|
9
|
+
assert_equal response, Http.get('http://www.example.com')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_it_returns_response_for_post
|
14
|
+
response = Object.new
|
15
|
+
Net::HTTP.stub(:post_form, response) do
|
16
|
+
assert_equal response, Http.post('http://www.example.com')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/test/test_keeper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestKeeper < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
end
|
9
|
+
|
10
|
+
def topic_id
|
11
|
+
'5906891b49b573503fcdcce058f1ef5d'
|
12
|
+
end
|
13
|
+
|
14
|
+
def topic_cron
|
15
|
+
'*/15 * * * *'
|
16
|
+
end
|
17
|
+
|
18
|
+
def topic_options_without_id
|
19
|
+
{topic: topic_cron}
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_it_resets_next_on_tick
|
23
|
+
topic = Topic.new(topic_options_without_id)
|
24
|
+
now = Time.new(2013,8,1)
|
25
|
+
Time.stub(:now, now) do
|
26
|
+
topic.save
|
27
|
+
end
|
28
|
+
keeper = Keeper.new
|
29
|
+
keeper.tick now + 15 * 60
|
30
|
+
assert_equal (now + 30 * 60).to_i, Zulu.redis.zscore(Topic::UPCOMING_KEY, topic_id)
|
31
|
+
end
|
32
|
+
end
|
data/test/test_subscription.rb
CHANGED
@@ -34,6 +34,34 @@ class TestSubscription < MiniTest::Test
|
|
34
34
|
assert_equal subscription_callback, callback
|
35
35
|
end
|
36
36
|
|
37
|
+
def test_it_saves_topic_on_save
|
38
|
+
subscription = Subscription.new(subscription_options_without_id)
|
39
|
+
mock_topic = MiniTest::Mock.new
|
40
|
+
metaclass = class << mock_topic; self; end
|
41
|
+
metaclass.send(:define_method, :subscribe) do |_|
|
42
|
+
true
|
43
|
+
end
|
44
|
+
mock_topic.expect(:save, true)
|
45
|
+
subscription.stub(:topic, mock_topic) do
|
46
|
+
subscription.save
|
47
|
+
end
|
48
|
+
mock_topic.verify
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_it_subscribes_to_topic_on_save
|
52
|
+
subscription = Subscription.new(subscription_options_without_id)
|
53
|
+
mock_topic = MiniTest::Mock.new
|
54
|
+
metaclass = class << mock_topic; self; end
|
55
|
+
metaclass.send(:define_method, :save) do
|
56
|
+
true
|
57
|
+
end
|
58
|
+
mock_topic.expect(:subscribe, true, [subscription_id])
|
59
|
+
subscription.stub(:topic, mock_topic) do
|
60
|
+
subscription.save
|
61
|
+
end
|
62
|
+
mock_topic.verify
|
63
|
+
end
|
64
|
+
|
37
65
|
def test_it_retrieves_callback_for_id
|
38
66
|
Subscription.new(subscription_options_without_id).save
|
39
67
|
callback = Subscription.new(id: subscription_id).callback
|
@@ -47,4 +75,15 @@ class TestSubscription < MiniTest::Test
|
|
47
75
|
assert_nil Zulu.redis.get "#{Subscription::KEY_PREFIX}:#{subscription_id}:callback"
|
48
76
|
end
|
49
77
|
|
78
|
+
def test_it_unsubscribes_to_topic_on_destroy
|
79
|
+
subscription = Subscription.new(subscription_options_without_id)
|
80
|
+
subscription.save
|
81
|
+
mock_topic = MiniTest::Mock.new
|
82
|
+
mock_topic.expect(:unsubscribe, true, [subscription_id])
|
83
|
+
subscription.stub(:topic, mock_topic) do
|
84
|
+
subscription.destroy
|
85
|
+
end
|
86
|
+
mock_topic.verify
|
87
|
+
end
|
88
|
+
|
50
89
|
end
|
@@ -5,7 +5,6 @@ class TestSubscriptionRequest < MiniTest::Test
|
|
5
5
|
|
6
6
|
def teardown
|
7
7
|
Zulu.redis.flushall
|
8
|
-
WebMock.reset!
|
9
8
|
end
|
10
9
|
|
11
10
|
def subscribe_options(opts={})
|
@@ -132,31 +131,36 @@ class TestSubscriptionRequest < MiniTest::Test
|
|
132
131
|
def test_it_requests_callback_on_verify
|
133
132
|
challenge = 'foobar'
|
134
133
|
request = subscribe_request
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
134
|
+
response = MiniTest::Mock.new
|
135
|
+
response.expect(:code, "200")
|
136
|
+
response.expect(:body, challenge)
|
137
|
+
Http.stub(:get, response) do
|
138
|
+
Challenge.stub(:new, challenge) do
|
139
|
+
request.verify
|
140
|
+
end
|
140
141
|
end
|
141
|
-
|
142
|
+
response.verify
|
142
143
|
end
|
143
144
|
|
144
145
|
def test_it_passes_on_good_verify
|
145
146
|
challenge = 'foobar'
|
146
147
|
request = subscribe_request
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
response = MiniTest::Mock.new
|
149
|
+
response.expect(:code, "200")
|
150
|
+
response.expect(:body, challenge)
|
151
|
+
Http.stub(:get, response) do
|
152
|
+
Challenge.stub(:new, challenge) do
|
153
|
+
assert request.verify
|
154
|
+
end
|
152
155
|
end
|
153
156
|
end
|
154
157
|
|
155
158
|
def test_it_fails_on_bad_verify
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
159
|
+
response = MiniTest::Mock.new
|
160
|
+
response.expect(:code, "404")
|
161
|
+
Http.stub(:get, response) do
|
162
|
+
deny subscribe_request.verify
|
163
|
+
end
|
160
164
|
end
|
161
165
|
|
162
166
|
def test_it_creates_subscription_on_subscribe
|
data/test/test_topic.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestSubscription < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
end
|
9
|
+
|
10
|
+
def topic_id
|
11
|
+
'5906891b49b573503fcdcce058f1ef5d'
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscription_id
|
15
|
+
'3a2d38ef403d8f494988a9e1513e7ce0'
|
16
|
+
end
|
17
|
+
|
18
|
+
def topic_cron
|
19
|
+
'*/15 * * * *'
|
20
|
+
end
|
21
|
+
|
22
|
+
def topic_options_without_id
|
23
|
+
{topic: topic_cron}
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_it_generates_an_id_for_topic
|
27
|
+
topic = Topic.new(topic_options_without_id)
|
28
|
+
assert_equal topic_id, topic.id
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_it_calculates_next_time
|
32
|
+
topic = Topic.new(topic_options_without_id)
|
33
|
+
now = Time.new(2013,8,1)
|
34
|
+
assert_equal now + 15 * 60, topic.next_time(now)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_it_stores_next_time_on_reset
|
38
|
+
topic = Topic.new(topic_options_without_id)
|
39
|
+
now = Time.new(2013,8,1)
|
40
|
+
topic.reset_next(now)
|
41
|
+
topic_next = Zulu.redis.zscore Topic::UPCOMING_KEY, topic_id
|
42
|
+
assert_equal (now + 15 * 60).to_i, topic_next
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_it_stores_next_time_on_save
|
46
|
+
topic = Topic.new(topic_options_without_id)
|
47
|
+
now = Time.new(2013,8,1)
|
48
|
+
Time.stub(:now, now) do
|
49
|
+
topic.save
|
50
|
+
end
|
51
|
+
topic_next = Zulu.redis.zscore Topic::UPCOMING_KEY, topic_id
|
52
|
+
assert_equal (now + 15 * 60).to_i, topic_next
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_it_stores_topic_on_save
|
56
|
+
topic = Topic.new(topic_options_without_id)
|
57
|
+
topic.save
|
58
|
+
topic_topic = Zulu.redis.get "#{Topic::KEY_PREFIX}:#{topic_id}"
|
59
|
+
assert_equal topic_cron, topic_topic
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_it_retrieves_topic_for_id
|
63
|
+
Topic.new(topic_options_without_id).save
|
64
|
+
topic_topic = Topic.new(id: topic_id).topic
|
65
|
+
assert_equal topic_cron, topic_topic
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_it_removes_topic_on_destroy
|
69
|
+
topic = Topic.new(topic_options_without_id)
|
70
|
+
topic.save
|
71
|
+
topic.destroy
|
72
|
+
assert_nil Zulu.redis.get "#{Topic::KEY_PREFIX}:#{topic_id}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_it_removes_topic_from_upcoming_on_destroy
|
76
|
+
topic = Topic.new(topic_options_without_id)
|
77
|
+
topic.save
|
78
|
+
topic.destroy
|
79
|
+
assert_empty Zulu.redis.zrange Topic::UPCOMING_KEY, 0, -1
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_it_finds_present_topic_in_happening
|
83
|
+
topic = Topic.new(topic_options_without_id)
|
84
|
+
now = Time.new(2013,8,1)
|
85
|
+
Time.stub(:now, now) do
|
86
|
+
topic.save
|
87
|
+
end
|
88
|
+
assert_includes Topic.happening(now + 15 * 60), topic.id
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_it_finds_past_topic_in_happening
|
92
|
+
topic = Topic.new(topic_options_without_id)
|
93
|
+
now = Time.new(2013,8,1)
|
94
|
+
Time.stub(:now, now) do
|
95
|
+
topic.save
|
96
|
+
end
|
97
|
+
assert_includes Topic.happening(now + 20 * 60), topic.id
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_it_does_not_find_future_topic_in_happening
|
101
|
+
topic = Topic.new(topic_options_without_id)
|
102
|
+
now = Time.new(2013,8,1)
|
103
|
+
Time.stub(:now, now) do
|
104
|
+
topic.save
|
105
|
+
end
|
106
|
+
deny_includes Topic.happening(now), topic.id
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_it_adds_subscription_on_subscribe
|
110
|
+
topic = Topic.new(topic_options_without_id)
|
111
|
+
topic.subscribe subscription_id
|
112
|
+
assert_includes topic.subscriptions, subscription_id
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_it_increments_subscription_count_on_subscribe
|
116
|
+
topic = Topic.new(topic_options_without_id)
|
117
|
+
topic.subscribe subscription_id
|
118
|
+
assert_equal 1, topic.subscriptions_count
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_it_removes_subscription_on_unsubscribe
|
122
|
+
topic = Topic.new(topic_options_without_id)
|
123
|
+
topic.subscribe subscription_id
|
124
|
+
topic.unsubscribe subscription_id
|
125
|
+
deny_includes topic.subscriptions, subscription_id
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_it_decrements_subscription_count_on_unsubscribe
|
129
|
+
topic = Topic.new(topic_options_without_id)
|
130
|
+
topic.subscribe subscription_id
|
131
|
+
topic.unsubscribe subscription_id
|
132
|
+
assert_equal 0, topic.subscriptions_count
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_it_destroys_topic_from_upcoming_on_destroy
|
136
|
+
topic = Topic.new(topic_options_without_id)
|
137
|
+
topic.save
|
138
|
+
topic.subscribe subscription_id
|
139
|
+
topic.unsubscribe subscription_id
|
140
|
+
assert_nil Zulu.redis.get "#{Topic::KEY_PREFIX}:#{topic_id}"
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestTopicDistribution < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
end
|
9
|
+
|
10
|
+
def topic_id
|
11
|
+
'5906891b49b573503fcdcce058f1ef5d'
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscription_id
|
15
|
+
'3a2d38ef403d8f494988a9e1513e7ce0'
|
16
|
+
end
|
17
|
+
|
18
|
+
def time_now
|
19
|
+
Time.new(2013,8,1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def distribution_options
|
23
|
+
{id: topic_id, now: time_now}
|
24
|
+
end
|
25
|
+
|
26
|
+
def topic_distribution
|
27
|
+
TopicDistribution.new(distribution_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_it_pops_the_next_distribution
|
31
|
+
distribution = topic_distribution
|
32
|
+
distribution.save
|
33
|
+
assert_equal distribution, TopicDistribution.pop
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_it_accepts_timeout_for_pop
|
37
|
+
mock = MiniTest::Mock.new
|
38
|
+
mock.expect(:brpop, nil, [TopicDistribution::LIST, timeout: 5])
|
39
|
+
Zulu.stub(:redis, mock) do
|
40
|
+
TopicDistribution.pop(5)
|
41
|
+
end
|
42
|
+
mock.verify
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_it_proxies_topic
|
46
|
+
assert_equal Topic.new(id: topic_id), topic_distribution.topic
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_it_returns_subscriptions_for_topic
|
50
|
+
topic = Topic.new(id: topic_id)
|
51
|
+
topic.subscribe subscription_id
|
52
|
+
assert_includes topic_distribution.subscriptions,
|
53
|
+
Subscription.new(id: subscription_id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_it_reconstructs_options
|
57
|
+
assert_equal distribution_options, topic_distribution.to_hash
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_it_converts_options_to_json
|
61
|
+
assert_equal Oj.dump(distribution_options), topic_distribution.to_json
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_it_adds_self_to_queue_on_save
|
65
|
+
distribution = topic_distribution
|
66
|
+
distribution.save
|
67
|
+
assert_equal distribution.to_json, Zulu.redis.rpop(TopicDistribution::LIST)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_it_posts_to_subscription_callback
|
71
|
+
distribution = topic_distribution
|
72
|
+
subscription = MiniTest::Mock.new
|
73
|
+
subscription.expect(:callback, 'www.example.com')
|
74
|
+
Http.stub(:post, true) do
|
75
|
+
distribution.stub(:subscriptions, [subscription]) do
|
76
|
+
distribution.process
|
77
|
+
end
|
78
|
+
end
|
79
|
+
subscription.verify
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestTopicDistributionProcessor < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_it_processes_request
|
11
|
+
processor = TopicDistributionProcessor.new
|
12
|
+
|
13
|
+
distribution = MiniTest::Mock.new
|
14
|
+
distribution.expect(:process, true)
|
15
|
+
|
16
|
+
TopicDistribution.stub(:pop, distribution) do
|
17
|
+
processor.stub(:reprocess, true) do
|
18
|
+
processor.process
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
distribution.verify
|
23
|
+
end
|
24
|
+
end
|
data/test/test_zulu.rb
CHANGED
@@ -37,6 +37,10 @@ class TestZulu < MiniTest::Test
|
|
37
37
|
deny Zulu.options[:keeper]
|
38
38
|
end
|
39
39
|
|
40
|
+
def test_it_has_default_option_for_interval
|
41
|
+
assert_equal 5, Zulu.options[:interval]
|
42
|
+
end
|
43
|
+
|
40
44
|
def test_it_accepts_option_for_port
|
41
45
|
Zulu.parse_options(['-p 3000'])
|
42
46
|
assert_equal 3000, Zulu.options[:port]
|
@@ -67,6 +71,11 @@ class TestZulu < MiniTest::Test
|
|
67
71
|
assert Zulu.options[:keeper]
|
68
72
|
end
|
69
73
|
|
74
|
+
def test_it_accepts_option_for_interval
|
75
|
+
Zulu.parse_options(['-i 30'])
|
76
|
+
assert_equal 30, Zulu.options[:interval]
|
77
|
+
end
|
78
|
+
|
70
79
|
def test_it_aborts_on_option_with_missing_arg
|
71
80
|
assert_raises(SystemExit) do
|
72
81
|
out, err = capture_io { Zulu.parse_options(['-p']) }
|
data/zulu.gemspec
CHANGED
@@ -25,11 +25,11 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_dependency "celluloid-redis", "~> 0.0.2"
|
26
26
|
spec.add_dependency "oj", "~> 2.1.4"
|
27
27
|
spec.add_dependency "addressable", "~> 2.3.5"
|
28
|
+
spec.add_dependency "rufus-scheduler", "~> 2.0.22"
|
28
29
|
|
29
30
|
spec.add_development_dependency "bundler", "~> 1.3"
|
30
31
|
spec.add_development_dependency "rake"
|
31
32
|
spec.add_development_dependency "minitest", "~> 5.0.6"
|
32
33
|
spec.add_development_dependency "minitest-english", "~> 0.1.0"
|
33
34
|
spec.add_development_dependency "rack-test", "~> 0.6.2"
|
34
|
-
spec.add_development_dependency "webmock", "~> 1.13.0"
|
35
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zulu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Weinand
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ~>
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 2.3.5
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rufus-scheduler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.0.22
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 2.0.22
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: bundler
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,20 +192,6 @@ dependencies:
|
|
178
192
|
- - ~>
|
179
193
|
- !ruby/object:Gem::Version
|
180
194
|
version: 0.6.2
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: webmock
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - ~>
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: 1.13.0
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - ~>
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: 1.13.0
|
195
195
|
description: A standalone PuSH-inspired service for scheduling web hook execution
|
196
196
|
email:
|
197
197
|
- dweinand@gmail.com
|
@@ -209,17 +209,27 @@ files:
|
|
209
209
|
- bin/zulu
|
210
210
|
- lib/zulu.rb
|
211
211
|
- lib/zulu/challenge.rb
|
212
|
+
- lib/zulu/http.rb
|
213
|
+
- lib/zulu/keeper.rb
|
212
214
|
- lib/zulu/server.rb
|
213
215
|
- lib/zulu/subscription.rb
|
214
216
|
- lib/zulu/subscription_request.rb
|
215
217
|
- lib/zulu/subscription_request_processor.rb
|
218
|
+
- lib/zulu/topic.rb
|
219
|
+
- lib/zulu/topic_distribution.rb
|
220
|
+
- lib/zulu/topic_distribution_processor.rb
|
216
221
|
- lib/zulu/version.rb
|
217
222
|
- test/minitest_helper.rb
|
218
223
|
- test/test_challenge.rb
|
224
|
+
- test/test_http.rb
|
225
|
+
- test/test_keeper.rb
|
219
226
|
- test/test_server.rb
|
220
227
|
- test/test_subscription.rb
|
221
228
|
- test/test_subscription_request.rb
|
222
229
|
- test/test_subscription_request_processor.rb
|
230
|
+
- test/test_topic.rb
|
231
|
+
- test/test_topic_distribution.rb
|
232
|
+
- test/test_topic_distribution_processor.rb
|
223
233
|
- test/test_zulu.rb
|
224
234
|
- zulu.gemspec
|
225
235
|
homepage: ''
|
@@ -249,8 +259,13 @@ summary: PuSH-inspired web hook scheduler
|
|
249
259
|
test_files:
|
250
260
|
- test/minitest_helper.rb
|
251
261
|
- test/test_challenge.rb
|
262
|
+
- test/test_http.rb
|
263
|
+
- test/test_keeper.rb
|
252
264
|
- test/test_server.rb
|
253
265
|
- test/test_subscription.rb
|
254
266
|
- test/test_subscription_request.rb
|
255
267
|
- test/test_subscription_request_processor.rb
|
268
|
+
- test/test_topic.rb
|
269
|
+
- test/test_topic_distribution.rb
|
270
|
+
- test/test_topic_distribution_processor.rb
|
256
271
|
- test/test_zulu.rb
|