firehose 1.2.20 → 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +29 -0
  3. data/.dockerignore +2 -0
  4. data/.gitignore +3 -1
  5. data/.rubocop.yml +1156 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +3 -7
  8. data/CHANGELOG.md +15 -0
  9. data/Dockerfile +11 -0
  10. data/Gemfile +4 -2
  11. data/Procfile.dev +0 -1
  12. data/README.md +66 -8
  13. data/Rakefile +43 -32
  14. data/coffeelint.json +129 -0
  15. data/docker-compose.yml +17 -0
  16. data/firehose.gemspec +5 -9
  17. data/karma.config.coffee +89 -0
  18. data/lib/assets/javascripts/firehose.js.coffee +1 -2
  19. data/lib/assets/javascripts/firehose/consumer.js.coffee +18 -2
  20. data/lib/assets/javascripts/firehose/core.js.coffee +2 -1
  21. data/lib/assets/javascripts/firehose/long_poll.js.coffee +69 -8
  22. data/lib/assets/javascripts/firehose/multiplexed_consumer.js.coffee +74 -0
  23. data/lib/assets/javascripts/firehose/transport.js.coffee +4 -2
  24. data/lib/assets/javascripts/firehose/web_socket.js.coffee +51 -5
  25. data/lib/firehose/cli.rb +2 -1
  26. data/lib/firehose/client/producer.rb +10 -4
  27. data/lib/firehose/rack/consumer.rb +39 -0
  28. data/lib/firehose/rack/consumer/http_long_poll.rb +118 -45
  29. data/lib/firehose/rack/consumer/web_socket.rb +133 -28
  30. data/lib/firehose/rack/ping.rb +1 -1
  31. data/lib/firehose/rack/publisher.rb +10 -4
  32. data/lib/firehose/server.rb +9 -9
  33. data/lib/firehose/server/channel.rb +23 -31
  34. data/lib/firehose/server/message_buffer.rb +59 -0
  35. data/lib/firehose/server/publisher.rb +16 -17
  36. data/lib/firehose/server/redis.rb +32 -0
  37. data/lib/firehose/server/subscriber.rb +7 -7
  38. data/lib/firehose/version.rb +2 -2
  39. data/package.json +14 -2
  40. data/spec/integrations/shared_examples.rb +89 -7
  41. data/spec/javascripts/firehose/multiplexed_consumer_spec.coffee +72 -0
  42. data/spec/javascripts/firehose/transport_spec.coffee +0 -2
  43. data/spec/javascripts/firehose/websocket_spec.coffee +2 -0
  44. data/spec/javascripts/helpers/spec_helper.js +1 -0
  45. data/spec/javascripts/support/jquery-1.11.1.js +10308 -0
  46. data/{lib/assets/javascripts/vendor → spec/javascripts/support}/json2.js +0 -0
  47. data/spec/javascripts/support/spec_helper.coffee +3 -0
  48. data/spec/lib/assets_spec.rb +8 -8
  49. data/spec/lib/client/producer_spec.rb +14 -14
  50. data/spec/lib/firehose_spec.rb +2 -2
  51. data/spec/lib/rack/consumer/http_long_poll_spec.rb +21 -3
  52. data/spec/lib/rack/consumer_spec.rb +4 -4
  53. data/spec/lib/rack/ping_spec.rb +4 -4
  54. data/spec/lib/rack/publisher_spec.rb +5 -5
  55. data/spec/lib/server/app_spec.rb +2 -2
  56. data/spec/lib/server/channel_spec.rb +58 -44
  57. data/spec/lib/server/message_buffer_spec.rb +148 -0
  58. data/spec/lib/server/publisher_spec.rb +29 -22
  59. data/spec/lib/server/redis_spec.rb +13 -0
  60. data/spec/lib/server/subscriber_spec.rb +14 -13
  61. data/spec/spec_helper.rb +8 -1
  62. metadata +34 -95
  63. data/.rbenv-version +0 -1
  64. data/Guardfile +0 -31
  65. data/config/evergreen.rb +0 -9
@@ -0,0 +1,3 @@
1
+ #= require ./jquery-1.11.1
2
+ #= require ./json2
3
+ #= require ./sinon-1.7.3
@@ -3,19 +3,19 @@ require 'sprockets'
3
3
 
4
4
  describe Firehose::Assets do
5
5
  describe ".path" do
6
- it "should have root path" do
7
- Firehose::Assets.path('poop').should == File.expand_path('../../../lib/assets/poop', __FILE__)
6
+ it "has root path" do
7
+ expect(Firehose::Assets.path('poop')).to eql(File.expand_path('../../../lib/assets/poop', __FILE__))
8
8
  end
9
9
 
