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