zulu 0.0.2 → 0.0.3
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/README.md +2 -0
- data/lib/zulu.rb +43 -6
- data/lib/zulu/challenge.rb +10 -0
- data/lib/zulu/server.rb +11 -2
- data/lib/zulu/subscription.rb +31 -0
- data/lib/zulu/subscription_request.rb +106 -0
- data/lib/zulu/subscription_request_processor.rb +33 -0
- data/lib/zulu/version.rb +1 -1
- data/test/minitest_helper.rb +11 -3
- data/test/test_challenge.rb +18 -0
- data/test/test_server.rb +21 -0
- data/test/test_subscription.rb +50 -0
- data/test/test_subscription_request.rb +208 -0
- data/test/test_subscription_request_processor.rb +25 -0
- data/test/test_zulu.rb +8 -4
- data/zulu.gemspec +8 -3
- metadata +84 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1bf65dca8019b224cf68263c2eb02030c6c73ee0
|
4
|
+
data.tar.gz: 129978dfebda32e679a14923de6d8e2629339397
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26cf2c9f96eaffba2a7e895d9a261e1435e975df1ddad5333101bb4b5d4636fb756c78c893af12ebf6e71a09b3984d52272a9d79b6d064832014e2af274bf546
|
7
|
+
data.tar.gz: 473a95bb5c9a92a69879b159e501dcefa2efc05f408aaf380b3e51902be39705fda8988f58abcadfc7889e10048c04edf0d1de58ccb0fc4f44cfcc4114f17711
|
data/README.md
CHANGED
data/lib/zulu.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
require "slop"
|
2
2
|
require "rack"
|
3
3
|
require "reel"
|
4
|
+
require "redis"
|
5
|
+
require "celluloid/redis"
|
6
|
+
require "logger"
|
4
7
|
|
5
8
|
require "zulu/version"
|
9
|
+
require "zulu/challenge"
|
6
10
|
require "zulu/server"
|
11
|
+
require "zulu/subscription"
|
12
|
+
require "zulu/subscription_request"
|
13
|
+
require "zulu/subscription_request_processor"
|
14
|
+
|
7
15
|
|
8
16
|
module Zulu
|
9
17
|
DEFAULTS = {
|
@@ -11,9 +19,9 @@ module Zulu
|
|
11
19
|
host: "0.0.0.0",
|
12
20
|
servers: 0,
|
13
21
|
workers: 5,
|
14
|
-
database: "
|
22
|
+
database: "redis://127.0.0.1:6379",
|
15
23
|
keeper: false
|
16
|
-
}
|
24
|
+
}.freeze
|
17
25
|
|
18
26
|
def self.options
|
19
27
|
@options ||= DEFAULTS.dup
|
@@ -34,7 +42,7 @@ module Zulu
|
|
34
42
|
on :o, :host, 'Listen on HOST (default: 0.0.0.0)', argument: true
|
35
43
|
on :s, :servers, 'Run SERVERS server workers (default: 0)', argument: true, as: Integer
|
36
44
|
on :w, :workers, 'Run WORKERS background workers (default: 5)', argument: true, as: Integer
|
37
|
-
on :d, :database, "Connect to DATABASE (default:
|
45
|
+
on :d, :database, "Connect to DATABASE (default: redis://127.0.0.1:6379)", argument: true
|
38
46
|
on :k, :keeper, "Run a keeper worker (default: false)"
|
39
47
|
end
|
40
48
|
opts.each do |option|
|
@@ -47,9 +55,17 @@ module Zulu
|
|
47
55
|
end
|
48
56
|
|
49
57
|
def self.run
|
50
|
-
if options[:
|
51
|
-
|
52
|
-
|
58
|
+
run_workers if options[:workers] > 0
|
59
|
+
run_servers if options[:servers] > 0
|
60
|
+
sleep
|
61
|
+
rescue Interrupt
|
62
|
+
stop
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.stop
|
66
|
+
stop_servers if options[:servers] > 0
|
67
|
+
stop_workers if options[:workers] > 0
|
68
|
+
exit
|
53
69
|
end
|
54
70
|
|
55
71
|
def self.run_servers
|
@@ -58,4 +74,25 @@ module Zulu
|
|
58
74
|
workers: options[:servers]
|
59
75
|
end
|
60
76
|
|
77
|
+
def self.stop_servers
|
78
|
+
Celluloid::Actor[:reel_server].terminate if Celluloid::Actor[:reel_server]
|
79
|
+
Celluloid::Actor[:reel_rack_pool].terminate if Celluloid::Actor[:reel_rack_pool]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.run_workers
|
83
|
+
Celluloid::Actor[:worker_supervisor] = Celluloid::SupervisionGroup.run!
|
84
|
+
Celluloid::Actor[:worker_supervisor].pool(SubscriptionRequestProcessor,
|
85
|
+
as: :request_processors,
|
86
|
+
size: options[:workers])
|
87
|
+
Celluloid::Actor[:request_processors].async.process
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.stop_workers
|
91
|
+
Celluloid::Actor[:worker_supervisor].terminate if Celluloid::Actor[:worker_supervisor]
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.redis
|
95
|
+
Thread.current[:redis] ||= Redis.new(url: options[:database], driver: :celluloid)
|
96
|
+
end
|
97
|
+
|
61
98
|
end
|
data/lib/zulu/server.rb
CHANGED
@@ -4,16 +4,25 @@ module Zulu
|
|
4
4
|
class Server < Sinatra::Base
|
5
5
|
CONTENT_TYPES = ['application/x-www-form-urlencoded']
|
6
6
|
|
7
|
+
get '/' do
|
8
|
+
Time.now.utc.xmlschema
|
9
|
+
end
|
10
|
+
|
7
11
|
post '/' do
|
8
12
|
if valid_content_type?
|
9
13
|
process_subscription
|
10
14
|
else
|
11
|
-
|
15
|
+
error 406
|
12
16
|
end
|
13
17
|
end
|
14
18
|
|
15
19
|
def process_subscription
|
16
|
-
|
20
|
+
request = SubscriptionRequest.new(params)
|
21
|
+
if request.save
|
22
|
+
status 202
|
23
|
+
else
|
24
|
+
error 400, request.error_messages.join("\n")
|
25
|
+
end
|
17
26
|
end
|
18
27
|
|
19
28
|
def valid_content_type?
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
|
3
|
+
module Zulu
|
4
|
+
class Subscription
|
5
|
+
|
6
|
+
KEY_PREFIX = "subscription".freeze
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
@id = options[:id]
|
10
|
+
@topic = options[:topic]
|
11
|
+
@callback = options[:callback]
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
@id ||= Digest::MD5.hexdigest [@topic, @callback].join(':')
|
16
|
+
end
|
17
|
+
|
18
|
+
def callback
|
19
|
+
@callback ||= Zulu.redis.get "#{KEY_PREFIX}:#{id}:callback"
|
20
|
+
end
|
21
|
+
|
22
|
+
def save
|
23
|
+
Zulu.redis.set "#{KEY_PREFIX}:#{id}:callback", callback
|
24
|
+
end
|
25
|
+
|
26
|
+
def destroy
|
27
|
+
Zulu.redis.del "#{KEY_PREFIX}:#{id}:callback"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require "addressable/uri"
|
2
|
+
require "oj"
|
3
|
+
|
4
|
+
|
5
|
+
module Zulu
|
6
|
+
class SubscriptionRequest
|
7
|
+
LIST = "subscription_requests".freeze
|
8
|
+
MODES = %w(subscribe unsubscribe).freeze
|
9
|
+
|
10
|
+
def self.push(request)
|
11
|
+
Zulu.redis.lpush(LIST, request.to_json)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.pop(timeout=nil)
|
15
|
+
_, params = Zulu.redis.brpop(LIST, timeout: timeout)
|
16
|
+
params and new(Oj.load(params))
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(params)
|
20
|
+
@mode = params['hub.mode']
|
21
|
+
@topic = params['hub.topic']
|
22
|
+
@callback = params['hub.callback']
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
@valid ||= begin
|
27
|
+
validate_mode
|
28
|
+
validate_topic
|
29
|
+
validate_callback
|
30
|
+
errors.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def errors
|
35
|
+
@errors ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def error_messages
|
39
|
+
errors.map {|e| "hub.#{e[0]} #{e[1]}" }
|
40
|
+
end
|
41
|
+
|
42
|
+
def save
|
43
|
+
valid? and self.class.push(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_hash
|
47
|
+
[:mode, :topic, :callback].inject({}) do |hash, attr|
|
48
|
+
hash["hub.#{attr}"] = instance_variable_get(:"@#{attr}")
|
49
|
+
hash
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_json
|
54
|
+
Oj.dump(to_hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
to_hash == other.to_hash
|
59
|
+
end
|
60
|
+
|
61
|
+
def verify
|
62
|
+
challenge = Challenge.new
|
63
|
+
params = to_hash
|
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)
|
68
|
+
response.code == "200" && response.body == challenge
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform
|
72
|
+
subscription = Subscription.new(topic: @topic, callback: @callback)
|
73
|
+
case @mode
|
74
|
+
when 'subscribe'
|
75
|
+
subscription.save
|
76
|
+
when 'unsubscribe'
|
77
|
+
subscription.destroy
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def process
|
82
|
+
verify and perform
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def validate_mode
|
88
|
+
@mode or errors << [:mode, 'must be present']
|
89
|
+
in_list = MODES.include?(@mode)
|
90
|
+
in_list or errors << [:mode, "must be either #{MODES.join(' or ')}"]
|
91
|
+
end
|
92
|
+
|
93
|
+
def validate_topic
|
94
|
+
@topic or errors << [:topic, 'must be present']
|
95
|
+
end
|
96
|
+
|
97
|
+
def validate_callback
|
98
|
+
@callback or errors << [:callback, 'must be present']
|
99
|
+
uri = Addressable::URI.parse(@callback)
|
100
|
+
uri and %w(http https).include? uri.scheme or fail Addressable::URI::InvalidURIError
|
101
|
+
rescue Addressable::URI::InvalidURIError
|
102
|
+
errors << [:callback, 'must be a valid http url']
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "celluloid/io"
|
2
|
+
|
3
|
+
module Zulu
|
4
|
+
class SubscriptionRequestProcessor
|
5
|
+
include Celluloid::IO
|
6
|
+
include Celluloid::Logger
|
7
|
+
|
8
|
+
finalizer :shutdown
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
debug "Request Processor starting up"
|
12
|
+
end
|
13
|
+
|
14
|
+
def process
|
15
|
+
debug "Looking for a subscription request"
|
16
|
+
request = SubscriptionRequest.pop(1)
|
17
|
+
if request
|
18
|
+
debug "Request found. Processing..."
|
19
|
+
request.process
|
20
|
+
end
|
21
|
+
async.reprocess
|
22
|
+
end
|
23
|
+
|
24
|
+
def reprocess
|
25
|
+
debug "Reprocessing..."
|
26
|
+
after(0) { process }
|
27
|
+
end
|
28
|
+
|
29
|
+
def shutdown
|
30
|
+
debug "Request Processor shutting down"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/zulu/version.rb
CHANGED
data/test/minitest_helper.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
-
|
3
|
-
require 'rack/test'
|
2
|
+
|
4
3
|
|
5
4
|
require 'minitest/autorun'
|
6
|
-
require 'minitest/
|
5
|
+
require 'minitest/pride'
|
6
|
+
require 'minitest/english/deny'
|
7
|
+
require 'rack/test'
|
8
|
+
require "webmock/minitest"
|
9
|
+
|
10
|
+
require 'celluloid/autostart'
|
11
|
+
require 'zulu'
|
12
|
+
|
13
|
+
Celluloid.logger = nil
|
14
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestChallenge < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def test_it_returns_24_characters_by_default
|
7
|
+
assert_equal 24, Challenge.new.length
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_it_allows_arbitrary_size
|
11
|
+
assert_equal 12, Challenge.new(12).length
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_it_produces_appropriate_string
|
15
|
+
assert_match /[a-zA-Z0-9]/, Challenge.new
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/test_server.rb
CHANGED
@@ -8,6 +8,10 @@ class TestServer < MiniTest::Test
|
|
8
8
|
Server
|
9
9
|
end
|
10
10
|
|
11
|
+
def teardown
|
12
|
+
Zulu.redis.flushall
|
13
|
+
end
|
14
|
+
|
11
15
|
def subscribe_options(opts={})
|
12
16
|
{
|
13
17
|
'hub.mode' => 'subscribe',
|
@@ -16,11 +20,28 @@ class TestServer < MiniTest::Test
|
|
16
20
|
}.merge(opts)
|
17
21
|
end
|
18
22
|
|
23
|
+
def test_it_renders_current_time_on_get
|
24
|
+
now = Time.now.utc
|
25
|
+
get '/'
|
26
|
+
assert_equal now.xmlschema, last_response.body
|
27
|
+
end
|
28
|
+
|
19
29
|
def test_it_accepts_subscription_request
|
20
30
|
post '/', subscribe_options
|
21
31
|
assert_equal 202, last_response.status, last_response.body
|
22
32
|
end
|
23
33
|
|
34
|
+
def test_it_errors_if_save_fails
|
35
|
+
post '/', subscribe_options('hub.mode' => 'dance')
|
36
|
+
assert_equal 400, last_response.status, last_response.body
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_it_displays_errors_if_save_fails
|
40
|
+
post '/', subscribe_options('hub.mode' => nil)
|
41
|
+
errs = "hub.mode must be present\nhub.mode must be either subscribe or unsubscribe"
|
42
|
+
assert_equal errs, last_response.body
|
43
|
+
end
|
44
|
+
|
24
45
|
def test_it_wont_accept_incorrect_format
|
25
46
|
post '/', '', 'CONTENT_TYPE' => 'application/rss+xml'
|
26
47
|
assert_equal 406, last_response.status
|
@@ -0,0 +1,50 @@
|
|
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 subscription_id
|
11
|
+
'3a2d38ef403d8f494988a9e1513e7ce0'
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscription_callback
|
15
|
+
'http://www.example.org/callback'
|
16
|
+
end
|
17
|
+
|
18
|
+
def subscription_options_without_id
|
19
|
+
{
|
20
|
+
topic: '*/15 * * * *',
|
21
|
+
callback: subscription_callback
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_it_generates_an_id_for_topic_and_callback
|
26
|
+
subscription = Subscription.new(subscription_options_without_id)
|
27
|
+
assert_equal subscription_id, subscription.id
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_it_stores_callback_on_save
|
31
|
+
subscription = Subscription.new(subscription_options_without_id)
|
32
|
+
subscription.save
|
33
|
+
callback = Zulu.redis.get "#{Subscription::KEY_PREFIX}:#{subscription_id}:callback"
|
34
|
+
assert_equal subscription_callback, callback
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_it_retrieves_callback_for_id
|
38
|
+
Subscription.new(subscription_options_without_id).save
|
39
|
+
callback = Subscription.new(id: subscription_id).callback
|
40
|
+
assert_equal subscription_callback, callback
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_it_removes_callback_on_destroy
|
44
|
+
subscription = Subscription.new(subscription_options_without_id)
|
45
|
+
subscription.save
|
46
|
+
subscription.destroy
|
47
|
+
assert_nil Zulu.redis.get "#{Subscription::KEY_PREFIX}:#{subscription_id}:callback"
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestSubscriptionRequest < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
WebMock.reset!
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe_options(opts={})
|
12
|
+
{
|
13
|
+
'hub.mode' => 'subscribe',
|
14
|
+
'hub.topic' => '*/15 * * * *',
|
15
|
+
'hub.callback' => 'http://www.example.org/callback'
|
16
|
+
}.merge(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def subscribe_request(opts={})
|
20
|
+
SubscriptionRequest.new(subscribe_options(opts))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_it_pops_the_next_request
|
24
|
+
request = subscribe_request
|
25
|
+
request.save
|
26
|
+
assert_equal request, SubscriptionRequest.pop
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_it_accepts_timeout_for_pop
|
30
|
+
mock = MiniTest::Mock.new
|
31
|
+
mock.expect(:brpop, nil, [SubscriptionRequest::LIST, timeout: 5])
|
32
|
+
Zulu.stub(:redis, mock) do
|
33
|
+
SubscriptionRequest.pop(5)
|
34
|
+
end
|
35
|
+
mock.verify
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_it_is_valid_with_subscribe
|
39
|
+
assert subscribe_request.valid?
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_it_is_valid_with_unsubscribe
|
43
|
+
assert subscribe_request('hub.mode' => 'unsubscribe').valid?
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_it_is_not_valid_with_missing_mode
|
47
|
+
deny subscribe_request('hub.mode' => nil).valid?
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_it_has_error_with_missing_mode
|
51
|
+
request = subscribe_request('hub.mode' => nil)
|
52
|
+
request.valid?
|
53
|
+
assert_includes request.errors, [:mode, 'must be present']
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_it_is_not_valid_with_wrong_mode
|
57
|
+
deny subscribe_request('hub.mode' => 'dance').valid?
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_it_has_error_with_wrong_mode
|
61
|
+
request = subscribe_request('hub.mode' => 'dance')
|
62
|
+
request.valid?
|
63
|
+
assert_includes request.errors, [:mode, "must be either subscribe or unsubscribe"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_it_is_not_valid_with_missing_topic
|
67
|
+
deny subscribe_request('hub.topic' => nil).valid?
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_it_has_error_with_missing_topic
|
71
|
+
request = subscribe_request('hub.topic' => nil)
|
72
|
+
request.valid?
|
73
|
+
assert_includes request.errors, [:topic, 'must be present']
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_it_is_not_valid_with_missing_callback
|
77
|
+
deny subscribe_request('hub.callback' => nil).valid?
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_it_has_error_with_missing_callback
|
81
|
+
request = subscribe_request('hub.callback' => nil)
|
82
|
+
request.valid?
|
83
|
+
assert_includes request.errors, [:callback, 'must be present']
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_it_is_not_valid_with_wrong_callback
|
87
|
+
deny subscribe_request('hub.callback' => '!?/#:-)').valid?
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_it_has_error_with_wrong_callback
|
91
|
+
request = subscribe_request('hub.callback' => '!?/#:-)')
|
92
|
+
request.valid?
|
93
|
+
assert_includes request.errors, [:callback, "must be a valid http url"]
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_it_contructs_proper_error_messages
|
97
|
+
request = subscribe_request('hub.callback' => nil)
|
98
|
+
request.valid?
|
99
|
+
assert_includes request.error_messages, 'hub.callback must be present'
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_it_reconstructs_params
|
103
|
+
assert_equal subscribe_options, subscribe_request.to_hash
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_it_converts_params_to_json
|
107
|
+
assert_equal Oj.dump(subscribe_options), subscribe_request.to_json
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_it_adds_self_to_queue_on_save
|
111
|
+
request = subscribe_request
|
112
|
+
request.save
|
113
|
+
assert_equal request.to_json, Zulu.redis.rpop(SubscriptionRequest::LIST)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_it_does_not_add_self_to_queue_on_save
|
117
|
+
request = subscribe_request('hub.mode' => nil)
|
118
|
+
request.save
|
119
|
+
assert_nil Zulu.redis.rpop(SubscriptionRequest::LIST)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_it_passes_save_when_valid
|
123
|
+
request = subscribe_request
|
124
|
+
assert request.save
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_it_fails_save_when_invalid
|
128
|
+
request = subscribe_request('hub.mode' => nil)
|
129
|
+
deny request.save
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_it_requests_callback_on_verify
|
133
|
+
challenge = 'foobar'
|
134
|
+
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
|
140
|
+
end
|
141
|
+
assert_requested stub_get
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_it_passes_on_good_verify
|
145
|
+
challenge = 'foobar'
|
146
|
+
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
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
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
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_it_creates_subscription_on_subscribe
|
163
|
+
mock = MiniTest::Mock.new
|
164
|
+
mock.expect(:save, true)
|
165
|
+
request = subscribe_request
|
166
|
+
request.stub(:verify, true) do
|
167
|
+
Subscription.stub(:new, mock) do
|
168
|
+
request.process
|
169
|
+
end
|
170
|
+
end
|
171
|
+
mock.verify
|
172
|
+
end
|
173
|
+
|
174
|
+
def test_it_does_not_create_subscription_if_verify_fails
|
175
|
+
mock = MiniTest::Mock.new
|
176
|
+
request = subscribe_request
|
177
|
+
request.stub(:verify, false) do
|
178
|
+
Subscription.stub(:new, mock) do
|
179
|
+
request.process
|
180
|
+
end
|
181
|
+
end
|
182
|
+
mock.verify
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_it_destroys_subscription_on_unsubscribe
|
186
|
+
mock = MiniTest::Mock.new
|
187
|
+
mock.expect(:destroy, true)
|
188
|
+
request = subscribe_request('hub.mode' => 'unsubscribe')
|
189
|
+
request.stub(:verify, true) do
|
190
|
+
Subscription.stub(:new, mock) do
|
191
|
+
request.process
|
192
|
+
end
|
193
|
+
end
|
194
|
+
mock.verify
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_it_does_not_destroy_subscription_if_verify_fails
|
198
|
+
mock = MiniTest::Mock.new
|
199
|
+
request = subscribe_request('hub.mode' => 'unsubscribe')
|
200
|
+
request.stub(:verify, false) do
|
201
|
+
Subscription.stub(:new, mock) do
|
202
|
+
request.process
|
203
|
+
end
|
204
|
+
end
|
205
|
+
mock.verify
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestSubscriptionRequestProcessor < MiniTest::Test
|
4
|
+
include Zulu
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Zulu.redis.flushall
|
8
|
+
WebMock.reset!
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_it_processes_request
|
12
|
+
processor = SubscriptionRequestProcessor.new
|
13
|
+
|
14
|
+
request_mock = MiniTest::Mock.new
|
15
|
+
request_mock.expect(:process, true)
|
16
|
+
|
17
|
+
SubscriptionRequest.stub(:pop, request_mock) do
|
18
|
+
processor.stub(:reprocess, true) do
|
19
|
+
processor.process
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
request_mock.verify
|
24
|
+
end
|
25
|
+
end
|
data/test/test_zulu.rb
CHANGED
@@ -30,7 +30,7 @@ class TestZulu < MiniTest::Test
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_it_has_default_option_for_database
|
33
|
-
assert_equal
|
33
|
+
assert_equal "redis://127.0.0.1:6379", Zulu.options[:database]
|
34
34
|
end
|
35
35
|
|
36
36
|
def test_it_has_default_option_for_keeper
|
@@ -58,8 +58,8 @@ class TestZulu < MiniTest::Test
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def test_it_accepts_option_for_database
|
61
|
-
Zulu.parse_options(['-d localhost:9000'])
|
62
|
-
assert_equal 'localhost:9000', Zulu.options[:database]
|
61
|
+
Zulu.parse_options(['-d redis://localhost:9000'])
|
62
|
+
assert_equal 'redis://localhost:9000', Zulu.options[:database]
|
63
63
|
end
|
64
64
|
|
65
65
|
def test_it_accepts_option_for_keeper
|
@@ -75,7 +75,11 @@ class TestZulu < MiniTest::Test
|
|
75
75
|
end
|
76
76
|
|
77
77
|
#
|
78
|
-
# Zulu.
|
78
|
+
# Zulu.redis
|
79
79
|
#
|
80
80
|
|
81
|
+
def test_it_creates_redis_connection
|
82
|
+
assert_kind_of Redis, Zulu.redis
|
83
|
+
end
|
84
|
+
|
81
85
|
end
|
data/zulu.gemspec
CHANGED
@@ -18,13 +18,18 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.
|
23
|
-
spec.
|
21
|
+
spec.add_dependency "slop", "~> 3.4.6"
|
22
|
+
spec.add_dependency "sinatra", "~> 1.4.3"
|
23
|
+
spec.add_dependency "celluloid-io", "~> 0.14.1"
|
24
|
+
spec.add_dependency "reel", "~> 0.3.0"
|
25
|
+
spec.add_dependency "celluloid-redis", "~> 0.0.2"
|
26
|
+
spec.add_dependency "oj", "~> 2.1.4"
|
27
|
+
spec.add_dependency "addressable", "~> 2.3.5"
|
24
28
|
|
25
29
|
spec.add_development_dependency "bundler", "~> 1.3"
|
26
30
|
spec.add_development_dependency "rake"
|
27
31
|
spec.add_development_dependency "minitest", "~> 5.0.6"
|
28
32
|
spec.add_development_dependency "minitest-english", "~> 0.1.0"
|
29
33
|
spec.add_development_dependency "rack-test", "~> 0.6.2"
|
34
|
+
spec.add_development_dependency "webmock", "~> 1.13.0"
|
30
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.3
|
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-
|
11
|
+
date: 2013-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 1.4.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: celluloid-io
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.14.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.14.1
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: reel
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,48 @@ dependencies:
|
|
52
66
|
- - ~>
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: 0.3.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: celluloid-redis
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.2
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.0.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: oj
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.4
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.4
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: addressable
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.3.5
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.3.5
|
55
111
|
- !ruby/object:Gem::Dependency
|
56
112
|
name: bundler
|
57
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +178,20 @@ dependencies:
|
|
122
178
|
- - ~>
|
123
179
|
- !ruby/object:Gem::Version
|
124
180
|
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
|
125
195
|
description: A standalone PuSH-inspired service for scheduling web hook execution
|
126
196
|
email:
|
127
197
|
- dweinand@gmail.com
|
@@ -138,10 +208,18 @@ files:
|
|
138
208
|
- Rakefile
|
139
209
|
- bin/zulu
|
140
210
|
- lib/zulu.rb
|
211
|
+
- lib/zulu/challenge.rb
|
141
212
|
- lib/zulu/server.rb
|
213
|
+
- lib/zulu/subscription.rb
|
214
|
+
- lib/zulu/subscription_request.rb
|
215
|
+
- lib/zulu/subscription_request_processor.rb
|
142
216
|
- lib/zulu/version.rb
|
143
217
|
- test/minitest_helper.rb
|
218
|
+
- test/test_challenge.rb
|
144
219
|
- test/test_server.rb
|
220
|
+
- test/test_subscription.rb
|
221
|
+
- test/test_subscription_request.rb
|
222
|
+
- test/test_subscription_request_processor.rb
|
145
223
|
- test/test_zulu.rb
|
146
224
|
- zulu.gemspec
|
147
225
|
homepage: ''
|
@@ -170,5 +248,9 @@ specification_version: 4
|
|
170
248
|
summary: PuSH-inspired web hook scheduler
|
171
249
|
test_files:
|
172
250
|
- test/minitest_helper.rb
|
251
|
+
- test/test_challenge.rb
|
173
252
|
- test/test_server.rb
|
253
|
+
- test/test_subscription.rb
|
254
|
+
- test/test_subscription_request.rb
|
255
|
+
- test/test_subscription_request_processor.rb
|
174
256
|
- test/test_zulu.rb
|