10
- it "should accept folders" do
11
- Firehose::Assets.path('poop').should == File.join(Firehose::Assets.path, 'poop')
10
+ it "accepts folders" do
11
+ expect(Firehose::Assets.path('poop')).to eql(File.join(Firehose::Assets.path, 'poop'))
12
12
  end
13
13
  end
14
14
 
15
15
  describe "Sprockets.configure" do
16
- it "should configure environment" do
16
+ it "configures environment" do
17
17
  env = Firehose::Assets::Sprockets.configure Sprockets::Environment.new
18
- env.paths.should include(Firehose::Assets.path('javascripts'))
18
+ expect(env.paths).to include(Firehose::Assets.path('javascripts'))
19
19
  end
20
20
  end
21
21
 
@@ -28,8 +28,8 @@ describe Firehose::Assets do
28
28
  "window.Firehose || (window.Firehose = {})"
29
29
  ]
30
30
  end
31
- it "should compile javascript" do
32
- Firehose::Assets::Sprockets.javascript.should include(*js_spot_checks)
31
+ it "compiles javascript" do
32
+ expect(Firehose::Assets::Sprockets.javascript).to include(*js_spot_checks)
33
33
  end
34
34
  end
35
35
  end
@@ -14,58 +14,58 @@ describe Firehose::Client::Producer::Http do
14
14
  Firehose::Client::Producer::Http.adapter = nil
15
15
  end
16
16
 
17
- it "should publish message to channel" do
17
+ it "publishes message to channel" do
18
18
  publish_stub.to_return(:body => "", :status => 202)
19
19
 
20
20
  Firehose::Client::Producer::Http.new.publish(message).to(channel)
21
- WebMock.should have_requested(:put, url).with { |req| req.body == message }
21
+ expect(WebMock).to have_requested(:put, url).with { |req| req.body == message }
22
22
  end
23
23
 
24
24
  context 'prefix is specified in URI' do
25
25
  let(:firehose_uri) {"#{Firehose::URI}/prefix"}
26
26
  let(:url) { "#{firehose_uri}#{channel}"}
27
27
 
28
- it "should publish message to channel" do
28
+ it "publishes message to channel" do
29
29
  publish_stub.to_return(:body => "", :status => 202)
30
30
 
31
31
  Firehose::Client::Producer::Http.new(firehose_uri).publish(message).to(channel)
32
- WebMock.should have_requested(:put, url).with { |req| req.body == message }
32
+ expect(WebMock).to have_requested(:put, url).with { |req| req.body == message }
33
33
  end
34
34
  end
35
35
 
36
- it "should publish message to channel with expiry headers" do
36
+ it "publishes message to channel with expiry headers" do
37
37
  publish_stub.to_return(:body => "", :status => 202)
38
38
  ttl = 20
39
39
 
40
40
  Firehose::Client::Producer::Http.new.publish(message).to(channel, :ttl => ttl)
41
- WebMock.should have_requested(:put, url).with { |req| req.body == message and req.headers['Cache-Control'] == "max-age=#{ttl}" }
41
+ expect(WebMock).to have_requested(:put, url).with { |req| req.body == message and req.headers['Cache-Control'] == "max-age=#{ttl}" }
42
42
  end
43
43
 
44
44
  describe "connection error handling" do
45
- it "should raise PublishError if not 201" do
45
+ it "raises PublishError if not 201" do
46
46
  publish_stub.to_return(:body => "", :status => 500)
47
47
 
48
- lambda{
48
+ expect {
49
49
  Firehose::Client::Producer::Http.new.publish(message).to(channel)
50
- }.should raise_exception(Firehose::Client::Producer::Http::PublishError)
50
+ }.to raise_exception(Firehose::Client::Producer::Http::PublishError)
51
51
  end
52
52
 
53
- it "should use .error_handler if not 201" do
53
+ it "uses .error_handler if not 201" do
54
54
  publish_stub.to_return(:body => "", :status => 500)
55
55
 
56
56
  producer = Firehose::Client::Producer::Http.new
57
57
  producer.on_error do |e|
58
- e.message.should =~ /could not publish.+to/i
58
+ expect(e.message).to match(/could not publish.+to/i)
59
59
  end
60
60
  producer.publish(message).to(channel)
61
61
  end
62
62
 
63
- it "should raise TimeoutError if timed out" do
63
+ it "raises TimeoutError if timed out" do
64
64
  publish_stub.to_timeout
65
65
 
66
- lambda{
66
+ expect {
67
67
  Firehose::Client::Producer::Http.new.publish(message).to(channel)
68
- }.should raise_exception(Firehose::Client::Producer::Http::TimeoutError)
68
+ }.to raise_exception(Firehose::Client::Producer::Http::TimeoutError)
69
69
  end
70
70
  end
71
71
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Firehose do
4
- it "should have 7474 for default port" do
5
- Firehose::URI.to_s.should == '//0.0.0.0:7474'
4
+ it "has 7474 for default port" do
5
+ expect(Firehose::URI.to_s).to eql('//0.0.0.0:7474')
6
6
  end
