firehose 1.3.6 → 2.1.0
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 +5 -5
- data/CHANGELOG.md +31 -10
- data/{LICENSE.txt → LICENSE} +6 -6
- data/README.md +379 -187
- data/app/assets/javascripts/firehose.js +258 -0
- data/config/importmap.rb +1 -0
- data/db/migrate/20260214185244_create_firehose_events.rb +23 -0
- data/lib/commands/firehose_command.rb +119 -0
- data/lib/firehose/channel.rb +40 -0
- data/lib/firehose/cleanup_job.rb +21 -0
- data/lib/firehose/engine.rb +36 -0
- data/lib/firehose/helper.rb +40 -0
- data/lib/firehose/models/channel.rb +12 -0
- data/lib/firehose/models/message.rb +11 -0
- data/lib/firehose/queue.rb +44 -0
- data/lib/firehose/server.rb +930 -17
- data/lib/firehose/sse.rb +187 -0
- data/lib/firehose/stream.rb +32 -0
- data/lib/firehose/streamable.rb +25 -0
- data/lib/firehose/websocket.rb +242 -0
- data/lib/firehose.rb +67 -15
- data/lib/generators/firehose/install/install_generator.rb +43 -0
- data/lib/generators/firehose/install/templates/firehose_controller.rb +16 -0
- metadata +39 -366
- data/.codeclimate.yml +0 -29
- data/.dockerignore +0 -2
- data/.env.sample +0 -17
- data/.gitignore +0 -14
- data/.rspec +0 -1
- data/.rubocop.yml +0 -1156
- data/.ruby-version +0 -1
- data/.travis.yml +0 -12
- data/Dockerfile +0 -11
- data/Gemfile +0 -6
- data/Procfile +0 -1
- data/Procfile.dev +0 -3
- data/Rakefile +0 -64
- data/bin/firehose +0 -9
- data/coffeelint.json +0 -129
- data/config/rainbows.rb +0 -17
- data/docker-compose.yml +0 -17
- data/firehose.gemspec +0 -45
- data/karma.config.coffee +0 -89
- data/lib/assets/javascripts/firehose/base.js.coffee +0 -4
- data/lib/assets/javascripts/firehose/consumer.js.coffee +0 -86
- data/lib/assets/javascripts/firehose/core.js.coffee +0 -6
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +0 -210
- data/lib/assets/javascripts/firehose/multiplexed_consumer.js.coffee +0 -74
- data/lib/assets/javascripts/firehose/transport.js.coffee +0 -44
- data/lib/assets/javascripts/firehose/version.js.coffee.erb +0 -2
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +0 -148
- data/lib/assets/javascripts/firehose.js.coffee +0 -2
- data/lib/firehose/assets.rb +0 -56
- data/lib/firehose/cli.rb +0 -79
- data/lib/firehose/client/consumer.rb +0 -94
- data/lib/firehose/client/producer.rb +0 -114
- data/lib/firehose/client.rb +0 -10
- data/lib/firehose/logging.rb +0 -32
- data/lib/firehose/rack/app.rb +0 -49
- data/lib/firehose/rack/consumer/http_long_poll.rb +0 -159
- data/lib/firehose/rack/consumer/web_socket.rb +0 -200
- data/lib/firehose/rack/consumer.rb +0 -78
- data/lib/firehose/rack/ping.rb +0 -88
- data/lib/firehose/rack/publisher.rb +0 -59
- data/lib/firehose/rack.rb +0 -32
- data/lib/firehose/rails.rb +0 -7
- data/lib/firehose/server/app.rb +0 -52
- data/lib/firehose/server/channel.rb +0 -72
- data/lib/firehose/server/message_buffer.rb +0 -59
- data/lib/firehose/server/publisher.rb +0 -133
- data/lib/firehose/server/redis.rb +0 -32
- data/lib/firehose/server/subscriber.rb +0 -51
- data/lib/firehose/version.rb +0 -4
- data/package.json +0 -17
- data/spec/integrations/integration_test_helper.rb +0 -22
- data/spec/integrations/rainbows_spec.rb +0 -7
- data/spec/integrations/shared_examples.rb +0 -202
- data/spec/integrations/thin_spec.rb +0 -7
- data/spec/javascripts/firehose/base_spec.coffee +0 -2
- data/spec/javascripts/firehose/consumer_spec.coffee +0 -45
- data/spec/javascripts/firehose/long_poll_spec.coffee +0 -44
- data/spec/javascripts/firehose/multiplexed_consumer_spec.coffee +0 -72
- data/spec/javascripts/firehose/transport_spec.coffee +0 -30
- data/spec/javascripts/firehose/websocket_spec.coffee +0 -27
- data/spec/javascripts/helpers/spec_helper.js +0 -31
- data/spec/javascripts/support/jquery-1.11.1.js +0 -10308
- data/spec/javascripts/support/json2.js +0 -487
- data/spec/javascripts/support/sinon-1.7.3.js +0 -4290
- data/spec/javascripts/support/spec_helper.coffee +0 -3
- data/spec/lib/assets_spec.rb +0 -35
- data/spec/lib/client/consumer_spec.rb +0 -13
- data/spec/lib/client/producer_spec.rb +0 -71
- data/spec/lib/firehose_spec.rb +0 -7
- data/spec/lib/rack/consumer/http_long_poll_spec.rb +0 -30
- data/spec/lib/rack/consumer_spec.rb +0 -19
- data/spec/lib/rack/ping_spec.rb +0 -28
- data/spec/lib/rack/publisher_spec.rb +0 -34
- data/spec/lib/server/app_spec.rb +0 -15
- data/spec/lib/server/channel_spec.rb +0 -178
- data/spec/lib/server/message_buffer_spec.rb +0 -148
- data/spec/lib/server/publisher_spec.rb +0 -91
- data/spec/lib/server/redis_spec.rb +0 -13
- data/spec/lib/server/subscriber_spec.rb +0 -70
- data/spec/spec_helper.rb +0 -84
data/spec/lib/assets_spec.rb
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'sprockets'
|
|
3
|
-
|
|
4
|
-
describe Firehose::Assets do
|
|
5
|
-
describe ".path" do
|
|
6
|
-
it "has root path" do
|
|
7
|
-
expect(Firehose::Assets.path('poop')).to eql(File.expand_path('../../../lib/assets/poop', __FILE__))
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
it "accepts folders" do
|
|
11
|
-
expect(Firehose::Assets.path('poop')).to eql(File.join(Firehose::Assets.path, 'poop'))
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
describe "Sprockets.configure" do
|
|
16
|
-
it "configures environment" do
|
|
17
|
-
env = Firehose::Assets::Sprockets.configure Sprockets::Environment.new
|
|
18
|
-
expect(env.paths).to include(Firehose::Assets.path('javascripts'))
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
describe "Sprockets.javascript" do
|
|
23
|
-
let(:js_spot_checks) do
|
|
24
|
-
[
|
|
25
|
-
"Firehose.Transport",
|
|
26
|
-
"Firehose.version",
|
|
27
|
-
"global.Firehose || (global.Firehose = {})",
|
|
28
|
-
"window.Firehose || (window.Firehose = {})"
|
|
29
|
-
]
|
|
30
|
-
end
|
|
31
|
-
it "compiles javascript" do
|
|
32
|
-
expect(Firehose::Assets::Sprockets.javascript).to include(*js_spot_checks)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Client::Consumer::WebSocket do
|
|
4
|
-
context "transport" do
|
|
5
|
-
# Transport for Firehose::Client::App class is tested via the spec/integrations suite.
|
|
6
|
-
end
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
describe Firehose::Client::Consumer::HttpLongPoll do
|
|
10
|
-
context "transport" do
|
|
11
|
-
# Transport for Firehose::Client::App class is tested via the spec/integrations suite.
|
|
12
|
-
end
|
|
13
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Client::Producer::Http do
|
|
4
|
-
let(:channel) { "/channel-awesome" }
|
|
5
|
-
let(:url) { "#{Firehose::URI}#{channel}"}
|
|
6
|
-
let(:publish_stub) { stub_request(:put, url) }
|
|
7
|
-
let(:message) { "hey dude" }
|
|
8
|
-
|
|
9
|
-
before(:all) do
|
|
10
|
-
Firehose::Client::Producer::Http.adapter = :net_http
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
after(:all) do
|
|
14
|
-
Firehose::Client::Producer::Http.adapter = nil
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it "publishes message to channel" do
|
|
18
|
-
publish_stub.to_return(:body => "", :status => 202)
|
|
19
|
-
|
|
20
|
-
Firehose::Client::Producer::Http.new.publish(message).to(channel)
|
|
21
|
-
expect(WebMock).to have_requested(:put, url).with { |req| req.body == message }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
context 'prefix is specified in URI' do
|
|
25
|
-
let(:firehose_uri) {"#{Firehose::URI}/prefix"}
|
|
26
|
-
let(:url) { "#{firehose_uri}#{channel}"}
|
|
27
|
-
|
|
28
|
-
it "publishes message to channel" do
|
|
29
|
-
publish_stub.to_return(:body => "", :status => 202)
|
|
30
|
-
|
|
31
|
-
Firehose::Client::Producer::Http.new(firehose_uri).publish(message).to(channel)
|
|
32
|
-
expect(WebMock).to have_requested(:put, url).with { |req| req.body == message }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "publishes message to channel with expiry headers" do
|
|
37
|
-
publish_stub.to_return(:body => "", :status => 202)
|
|
38
|
-
ttl = 20
|
|
39
|
-
|
|
40
|
-
Firehose::Client::Producer::Http.new.publish(message).to(channel, :ttl => ttl)
|
|
41
|
-
expect(WebMock).to have_requested(:put, url).with { |req| req.body == message and req.headers['Cache-Control'] == "max-age=#{ttl}" }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
describe "connection error handling" do
|
|
45
|
-
it "raises PublishError if not 201" do
|
|
46
|
-
publish_stub.to_return(:body => "", :status => 500)
|
|
47
|
-
|
|
48
|
-
expect {
|
|
49
|
-
Firehose::Client::Producer::Http.new.publish(message).to(channel)
|
|
50
|
-
}.to raise_exception(Firehose::Client::Producer::Http::PublishError)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
it "uses .error_handler if not 201" do
|
|
54
|
-
publish_stub.to_return(:body => "", :status => 500)
|
|
55
|
-
|
|
56
|
-
producer = Firehose::Client::Producer::Http.new
|
|
57
|
-
producer.on_error do |e|
|
|
58
|
-
expect(e.message).to match(/could not publish.+to/i)
|
|
59
|
-
end
|
|
60
|
-
producer.publish(message).to(channel)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it "raises TimeoutError if timed out" do
|
|
64
|
-
publish_stub.to_timeout
|
|
65
|
-
|
|
66
|
-
expect {
|
|
67
|
-
Firehose::Client::Producer::Http.new.publish(message).to(channel)
|
|
68
|
-
}.to raise_exception(Firehose::Client::Producer::Http::TimeoutError)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
data/spec/lib/firehose_spec.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'rack/test'
|
|
3
|
-
require 'async_rack_test'
|
|
4
|
-
|
|
5
|
-
describe Firehose::Rack::Consumer::HttpLongPoll do
|
|
6
|
-
include AsyncRackTest::Methods
|
|
7
|
-
let(:app) { Firehose::Rack::Consumer::HttpLongPoll.new }
|
|
8
|
-
|
|
9
|
-
context "transport" do
|
|
10
|
-
# Transport for Firehose::Rack::App class is tested via the spec/integrations suite.
|
|
11
|
-
end
|
|
12
|
-
context "configuration" do
|
|
13
|
-
it "has #timeout" do
|
|
14
|
-
expect(Firehose::Rack::Consumer::HttpLongPoll.new(200).timeout).to eql(200)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
context "POST request" do
|
|
19
|
-
before do
|
|
20
|
-
post "/blah"
|
|
21
|
-
end
|
|
22
|
-
it "returns 405 for POST" do
|
|
23
|
-
expect(last_response.status).to eql(405)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it "specifies GET in the Allow header" do
|
|
27
|
-
expect(last_response.headers["Allow"]).to eql("GET")
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'rack/test'
|
|
3
|
-
|
|
4
|
-
describe Firehose::Rack::Consumer, :type => :request do
|
|
5
|
-
include Rack::Test::Methods
|
|
6
|
-
let(:app) { Firehose::Rack::Consumer.new }
|
|
7
|
-
let(:path) { "/test/path/#{Time.now.to_i}" }
|
|
8
|
-
|
|
9
|
-
it "has Content-Length on OPTIONS request"
|
|
10
|
-
it "has Content-Length on GET request"
|
|
11
|
-
|
|
12
|
-
context "configuration" do
|
|
13
|
-
let(:app) { Firehose::Rack::Consumer }
|
|
14
|
-
|
|
15
|
-
it "configures long polling timeout" do
|
|
16
|
-
expect(app.new{ |a| a.http_long_poll.timeout = 300 }.http_long_poll.timeout).to eql(300)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
data/spec/lib/rack/ping_spec.rb
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'rack/test'
|
|
3
|
-
require 'async_rack_test'
|
|
4
|
-
|
|
5
|
-
describe Firehose::Rack::Ping, :type => :request do
|
|
6
|
-
include AsyncRackTest::Methods
|
|
7
|
-
let(:app) { Firehose::Rack::Ping.new dummy_redis }
|
|
8
|
-
let(:path) { "/test/path/#{Time.now.to_i}" }
|
|
9
|
-
let(:deferrable) { EM::DefaultDeferrable.new }
|
|
10
|
-
let(:dummy_redis) { double 'redis', :set => deferrable, :get => deferrable, :expire => deferrable }
|
|
11
|
-
|
|
12
|
-
context 'redis is available' do
|
|
13
|
-
before { deferrable.succeed Firehose::Rack::Ping::PingCheck::TEST_VALUE }
|
|
14
|
-
|
|
15
|
-
it "returns 200" do
|
|
16
|
-
ahead path
|
|
17
|
-
expect(last_response.status).to eql(200)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
context 'redis is not available' do
|
|
22
|
-
before { deferrable.fail 'some error' }
|
|
23
|
-
it "returns 500" do
|
|
24
|
-
ahead path
|
|
25
|
-
expect(last_response.status).to eql(500)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
require 'rack/test'
|
|
3
|
-
require 'async_rack_test'
|
|
4
|
-
|
|
5
|
-
describe Firehose::Rack::Publisher, :type => :request do
|
|
6
|
-
include AsyncRackTest::Methods
|
|
7
|
-
let(:app) { Firehose::Rack::Publisher.new }
|
|
8
|
-
let(:path) { "/test/path/#{Time.now.to_i}" }
|
|
9
|
-
let(:deferrable) { EM::DefaultDeferrable.new }
|
|
10
|
-
|
|
11
|
-
context 'publishing is successful' do
|
|
12
|
-
before { deferrable.succeed }
|
|
13
|
-
|
|
14
|
-
it "returns 202" do
|
|
15
|
-
app.stub(:publisher => double('publisher', :publish => deferrable))
|
|
16
|
-
aput path, :body => "some nice little message"
|
|
17
|
-
expect(last_response.status).to eql(202)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
it "has Content-Length of zero" do
|
|
21
|
-
app.stub(:publisher => double('publisher', :publish => deferrable))
|
|
22
|
-
aput path, :body => "some nice little message"
|
|
23
|
-
expect(last_response.headers['Content-Length']).to eql('0')
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
it "parses Cache-Control max-age" do
|
|
27
|
-
body = "howdy dude!"
|
|
28
|
-
ttl = '92'
|
|
29
|
-
|
|
30
|
-
Firehose::Server::Publisher.any_instance.stub(:publish).with(path, body, :ttl => ttl).and_return(deferrable)
|
|
31
|
-
aput path, body, 'HTTP_CACHE_CONTROL' => 'max-age=92'
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
data/spec/lib/server/app_spec.rb
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Rack::App do
|
|
4
|
-
context "transport" do
|
|
5
|
-
# Transport for Firehose::Rack::App class is tested via the spec/integrations suite.
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
context "configuration" do
|
|
9
|
-
let(:app) { Firehose::Rack::App }
|
|
10
|
-
|
|
11
|
-
it "configures long polling timeout" do
|
|
12
|
-
expect(app.new{ |a| a.consumer.http_long_poll.timeout = 300 }.consumer.http_long_poll.timeout).to eql(300)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Server::Channel do
|
|
4
|
-
include EM::TestHelper
|
|
5
|
-
|
|
6
|
-
let(:channel_key) { '/bears/are/mean' }
|
|
7
|
-
let(:channel) { Firehose::Server::Channel.new(channel_key, Firehose::Server.redis.connection, subscriber) }
|
|
8
|
-
let(:subscriber) { Firehose::Server::Subscriber.new(Firehose::Server.redis.connection) }
|
|
9
|
-
let(:message) { 'Raaaarrrrrr!!!!' }
|
|
10
|
-
let(:publisher) { Firehose::Server::Publisher.new }
|
|
11
|
-
|
|
12
|
-
def push_message
|
|
13
|
-
redis_exec 'lpush', "firehose:#{channel_key}:list", message
|
|
14
|
-
redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
describe "#next_messages" do
|
|
18
|
-
it "waits for message if message was not published before subscription" do
|
|
19
|
-
em do
|
|
20
|
-
channel.next_messages.callback do |messages|
|
|
21
|
-
msg = messages.first
|
|
22
|
-
expect(msg.payload).to eql(message)
|
|
23
|
-
expect(msg.sequence).to eql(1)
|
|
24
|
-
em.next_tick { em.stop }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
publisher.publish(channel_key, message)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it "returns the latest message and sequence if no sequence is given" do
|
|
32
|
-
push_message
|
|
33
|
-
|
|
34
|
-
em do
|
|
35
|
-
channel.next_messages.callback do |messages|
|
|
36
|
-
msg = messages.first
|
|
37
|
-
expect(msg.payload).to eql(message)
|
|
38
|
-
expect(msg.sequence).to eql(100)
|
|
39
|
-
|
|
40
|
-
# This must happen _after_ the callback runs in order to pass consistently.
|
|
41
|
-
em.next_tick { em.stop }
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
it "waits for message if most recent sequence is given" do
|
|
47
|
-
push_message
|
|
48
|
-
|
|
49
|
-
em 3 do
|
|
50
|
-
channel.next_messages(100).callback do |messages|
|
|
51
|
-
msg = messages.first.payload
|
|
52
|
-
seq = messages.first.sequence
|
|
53
|
-
expect(msg).to eql(message)
|
|
54
|
-
expect(seq).to eql(101)
|
|
55
|
-
em.next_tick { em.stop }
|
|
56
|
-
end.errback
|
|
57
|
-
|
|
58
|
-
publisher.publish(channel_key, message)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it "waits for message if a future sequence is given" do
|
|
63
|
-
push_message
|
|
64
|
-
|
|
65
|
-
em 3 do
|
|
66
|
-
channel.next_messages(101).callback do |messages|
|
|
67
|
-
msg = messages.first.payload
|
|
68
|
-
seq = messages.first.sequence
|
|
69
|
-
expect(msg).to eql(message)
|
|
70
|
-
expect(seq).to eql(101)
|
|
71
|
-
em.next_tick { em.stop }
|
|
72
|
-
end.errback
|
|
73
|
-
|
|
74
|
-
publisher.publish(channel_key, message)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "immediatly gets a message if message sequence is behind and in list" do
|
|
79
|
-
messages = %w[a b c d e]
|
|
80
|
-
|
|
81
|
-
em 3 do
|
|
82
|
-
publish_messages(messages) do
|
|
83
|
-
channel.next_messages(2).callback do |messages|
|
|
84
|
-
msg = messages.first.payload
|
|
85
|
-
seq = messages.first.sequence
|
|
86
|
-
expect(msg).to eql('c')
|
|
87
|
-
expect(seq).to eql(3)
|
|
88
|
-
|
|
89
|
-
# This must happen _after_ the callback runs in order to pass consistently.
|
|
90
|
-
em.next_tick { em.stop }
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "gets current message if sequence is really far behind in list" do
|
|
97
|
-
messages = ('aa'..'zz').to_a
|
|
98
|
-
|
|
99
|
-
em 3 do
|
|
100
|
-
publish_messages(messages) do
|
|
101
|
-
channel.next_messages(2).callback do |msgs|
|
|
102
|
-
msg = msgs.last.payload
|
|
103
|
-
seq = msgs.last.sequence
|
|
104
|
-
expect(msg).to eql(messages.last)
|
|
105
|
-
expect(seq).to eql(messages.size)
|
|
106
|
-
|
|
107
|
-
# This must happen _after_ the callback runs in order to pass consistently.
|
|
108
|
-
em.next_tick { em.stop }
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
context "a timeout is set" do
|
|
115
|
-
it "times out if message isn't published in time" do
|
|
116
|
-
push_message
|
|
117
|
-
|
|
118
|
-
em 3 do
|
|
119
|
-
channel.next_messages(100, :timeout => 1).callback do |messages|
|
|
120
|
-
msg = messages.first.payload
|
|
121
|
-
seq = messages.first.sequence
|
|
122
|
-
raise 'test failed'
|
|
123
|
-
end.errback do |e|
|
|
124
|
-
expect(e).to eql(:timeout)
|
|
125
|
-
em.next_tick { em.stop }
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
EM::add_timer(2) do
|
|
129
|
-
publisher.publish(channel_key, message)
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
it "does not timeout if message is published in time" do
|
|
135
|
-
push_message
|
|
136
|
-
|
|
137
|
-
em 3 do
|
|
138
|
-
d = channel.next_messages(100, :timeout => 2).callback do |messages|
|
|
139
|
-
msg = messages.first.payload
|
|
140
|
-
seq = messages.first.sequence
|
|
141
|
-
expect(msg).to eql(message)
|
|
142
|
-
expect(seq).to eql(101)
|
|
143
|
-
EM::add_timer(1) do
|
|
144
|
-
em.stop
|
|
145
|
-
end
|
|
146
|
-
end.errback do |e|
|
|
147
|
-
raise 'test failed'
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
d.should_not_receive(:fail)
|
|
151
|
-
|
|
152
|
-
EM::add_timer(1) do
|
|
153
|
-
publisher.publish(channel_key, message)
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
# Publishes the given messages and executes the given block when finished.
|
|
162
|
-
def publish_messages(all_messages)
|
|
163
|
-
publish = Proc.new do |messages_to_publish|
|
|
164
|
-
msg = messages_to_publish.shift
|
|
165
|
-
publisher.publish(channel_key, msg).callback do
|
|
166
|
-
if messages_to_publish.empty?
|
|
167
|
-
# Publishing is done, proceed with the test
|
|
168
|
-
yield
|
|
169
|
-
else
|
|
170
|
-
# Publish the next message
|
|
171
|
-
publish.call messages_to_publish
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
publish.call all_messages.dup
|
|
177
|
-
end
|
|
178
|
-
end
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Server::MessageBuffer do
|
|
4
|
-
it "has 100 DEFAULT_SIZE" do
|
|
5
|
-
expect(Firehose::Server::MessageBuffer::DEFAULT_SIZE).to eql(100)
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
subject { Firehose::Server::MessageBuffer.new(messages, channel_sequence, consumer_sequence) }
|
|
9
|
-
|
|
10
|
-
context "no messages" do
|
|
11
|
-
let(:messages) { [] }
|
|
12
|
-
let(:channel_sequence) { 0 }
|
|
13
|
-
shared_examples "empty channel" do
|
|
14
|
-
it "has empty remaining" do
|
|
15
|
-
expect(subject.remaining_messages).to be_empty
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
context "nil sequence" do
|
|
19
|
-
let(:consumer_sequence) { nil }
|
|
20
|
-
it_behaves_like "empty channel"
|
|
21
|
-
end
|
|
22
|
-
context "0 sequence" do
|
|
23
|
-
let(:consumer_sequence) { 0 }
|
|
24
|
-
it_behaves_like "empty channel"
|
|
25
|
-
end
|
|
26
|
-
context "negative sequence" do
|
|
27
|
-
let(:consumer_sequence) { -1 }
|
|
28
|
-
it_behaves_like "empty channel"
|
|
29
|
-
end
|
|
30
|
-
context "positive sequence" do
|
|
31
|
-
let(:consumer_sequence) { 100 }
|
|
32
|
-
it_behaves_like "empty channel"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
context "has messages (buffer size of 5, at channel sequence 5)" do
|
|
37
|
-
let(:messages) { %w[a b c d e] }
|
|
38
|
-
let(:channel_sequence) { 5 }
|
|
39
|
-
|
|
40
|
-
shared_examples "latest message" do
|
|
41
|
-
it "returns just the latest messages" do
|
|
42
|
-
expect(subject.remaining_messages.map(&:payload)).to eql(["e"])
|
|
43
|
-
end
|
|
44
|
-
it "has the correct sequence" do
|
|
45
|
-
expect(subject.remaining_messages.map(&:sequence)).to eql([5])
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
context "nil sequence" do
|
|
50
|
-
let(:consumer_sequence) { nil }
|
|
51
|
-
it_behaves_like "latest message"
|
|
52
|
-
end
|
|
53
|
-
context "0 sequence" do
|
|
54
|
-
let(:consumer_sequence) { 0 }
|
|
55
|
-
it_behaves_like "latest message"
|
|
56
|
-
end
|
|
57
|
-
context "negative sequence" do
|
|
58
|
-
let(:consumer_sequence) { -1 }
|
|
59
|
-
it_behaves_like "latest message"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
context "running behind" do
|
|
63
|
-
let(:consumer_sequence) { 2 }
|
|
64
|
-
it "has some remaining messages" do
|
|
65
|
-
expect(subject.remaining_messages.map(&:payload)).to eql(%w[c d e])
|
|
66
|
-
end
|
|
67
|
-
it "has the correct sequences" do
|
|
68
|
-
expect(subject.remaining_messages.map(&:sequence)).to eql((3..5).to_a)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
context "caught up" do
|
|
73
|
-
let(:consumer_sequence) { 5 }
|
|
74
|
-
it "has no remaining messages" do
|
|
75
|
-
expect(subject.remaining_messages).to be_empty
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
context "ahead" do
|
|
79
|
-
let(:consumer_sequence) { 10 }
|
|
80
|
-
it "has no remaining messages" do
|
|
81
|
-
expect(subject.remaining_messages).to be_empty
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
context "has messages (buffer size of 5, at channel sequence 10)" do
|
|
86
|
-
let(:messages) { %w[f g h i j] }
|
|
87
|
-
let(:channel_sequence) { 10 }
|
|
88
|
-
|
|
89
|
-
shared_examples "latest message" do
|
|
90
|
-
it "returns just the latest messages" do
|
|
91
|
-
expect(subject.remaining_messages.map(&:payload)).to eql(["j"])
|
|
92
|
-
end
|
|
93
|
-
it "has the correct sequence" do
|
|
94
|
-
expect(subject.remaining_messages.map(&:sequence)).to eql([10])
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
context "nil sequence" do
|
|
99
|
-
let(:consumer_sequence) { nil }
|
|
100
|
-
it_behaves_like "latest message"
|
|
101
|
-
end
|
|
102
|
-
context "0 sequence" do
|
|
103
|
-
let(:consumer_sequence) { 0 }
|
|
104
|
-
it_behaves_like "latest message"
|
|
105
|
-
end
|
|
106
|
-
context "negative sequence" do
|
|
107
|
-
let(:consumer_sequence) { -1 }
|
|
108
|
-
it_behaves_like "latest message"
|
|
109
|
-
end
|
|
110
|
-
context "underwater" do
|
|
111
|
-
let(:consumer_sequence) { 2 }
|
|
112
|
-
it_behaves_like "latest message"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
context "almost underwater" do
|
|
116
|
-
let(:consumer_sequence) { 5 }
|
|
117
|
-
it "has all remaining messages" do
|
|
118
|
-
expect(subject.remaining_messages.map(&:payload)).to eql(%w[f g h i j])
|
|
119
|
-
end
|
|
120
|
-
it "has the correct sequences" do
|
|
121
|
-
expect(subject.remaining_messages.map(&:sequence)).to eql((6..10).to_a)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
context "running behind" do
|
|
126
|
-
let(:consumer_sequence) { 7 }
|
|
127
|
-
it "has some remaining messages" do
|
|
128
|
-
expect(subject.remaining_messages.map(&:payload)).to eql(%w[h i j])
|
|
129
|
-
end
|
|
130
|
-
it "has the correct sequences" do
|
|
131
|
-
expect(subject.remaining_messages.map(&:sequence)).to eql((8..10).to_a)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
context "caught up" do
|
|
136
|
-
let(:consumer_sequence) { 10 }
|
|
137
|
-
it "has no remaining messages" do
|
|
138
|
-
expect(subject.remaining_messages).to be_empty
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
context "ahead" do
|
|
142
|
-
let(:consumer_sequence) { 15 }
|
|
143
|
-
it "has no remaining messages" do
|
|
144
|
-
expect(subject.remaining_messages).to be_empty
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe Firehose::Server::Publisher do
|
|
4
|
-
include EM::TestHelper
|
|
5
|
-
|
|
6
|
-
let(:publisher) { Firehose::Server::Publisher.new }
|
|
7
|
-
let(:channel_key) { "/firehose/publisher/test/#{Time.now.to_i}" }
|
|
8
|
-
let(:message) { "howdy friends!" }
|
|
9
|
-
|
|
10
|
-
it "has 1 day TTL" do
|
|
11
|
-
expect(Firehose::Server::Publisher::TTL).to eql(86400)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
describe "#publish" do
|
|
15
|
-
it "publishes message change" do
|
|
16
|
-
em do
|
|
17
|
-
hiredis = Firehose::Server.redis.connection.pubsub
|
|
18
|
-
hiredis.subscribe "firehose:channel_updates"
|
|
19
|
-
hiredis.on(:message) {|_, msg|
|
|
20
|
-
expect(msg).to eql("#{channel_key}\n1\n#{message}")
|
|
21
|
-
em.next_tick { em.stop }
|
|
22
|
-
}
|
|
23
|
-
Firehose::Server::Publisher.new.publish channel_key, message
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
"\"'\r\t\n!@\#$%^&*()[]\v\f\a\b\e{}/=?+\\|".each_char do |char|
|
|
28
|
-
it "publishes messages with the '#{char.inspect}' character" do
|
|
29
|
-
msg = [char, message, char].join
|
|
30
|
-
em 1 do
|
|
31
|
-
Firehose::Server::Publisher.new.publish(channel_key, msg).callback { em.stop }
|
|
32
|
-
end
|
|
33
|
-
expect(redis_exec('lpop', "firehose:#{channel_key}:list")).to eql(msg)
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it "adds message to list" do
|
|
38
|
-
em do
|
|
39
|
-
Firehose::Server::Publisher.new.publish(channel_key, message).callback { em.stop }
|
|
40
|
-
end
|
|
41
|
-
expect(redis_exec('lpop', "firehose:#{channel_key}:list")).to eql(message)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it "limits list to DEFAULT_SIZE messages" do
|
|
45
|
-
em do
|
|
46
|
-
Firehose::Server::MessageBuffer::DEFAULT_SIZE.times do |n|
|
|
47
|
-
publisher.publish(channel_key, message)
|
|
48
|
-
end
|
|
49
|
-
publisher.publish(channel_key, message).callback { em.stop }
|
|
50
|
-
end
|
|
51
|
-
expect(redis_exec('llen', "firehose:#{channel_key}:list")).to eql(Firehose::Server::MessageBuffer::DEFAULT_SIZE)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
it "limits message list to a custom buffer size" do
|
|
55
|
-
buffer_size = rand(100)
|
|
56
|
-
em do
|
|
57
|
-
Firehose::Server::MessageBuffer::DEFAULT_SIZE.times do |n|
|
|
58
|
-
publisher.publish(channel_key, message)
|
|
59
|
-
end
|
|
60
|
-
publisher.publish(channel_key, message, buffer_size: buffer_size).callback { em.stop }
|
|
61
|
-
end
|
|
62
|
-
redis_exec('llen', "firehose:#{channel_key}:list").should == buffer_size
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "increments sequence" do
|
|
66
|
-
sequence_key = "firehose:#{channel_key}:sequence"
|
|
67
|
-
|
|
68
|
-
@done_counter = 0
|
|
69
|
-
expect(redis_exec('get', sequence_key)).to be_nil
|
|
70
|
-
em do
|
|
71
|
-
publisher.publish(channel_key, message).callback { @done_counter += 1; em.next_tick { em.stop } if @done_counter > 1 }
|
|
72
|
-
publisher.publish(channel_key, message).callback { @done_counter += 1; em.next_tick { em.stop } if @done_counter > 1 }
|
|
73
|
-
end
|
|
74
|
-
expect(redis_exec('get', sequence_key).to_i).to eql(2)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it "sets expiry on sequence and list keys" do
|
|
78
|
-
ttl = 78 # Why 78? Why not!
|
|
79
|
-
|
|
80
|
-
em do
|
|
81
|
-
publisher.publish(channel_key, message, :ttl => 78).callback do
|
|
82
|
-
# Allow for 1 second of slippage/delay
|
|
83
|
-
expect(redis_exec('TTL', "firehose:#{channel_key}:sequence")).to be > (ttl- 1)
|
|
84
|
-
expect(redis_exec('TTL', "firehose:#{channel_key}:list")).to be > (ttl - 1)
|
|
85
|
-
em.stop
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
end
|
|
91
|
-
end
|