zulu 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1bf65dca8019b224cf68263c2eb02030c6c73ee0
4
- data.tar.gz: 129978dfebda32e679a14923de6d8e2629339397
3
+ metadata.gz: 8ec4fc2ba4cf0c2764d457ed2063945de083073c
4
+ data.tar.gz: 911a784bda238a5d20280795b9002c8980969107
5
5
  SHA512:
6
- metadata.gz: 26cf2c9f96eaffba2a7e895d9a261e1435e975df1ddad5333101bb4b5d4636fb756c78c893af12ebf6e71a09b3984d52272a9d79b6d064832014e2af274bf546
7
- data.tar.gz: 473a95bb5c9a92a69879b159e501dcefa2efc05f408aaf380b3e51902be39705fda8988f58abcadfc7889e10048c04edf0d1de58ccb0fc4f44cfcc4114f17711
6
+ metadata.gz: 6e082fe6f5c99731a436c02dddf7cd7794438cadcb154917fa91038d5acb0523dd0a82f9e1293391a6576d45108bb8600b9e95f1f7fdb98a2ad2202af3efb8b8
7
+ data.tar.gz: 0a07fa35295615de3ab65be016ef3b08937213e1fc04e0cbbd4e0426b1d1c1cd543b7a5a02065185e7da889b02d251f61ae4452ebc698c1eb6a1f63e4cf43706
@@ -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[(rand(CHARS.size - 1))] until length == len
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
@@ -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
@@ -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
- uri = Addressable::URI.parse(@callback)
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
@@ -1,3 +1,3 @@
1
1
  module Zulu
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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
- option.value.is_a?(String) and option.value.strip!
51
- options[option.key.to_sym] = option.value
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
@@ -5,7 +5,6 @@ require 'minitest/autorun'
5
5
  require 'minitest/pride'
6
6
  require 'minitest/english/deny'
7
7
  require 'rack/test'
8
- require "webmock/minitest"
9
8
 
10
9
  require 'celluloid/autostart'
11
10
  require 'zulu'
@@ -12,7 +12,7 @@ class TestChallenge < MiniTest::Test
12
12
  end
13
13
 
14
14
  def test_it_produces_appropriate_string
15
- assert_match /[a-zA-Z0-9]/, Challenge.new
15
+ assert_match /^[a-zA-Z0-9]+$/, Challenge.new
16
16
  end
17
17
 
18
18
  end
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
@@ -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
@@ -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
- stub_get = stub_http_request(:get, "www.example.org/callback").
136
- with(query: subscribe_options('hub.challenge' => challenge)).
137
- to_return(body: challenge)
138
- Challenge.stub(:new, challenge) do
139
- request.verify
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
- assert_requested stub_get
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
- stub_get = stub_http_request(:get, "www.example.org/callback").
148
- with(query: subscribe_options('hub.challenge' => challenge)).
149
- to_return(body: challenge)
150
- Challenge.stub(:new, challenge) do
151
- assert request.verify
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
- stub_get = stub_http_request(:get, "www.example.org/callback").
157
- with(query: hash_including(subscribe_options)).
158
- to_return(status: [404, "Not Found"])
159
- deny subscribe_request.verify
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
@@ -5,7 +5,6 @@ class TestSubscriptionRequestProcessor < MiniTest::Test
5
5
 
6
6
  def teardown
7
7
  Zulu.redis.flushall
8
- WebMock.reset!
9
8
  end
10
9
 
11
10
  def test_it_processes_request
@@ -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.3
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-08-27 00:00:00.000000000 Z
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