7
7
  end
@@ -1,12 +1,30 @@
1
1
  require 'spec_helper'
2
+ require 'rack/test'
3
+ require 'async_rack_test'
2
4
 
3
5
  describe Firehose::Rack::Consumer::HttpLongPoll do
6
+ include AsyncRackTest::Methods
7
+ let(:app) { Firehose::Rack::Consumer::HttpLongPoll.new }
8
+
4
9
  context "transport" do
5
10
  # Transport for Firehose::Rack::App class is tested via the spec/integrations suite.
6
11
  end
7
12
  context "configuration" do
8
- it "should have #timeout" do
9
- Firehose::Rack::Consumer::HttpLongPoll.new(200).timeout.should == 200
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")
10
28
  end
11
29
  end
12
- end
30
+ end
@@ -6,14 +6,14 @@ describe Firehose::Rack::Consumer, :type => :request do
6
6
  let(:app) { Firehose::Rack::Consumer.new }
7
7
  let(:path) { "/test/path/#{Time.now.to_i}" }
8
8
 
9
- it "should have Content-Length on OPTIONS request"
10
- it "should have Content-Length on GET request"
9
+ it "has Content-Length on OPTIONS request"
10
+ it "has Content-Length on GET request"
11
11
 
12
12
  context "configuration" do
13
13
  let(:app) { Firehose::Rack::Consumer }
14
14
 
15
- it "should configure long polling timeout" do
16
- app.new{ |a| a.http_long_poll.timeout = 300 }.http_long_poll.timeout.should == 300
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
17
  end
18
18
  end
19
19
  end
@@ -12,17 +12,17 @@ describe Firehose::Rack::Ping, :type => :request do
12
12
  context 'redis is available' do
13
13
  before { deferrable.succeed Firehose::Rack::Ping::PingCheck::TEST_VALUE }
14
14
 
15
- it "should return 200" do
15
+ it "returns 200" do
16
16
  ahead path
17
- last_response.status.should == 200
17
+ expect(last_response.status).to eql(200)
18
18
  end
19
19
  end
20
20
 
21
21
  context 'redis is not available' do
22
22
  before { deferrable.fail 'some error' }
23
- it "should return 500" do
23
+ it "returns 500" do
24
24
  ahead path
25
- last_response.status.should == 500
25
+ expect(last_response.status).to eql(500)
26
26
  end
27
27
  end
28
28
  end
@@ -11,19 +11,19 @@ describe Firehose::Rack::Publisher, :type => :request do
11
11
  context 'publishing is successful' do
12
12
  before { deferrable.succeed }
13
13
 
14
- it "should return 202" do
14
+ it "returns 202" do
15
15
  app.stub(:publisher => double('publisher', :publish => deferrable))
16
16
  aput path, :body => "some nice little message"
17
- last_response.status.should == 202
17
+ expect(last_response.status).to eql(202)
18
18
  end
19
19
 
20
- it "should have Content-Length of zero" do
20
+ it "has Content-Length of zero" do
21
21
  app.stub(:publisher => double('publisher', :publish => deferrable))
22
22
  aput path, :body => "some nice little message"
23
- last_response.headers['Content-Length'].should == '0'
23
+ expect(last_response.headers['Content-Length']).to eql('0')
24
24
  end
25
25
 
26
- it "should parse Cache-Control max-age" do
26
+ it "parses Cache-Control max-age" do
27
27
  body = "howdy dude!"
28
28
  ttl = '92'
29
29
 
@@ -8,8 +8,8 @@ describe Firehose::Rack::App do
8
8
  context "configuration" do
9
9
  let(:app) { Firehose::Rack::App }
10
10
 
11
- it "should configure long polling timeout" do
12
- app.new{ |a| a.consumer.http_long_poll.timeout = 300 }.consumer.http_long_poll.timeout.should == 300
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
13
  end
14
14
  end
15
15
  end
@@ -4,17 +4,23 @@ describe Firehose::Server::Channel do
4
4
  include EM::TestHelper
5
5
 
6
6
  let(:channel_key) { '/bears/are/mean' }
7
- let(:channel) { Firehose::Server::Channel.new(channel_key, EM::Hiredis.connect, subscriber) }
8
- let(:subscriber) { Firehose::Server::Subscriber.new(EM::Hiredis.connect) }
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
9
  let(:message) { 'Raaaarrrrrr!!!!' }
10
10
  let(:publisher) { Firehose::Server::Publisher.new }
11
11
 
12
- describe "#next_message" do
13
- it "should wait for message if message was not published before subscription" do
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
14
19
  em do
15
- channel.next_message.callback do |msg, seq|
16
- msg.should == message
17
- seq.should == 1
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)
18
24
  em.next_tick { em.stop }
19
25
  end
20
26
 
@@ -22,14 +28,14 @@ describe Firehose::Server::Channel do
22
28
  end
23
29
  end
24
30
 
25
- it "should return the latest message and sequence if no sequence is given" do
26
- redis_exec 'lpush', "firehose:#{channel_key}:list", message
27
- redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
31
+ it "returns the latest message and sequence if no sequence is given" do
32
+ push_message
28
33
 
29
34
  em do
30
- channel.next_message.callback do |msg, seq|
31
- msg.should == message
32
- seq.should == 100
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)
33
39
 
34
40
  # This must happen _after_ the callback runs in order to pass consistently.
35
41
  em.next_tick { em.stop }
@@ -37,14 +43,15 @@ describe Firehose::Server::Channel do
37
43
  end
38
44
  end
39
45
 
40
- it "should wait for message if most recent sequence is given" do
41
- redis_exec 'lpush', "firehose:#{channel_key}:list", message
42
- redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
46
+ it "waits for message if most recent sequence is given" do
47
+ push_message
43
48
 
44
49
  em 3 do
45
- channel.next_message(100).callback do |msg, seq|
46
- msg.should == message
47
- seq.should == 101
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)
48
55
  em.next_tick { em.stop }
49
56
  end.errback
50
57
 
@@ -52,14 +59,15 @@ describe Firehose::Server::Channel do
52
59
  end
53
60
  end
54
61
 
55
- it "should wait for message if a future sequence is given" do
56
- redis_exec 'lpush', "firehose:#{channel_key}:list", message
57
- redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
62
+ it "waits for message if a future sequence is given" do
63
+ push_message
58
64
 
59
65
  em 3 do
60
- channel.next_message(101).callback do |msg, seq|
61
- msg.should == message
62
- seq.should == 101
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)
63
71
  em.next_tick { em.stop }
64
72
  end.errback
65
73
 
@@ -67,14 +75,16 @@ describe Firehose::Server::Channel do
67
75
  end
68
76
  end
69
77
 
70
- it "should immediatly get a message if message sequence is behind and in list" do
78
+ it "immediatly gets a message if message sequence is behind and in list" do
71
79
  messages = %w[a b c d e]
72
80
 
73
81
  em 3 do
74
82
  publish_messages(messages) do
75
- channel.next_message(2).callback do |msg, seq|
76
- msg.should == 'c'
77
- seq.should == 3
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)
78
88
 
79
89
  # This must happen _after_ the callback runs in order to pass consistently.
80
90
  em.next_tick { em.stop }
@@ -83,14 +93,16 @@ describe Firehose::Server::Channel do
83
93
  end
84
94
  end
85
95
 
86
- it "should get current message if sequence is really far behind in list" do
96
+ it "gets current message if sequence is really far behind in list" do
87
97
  messages = ('aa'..'zz').to_a
88
98
 
89
99
  em 3 do
90
100
  publish_messages(messages) do
91
- channel.next_message(2).callback do |msg, seq|
92
- msg.should == messages.last
93
- seq.should == messages.size
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)
94
106
 
95
107
  # This must happen _after_ the callback runs in order to pass consistently.
96
108
  em.next_tick { em.stop }
@@ -100,15 +112,16 @@ describe Firehose::Server::Channel do
100
112
  end
101
113
 
102
114
  context "a timeout is set" do
103
- it "should timeout if message isn't published in time" do
104
- redis_exec 'lpush', "firehose:#{channel_key}:list", message
105
- redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
115
+ it "times out if message isn't published in time" do
116
+ push_message
106
117
 
107
118
  em 3 do
108
- channel.next_message(100, :timeout => 1).callback do |msg, seq|
119
+ channel.next_messages(100, :timeout => 1).callback do |messages|
120
+ msg = messages.first.payload
121
+ seq = messages.first.sequence
109
122
  raise 'test failed'
110
123
  end.errback do |e|
111
- e.should == :timeout
124
+ expect(e).to eql(:timeout)
112
125
  em.next_tick { em.stop }
113
126
  end
114
127
 
@@ -118,14 +131,15 @@ describe Firehose::Server::Channel do
118
131
  end
119
132
  end
120
133
 
121
- it "should not timeout if message is published in time" do
122
- redis_exec 'lpush', "firehose:#{channel_key}:list", message
123
- redis_exec 'set', "firehose:#{channel_key}:sequence", '100'
134
+ it "does not timeout if message is published in time" do
135
+ push_message
124
136
 
125
137
  em 3 do
126
- d = channel.next_message(100, :timeout => 2).callback do |msg, seq|
127
- msg.should == message
128
- seq.should == 101
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)
129
143
  EM::add_timer(1) do
130
144
  em.stop
131
145
  end
@@ -0,0 +1,148 @@
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