rack-berater 0.1.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3475519672f1074d749de982b36d3a2d0d15a468d38d35b91e0f237de02ba2f8
4
- data.tar.gz: fd08c05ccc694adc8ca540a2468956144c0d180611dd4622070e15aa8dd120ef
3
+ metadata.gz: d9e1b9ca0c696948b6d7ec973e1855c71a73fe9ca59d0833107e57295d2d9f21
4
+ data.tar.gz: fcbbeef6465c6f7cefa0c110401e4c80d6462ea3adc69a7a1e3975e19e9f8355
5
5
  SHA512:
6
- metadata.gz: 88428422463a296369f8a2f141c74dadc90f132e2d2072c3505bf33012e344668277e23146125cf43bca55a7d6ec83da0871d6e5b23684a72f3875e48f5d43d7
7
- data.tar.gz: be42f6faa19469ccbfed7995f02b81533051b828e688779d8b5986be6a83174baf71c8ca399b9fabb419766a59fbd8a0c366eefdee977865d7c215970e815527
6
+ metadata.gz: 6d00a14d8136ac96cac236c4bb2ff55c98871dc32cb85b30d8800b4784526cb27c53e54979703f554cb1698ea99093ea77584222dab213f4cbbbe6aeba1b2e71
7
+ data.tar.gz: 03b030982e003903461f929853138f7c4dcbffbfbcccb2aba1335ffac6e2aa9aeb0b265c4c13d3ebc6f1e04ef4acc1c942606c9d57f44cf2003c3a46e42b8c64
@@ -0,0 +1,75 @@
1
+ require 'rack'
2
+
3
+ module Rack
4
+ class Berater
5
+ class Prioritizer
6
+ ENV_KEY = 'berater_priority'
7
+ HEADER = 'X-Berater-Priority'
8
+
9
+ def initialize(app, options = {})
10
+ @app = app
11
+ @header = options[:header] || HEADER
12
+ end
13
+
14
+ def call(env)
15
+ priority = env[@header] || env["HTTP_#{@header.upcase.tr('-', '_')}"]
16
+
17
+ if priority
18
+ set_priority(priority)
19
+ return @app.call(env)
20
+ end
21
+
22
+ cache_key = cache_key_for(env)
23
+ cached_priority = cache_get(cache_key)
24
+
25
+ if cached_priority
26
+ set_priority(cached_priority)
27
+ end
28
+
29
+ @app.call(env).tap do |status, headers, body|
30
+ app_priority = headers.delete(@header) if headers
31
+
32
+ if app_priority && app_priority != cached_priority
33
+ # update cache for next time
34
+ cache_set(cache_key, app_priority)
35
+ end
36
+ end
37
+ ensure
38
+ Thread.current[ENV_KEY] = nil
39
+ end
40
+
41
+ def self.current_priority
42
+ Thread.current[ENV_KEY]
43
+ end
44
+
45
+ protected
46
+
47
+ def set_priority(priority)
48
+ Thread.current[ENV_KEY] = priority
49
+ end
50
+
51
+ def cache_key_for(env)
52
+ [
53
+ env[Rack::REQUEST_METHOD].downcase,
54
+
55
+ # normalize RESTful paths
56
+ env['PATH_INFO'].gsub(%r{/[0-9]+(/|$)}, '/x\1'),
57
+ ].join(':')
58
+ end
59
+
60
+ @@cache = {}
61
+ def cache_get(key)
62
+ synchronize { @@cache[key] }
63
+ end
64
+
65
+ def cache_set(key, priority)
66
+ synchronize { @@cache[key] = priority }
67
+ end
68
+
69
+ @@lock = Thread::Mutex.new
70
+ def synchronize(&block)
71
+ @@lock.synchronize(&block)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,17 @@
1
+ require 'action_controller/metal'
2
+ require 'action_dispatch'
3
+
4
+ module Rack
5
+ class Berater
6
+ class RailsPrioritizer < Prioritizer
7
+ def cache_key_for(env)
8
+ Rails.application.routes.recognize_path(
9
+ env[Rack::PATH_INFO],
10
+ method: env[Rack::REQUEST_METHOD],
11
+ ).values_at(:controller, :action).compact.join('#')
12
+ rescue ActionController::RoutingError
13
+ super
14
+ end
15
+ end
16
+ end
17
+ end
@@ -3,7 +3,11 @@ require 'rails/railtie'
3
3
  module Rack
4
4
  class Berater
5
5
  class Railtie < Rails::Railtie
6
- initializer "rack.berater.initializer" do |app|
6
+ initializer 'rack.berater' do |app|
7
+ if ::Berater.middleware.include?(::Berater::Middleware::LoadShedder)
8
+ app.middleware.use Rack::Berater::RailsPrioritizer
9
+ end
10
+
7
11
  app.middleware.use Rack::Berater
8
12
  end
9
13
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Berater
3
- VERSION = "0.1.0"
3
+ VERSION = "0.3.2"
4
4
  end
5
5
  end
data/lib/rack/berater.rb CHANGED
@@ -1,13 +1,15 @@
1
- require "berater"
2
- require "rack"
3
- require "rack/berater/version"
4
- require "set"
1
+ require 'berater'
2
+ require 'rack'
3
+ require 'rack/berater/version'
4
+ require 'set'
5
5
 
6
6
  module Rack
7
7
  class Berater
8
- autoload :Railtie, "rack/berater/railtie"
8
+ autoload :Prioritizer, 'rack/berater/prioritizer'
9
+ autoload :RailsPrioritizer, 'rack/berater/rails_prioritizer'
10
+ autoload :Railtie, 'rack/berater/railtie'
9
11
 
10
- ERROR_TYPES = Set.new([ ::Berater::Overloaded ])
12
+ ERRORS = Set[ ::Berater::Overloaded ]
11
13
 
12
14
  def initialize(app, options = {})
13
15
  @app = app
@@ -27,23 +29,23 @@ module Rack
27
29
  when String
28
30
  options[:body]
29
31
  else
30
- raise ArgumentError, "invalid :body option: #{options[:body]}"
32
+ raise ArgumentError, 'invalid :body option: #{options[:body]}'
31
33
  end
32
34
 
33
35
  # configure headers
34
36
  if @options[:body]
35
- @options[:headers][Rack::CONTENT_TYPE] = "text/plain"
37
+ @options[:headers][Rack::CONTENT_TYPE] = 'text/plain'
36
38
  end
37
39
  @options[:headers].update(options.fetch(:headers, {}))
38
40
  end
39
41
 
40
42
  def call(env)
41
43
  if enabled?(env)
42
- @limiter.limit { @app.call(env) }
44
+ limit(env) { @app.call(env) }
43
45
  else
44
46
  @app.call(env)
45
47
  end
46
- rescue *ERROR_TYPES => e
48
+ rescue *ERRORS => e
47
49
  [
48
50
  @options[:status_code],
49
51
  @options[:headers],
@@ -57,5 +59,10 @@ module Rack
57
59
  return false unless @limiter
58
60
  @enabled.nil? ? true : @enabled.call(env)
59
61
  end
62
+
63
+ def limit(env, &block)
64
+ limiter = @limiter.respond_to?(:call) ? @limiter.call(env) : @limiter
65
+ limiter.limit(&block)
66
+ end
60
67
  end
61
68
  end
data/lib/rack-berater.rb CHANGED
@@ -1 +1 @@
1
- require "rack/berater"
1
+ require 'rack/berater'
data/spec/limiter_spec.rb CHANGED
@@ -9,86 +9,108 @@ describe Rack::Berater do
9
9
  Rack::Builder.new do
10
10
  use Rack::Lint
11
11
  run (lambda do |env|
12
- [200, {"Content-Type" => "text/plain"}, ["OK"]]
12
+ [200, {'Content-Type' => 'text/plain'}, ['OK']]
13
13
  end)
14
14
  end
15
15
  end
16
- let(:response) { get "/" }
16
+ let(:response) { get '/' }
17
17
 
18
- shared_examples "works nominally" do
19
- it "has the correct status code" do
18
+ shared_examples 'works nominally' do
19
+ it 'has the correct status code' do
20
20
  expect(response.status).to eq 200
21
21
  end
22
22
 
23
- it "has the correct headers" do
23
+ it 'has the correct headers' do
24
24
  expect(response.headers).to eq({
25
- "Content-Type" => "text/plain",
26
- "Content-Length" => "2",
25
+ 'Content-Type' => 'text/plain',
26
+ 'Content-Length' => '2',
27
27
  })
28
28
  end
29
29
 
30
- it "has the correct body" do
31
- expect(response.body).to eq "OK"
30
+ it 'has the correct body' do
31
+ expect(response.body).to eq 'OK'
32
32
  end
33
33
  end
34
34
 
35
- context "without a limiter" do
35
+ context 'without a limiter' do
36
36
  before { Berater.test_mode = :fail }
37
37
 
38
- include_examples "works nominally"
38
+ include_examples 'works nominally'
39
39
  end
40
40
 
41
- describe "limiter option" do
42
- let(:limiter) { ::Berater::Unlimiter.new }
41
+ describe 'limiter option' do
42
+ context 'when limiter is a limiter' do
43
+ let(:limiter) { ::Berater::Unlimiter.new }
44
+
45
+ include_examples 'works nominally'
46
+
47
+ it 'calls the limiter' do
48
+ expect(limiter).to receive(:limit).and_call_original
49
+ response
50
+ end
43
51
 
44
- include_examples "works nominally"
52
+ context 'when operating beyond limits' do
53
+ before { Berater.test_mode = :fail }
45
54
 
46
- it "calls the limiter" do
47
- expect(limiter).to receive(:limit).and_call_original
48
- response
55
+ it 'returns an error' do
56
+ expect(response.status).to eq 429
57
+ end
58
+ end
49
59
  end
50
60
 
51
- context "when operating beyond limits" do
52
- before { Berater.test_mode = :fail }
61
+ context 'when limiter is a proc' do
62
+ let(:limiter_instance) { ::Berater::Unlimiter.new }
63
+ let(:limiter) { Proc.new { limiter_instance } }
53
64
 
54
- it "returns an error" do
55
- expect(response.status).to eq 429
65
+ include_examples 'works nominally'
66
+
67
+ it 'calls the proc with env' do
68
+ expect(limiter).to receive(:call).with(Hash).and_call_original
69
+ response
70
+ end
71
+
72
+ context 'when operating beyond limits' do
73
+ before { Berater.test_mode = :fail }
74
+
75
+ it 'returns an error' do
76
+ expect(response.status).to eq 429
77
+ end
56
78
  end
57
79
  end
58
80
  end
59
81
 
60
- describe "enabled? option" do
82
+ describe 'enabled? option' do
61
83
  after { expect(response.status).to eq 200 }
62
84
 
63
85
  let(:enabled?) { double }
64
86
 
65
- context "when there is a limiter" do
87
+ context 'when there is a limiter' do
66
88
  let(:limiter) { ::Berater::Unlimiter.new }
67
89
 
68
- it "should be called with the env hash" do
90
+ it 'should be called with the env hash' do
69
91
  expect(enabled?).to receive(:call) do |env|
70
92
  expect(env).to be_a Hash
71
- expect(Rack::Request.new(env).path).to eq "/"
93
+ expect(Rack::Request.new(env).path).to eq '/'
72
94
  end
73
95
  end
74
96
 
75
- context "when enabled" do
76
- it "should call the limiter" do
97
+ context 'when enabled' do
98
+ it 'should call the limiter' do
77
99
  expect(enabled?).to receive(:call).and_return(true)
78
100
  expect(limiter).to receive(:limit).and_call_original
79
101
  end
80
102
  end
81
103
 
82
- context "when disabled" do
83
- it "should not call the limiter" do
104
+ context 'when disabled' do
105
+ it 'should not call the limiter' do
84
106
  expect(enabled?).to receive(:call).and_return(false)
85
107
  expect(limiter).not_to receive(:limit)
86
108
  end
87
109
  end
88
110
  end
89
111
 
90
- context "when there is no limiter" do
91
- it "should not call enabled?" do
112
+ context 'when there is no limiter' do
113
+ it 'should not call enabled?' do
92
114
  expect(enabled?).not_to receive(:call)
93
115
  end
94
116
  end
@@ -0,0 +1,218 @@
1
+ describe Rack::Berater::Prioritizer do
2
+ after do
3
+ cache.clear
4
+ Thread.current[described_class::ENV_KEY] = nil
5
+ end
6
+
7
+ let(:cache) { described_class.class_variable_get(:@@cache) }
8
+
9
+ describe '#call' do
10
+ subject { described_class.new(app) }
11
+
12
+ let(:app) { ->(*) { [200, {'Content-Type' => 'text/plain'}, ['OK']] } }
13
+ let(:cache_key) { subject.send(:cache_key_for, env) }
14
+ let(:env) { Rack::MockRequest.env_for('/') }
15
+
16
+ specify 'sanity check' do
17
+ expect(app).to receive(:call).and_call_original
18
+
19
+ Rack::Lint.new(subject).call(env)
20
+ end
21
+
22
+ it 'checks the cache' do
23
+ is_expected.to receive(:cache_get)
24
+
25
+ subject.call(env)
26
+ end
27
+
28
+ context 'with a cached priority' do
29
+ before do
30
+ allow(subject).to receive(:cache_get).with(cache_key).and_return(priority)
31
+ end
32
+
33
+ let(:priority) { '3' }
34
+
35
+ after { subject.call(env) }
36
+
37
+ it 'sets the priority accordingly' do
38
+ is_expected.to receive(:set_priority).with(priority)
39
+ end
40
+
41
+ it 'updates the global priority during the request' do
42
+ expect(app).to receive(:call) do
43
+ expect(described_class.current_priority).to eq priority
44
+ end
45
+ end
46
+
47
+ it 'resets the priority after the request completes' do
48
+ subject.call(env)
49
+ expect(described_class.current_priority).to be nil
50
+ end
51
+ end
52
+
53
+ context 'with an incoming priority header' do
54
+ let(:env) do
55
+ Rack::MockRequest.env_for(
56
+ '/',
57
+ described_class::HEADER => priority,
58
+ )
59
+ end
60
+ let(:priority) { '2' }
61
+
62
+ after { subject.call(env) }
63
+
64
+ it 'uses the header' do
65
+ is_expected.to receive(:set_priority).with(priority)
66
+ end
67
+
68
+ it 'ignores any cached value' do
69
+ allow(subject).to receive(:cache_get).with(cache_key).and_return('123')
70
+ is_expected.to receive(:set_priority).with(priority)
71
+ end
72
+
73
+ it 'resets the priority after the request completes' do
74
+ subject.call(env)
75
+ expect(described_class.current_priority).to be nil
76
+ end
77
+ end
78
+
79
+ context 'when the app returns a priority header' do
80
+ let(:app) do
81
+ ->(*) { [200, { described_class::HEADER => priority }, ['OK']] }
82
+ end
83
+
84
+ let(:priority) { '5' }
85
+
86
+ after { subject.call(env) }
87
+
88
+ it 'caches the priority' do
89
+ is_expected.to receive(:cache_set).with(cache_key, priority)
90
+ end
91
+
92
+ it 'removes the header' do
93
+ _, headers, _ = subject.call(env)
94
+ expect(headers).not_to include described_class::HEADER
95
+ end
96
+
97
+ it 'updates the cache when a different priority is returned' do
98
+ expect(subject).to receive(:cache_get).and_return('123')
99
+ is_expected.to receive(:cache_set).with(cache_key, priority)
100
+ end
101
+
102
+ it 'does not update the cache when the same priority is returned' do
103
+ expect(subject).to receive(:cache_get).and_return(priority)
104
+ is_expected.not_to receive(:cache_set)
105
+ end
106
+ end
107
+
108
+ it 'does not update the cache when no priority is returned' do
109
+ is_expected.not_to receive(:cache_set)
110
+ subject.call(env)
111
+ end
112
+ end
113
+
114
+ describe '#cache_key_for' do
115
+ subject{ described_class.new(nil).send(:cache_key_for, env) }
116
+
117
+ context 'with a basic env' do
118
+ let(:env) { Rack::MockRequest.env_for('/') }
119
+
120
+ it 'combines the verb and path' do
121
+ is_expected.to match %r{get:/$}
122
+ end
123
+ end
124
+
125
+ context 'with a different verb' do
126
+ let(:env) { Rack::MockRequest.env_for('/', method: 'PUT') }
127
+
128
+ it 'combines the verb and path' do
129
+ is_expected.to match %r{put:/$}
130
+ end
131
+ end
132
+
133
+ context 'with a RESTful path' do
134
+ let(:env) { Rack::MockRequest.env_for('/user/123') }
135
+
136
+ it 'normalizes the id' do
137
+ is_expected.to match %r{get:/user/x$}
138
+ end
139
+ end
140
+
141
+ context 'with a RESTful path and trailing slash' do
142
+ let(:env) { Rack::MockRequest.env_for('/user/123/') }
143
+
144
+ it 'normalizes the id and keeps the trailing slash' do
145
+ is_expected.to match %r{get:/user/x/$}
146
+ end
147
+ end
148
+
149
+ context 'with a very RESTful path' do
150
+ let(:env) { Rack::MockRequest.env_for('/user/123/friend/456') }
151
+
152
+ it 'normalizes both ids' do
153
+ is_expected.to match %r{get:/user/x/friend/x$}
154
+ end
155
+ end
156
+ end
157
+
158
+ context 'as Rack middleware' do
159
+ def call(path = '/')
160
+ get(path).body
161
+ end
162
+
163
+ let(:app) do
164
+ headers = {
165
+ 'Content-Type' => 'text/plain',
166
+ described_class::HEADER => app_priority,
167
+ }.compact
168
+
169
+ Rack::Builder.new do
170
+ use Rack::Lint
171
+ use Rack::Berater::Prioritizer
172
+
173
+ run (lambda do |env|
174
+ [200, headers, [ Rack::Berater::Prioritizer.current_priority.to_s ]]
175
+ end)
176
+ end
177
+ end
178
+
179
+ let(:app_priority) { nil }
180
+
181
+ it 'starts empty' do
182
+ expect(call).to be_empty
183
+ end
184
+
185
+ it 'parses incoming priority header' do
186
+ header described_class::HEADER, '7'
187
+
188
+ expect(call).to eq '7'
189
+ end
190
+
191
+ context 'when app returns a priority header' do
192
+ let(:app_priority) { '8' }
193
+
194
+ it 'parses the priority returned from the app' do
195
+ expect(call).to be_empty
196
+ expect(cache.values).to include app_priority
197
+ end
198
+
199
+ it 'uses the cached priority for subsequent calls' do
200
+ expect(call).to be_empty
201
+ expect(call).to eq app_priority
202
+ end
203
+ end
204
+
205
+ # context 'when two different endpoints are called' do
206
+ # fit 'parses and caches each priority' do
207
+ # @app_priority = '6'
208
+ # expect(call('/six')).to be_empty
209
+
210
+ # expect(call('/six')).to eq '6'
211
+
212
+ # @app_priority = '9'
213
+ # expect(call('/nine')).to be_empty
214
+ # expect(call('/nine')).to '9'
215
+ # end
216
+ # end
217
+ end
218
+ end
@@ -0,0 +1,95 @@
1
+ require 'rspec/rails'
2
+
3
+ describe Rack::Berater::RailsPrioritizer do
4
+ before do
5
+ class EchoController < ActionController::Base
6
+ def index
7
+ render plain: Rack::Berater::Prioritizer.current_priority
8
+ end
9
+
10
+ def six
11
+ response.set_header(Rack::Berater::Prioritizer::HEADER, '6')
12
+ index
13
+ end
14
+
15
+ def nine
16
+ response.set_header(Rack::Berater::Prioritizer::HEADER, '9')
17
+ index
18
+ end
19
+ end
20
+
21
+ Rails.application = Class.new(Rails::Application) do
22
+ config.eager_load = false
23
+ config.hosts.clear # disable hostname filtering
24
+ # config.logger = ActiveSupport::Logger.new($stdout)
25
+ end
26
+ Rails.application.middleware.use described_class
27
+ Rails.initialize!
28
+
29
+ Rails.application.routes.draw do
30
+ get '/' => 'echo#index'
31
+ get '/six' => 'echo#six'
32
+ post '/nine' => 'echo#nine'
33
+ end
34
+ end
35
+
36
+ let(:app) { Rails.application }
37
+ let(:middleware) { described_class.new(app) }
38
+
39
+ after do
40
+ cache.clear
41
+ Thread.current[described_class::ENV_KEY] = nil
42
+ Rails.application = nil
43
+ end
44
+
45
+ let(:cache) { described_class.class_variable_get(:@@cache) }
46
+
47
+ describe '#cache_key_for' do
48
+ subject { described_class.new(app).method(:cache_key_for) }
49
+
50
+ it 'uses the controller and action name' do
51
+ expect(
52
+ subject.call(Rack::MockRequest.env_for('/'))
53
+ ).to match /echo#index/
54
+
55
+ expect(
56
+ subject.call(Rack::MockRequest.env_for('/six'))
57
+ ).to match /echo#six/
58
+
59
+ expect(
60
+ subject.call(Rack::MockRequest.env_for('/nine', method: 'POST'))
61
+ ).to match /echo#nine/
62
+ end
63
+
64
+ it 'falls back to Rack style names' do
65
+ expect(
66
+ subject.call(Rack::MockRequest.env_for('/nine'))
67
+ ).to match %r{get:/nine}
68
+ end
69
+ end
70
+
71
+ context 'when a priority header is sent' do
72
+ before { header described_class::HEADER, priority }
73
+
74
+ let(:priority) { '6' }
75
+
76
+ it 'sets the priority' do
77
+ expect(get('/six').body).to eq priority
78
+ end
79
+ end
80
+
81
+ context 'when the app returns a priority' do
82
+ it 'does not know the first time the controller is called' do
83
+ expect(get('/six').body).to be_empty
84
+ expect(post('/nine').body).to be_empty
85
+ end
86
+
87
+ it 'caches the repsonses for the second time' do
88
+ expect(get('/six').body).to be_empty
89
+ expect(post('/nine').body).to be_empty
90
+
91
+ expect(get('/six').body).to eq '6'
92
+ expect(post('/nine').body).to eq '9'
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,16 @@
1
+ require 'rails'
2
+ require 'rack/berater/railtie'
3
+
4
+ RSpec.describe Rack::Berater::Railtie do
5
+ subject { Rails.initialize! }
6
+
7
+ before do
8
+ Rails.application = Class.new(Rails::Application) do
9
+ config.eager_load = false
10
+ end
11
+ end
12
+
13
+ it 'adds middleware automatically' do
14
+ expect(subject.middleware).to include(Rack::Berater)
15
+ end
16
+ end
data/spec/rescuer_spec.rb CHANGED
@@ -4,34 +4,34 @@ describe Rack::Berater do
4
4
  use Rack::Lint
5
5
  run (lambda do |env|
6
6
  Berater::Unlimiter() do
7
- [200, {"Content-Type" => "text/plain"}, ["OK"]]
7
+ [200, {'Content-Type' => 'text/plain'}, ['OK']]
8
8
  end
9
9
  end)
10
10
  end
11
11
  end
12
- let(:response) { get "/" }
12
+ let(:response) { get '/' }
13
13
 
14
- shared_examples "works nominally" do
15
- it "has the correct status code" do
14
+ shared_examples 'works nominally' do
15
+ it 'has the correct status code' do
16
16
  expect(response.status).to eq 200
17
17
  end
18
18
 
19
- it "has the correct headers" do
19
+ it 'has the correct headers' do
20
20
  expect(response.headers).to eq({
21
- "Content-Type" => "text/plain",
22
- "Content-Length" => "2",
21
+ 'Content-Type' => 'text/plain',
22
+ 'Content-Length' => '2',
23
23
  })
24
24
  end
25
25
 
26
- it "has the correct body" do
27
- expect(response.body).to eq "OK"
26
+ it 'has the correct body' do
27
+ expect(response.body).to eq 'OK'
28
28
  end
29
29
  end
30
30
 
31
- context "without middleware" do
32
- include_examples "works nominally"
31
+ context 'without middleware' do
32
+ include_examples 'works nominally'
33
33
 
34
- it "does not catch limit errors" do
34
+ it 'does not catch limit errors' do
35
35
  Berater.test_mode = :fail
36
36
  expect {
37
37
  response
@@ -39,105 +39,115 @@ describe Rack::Berater do
39
39
  end
40
40
  end
41
41
 
42
- context "with middleware using default settings" do
42
+ context 'with middleware using default settings' do
43
43
  before { app.use described_class }
44
44
 
45
- include_examples "works nominally"
45
+ include_examples 'works nominally'
46
46
 
47
- it "catches and transforms limit errors" do
47
+ it 'catches and transforms limit errors' do
48
48
  Berater.test_mode = :fail
49
49
  expect(response.status).to eq 429
50
- expect(response.body).to eq "Too Many Requests"
50
+ expect(response.body).to eq 'Too Many Requests'
51
51
  end
52
52
  end
53
53
 
54
- context "with middleware using custom settings" do
54
+ context 'with middleware using custom settings' do
55
55
  before do
56
56
  app.use described_class, options
57
57
  Berater.test_mode = :fail
58
58
  end
59
59
 
60
- context "with a custom body" do
61
- context "with body nil" do
60
+ context 'with a custom body' do
61
+ context 'with body nil' do
62
62
  let(:options) { { body: nil } }
63
63
 
64
- it "should default to sending a body" do
65
- expect(response.body).to eq "Too Many Requests"
64
+ it 'falls back to the default' do
65
+ expect(response.body).to eq 'Too Many Requests'
66
66
  end
67
67
  end
68
68
 
69
- context "with body disabled" do
69
+ context 'with body disabled' do
70
70
  let(:options) { { body: false } }
71
71
 
72
- it "should not send a body" do
72
+ it 'should not send a body' do
73
73
  expect(response.body).to be_empty
74
74
  end
75
75
 
76
- it "should not send the Content-Type header" do
76
+ it 'should not send the Content-Type header' do
77
77
  expect(response.headers.keys).not_to include(Rack::CONTENT_TYPE)
78
78
  end
79
79
  end
80
80
 
81
- context "with a string" do
82
- let(:body) { "none shall pass!" }
81
+ context 'with a string' do
82
+ let(:body) { 'none shall pass!' }
83
83
  let(:options) { { body: body } }
84
84
 
85
- it "should send the custom string" do
85
+ it 'should send the custom string' do
86
86
  expect(response.body).to eq body
87
87
  end
88
88
  end
89
+
90
+ context 'with an erroneous value' do
91
+ let(:options) { { body: 123 } }
92
+
93
+ it 'should raise an error' do
94
+ expect {
95
+ response
96
+ }.to raise_error(ArgumentError)
97
+ end
98
+ end
89
99
  end
90
100
 
91
- context "with custom headers" do
92
- context "with an extra header" do
93
- let(:options) { { headers: { Rack::CACHE_CONTROL => "no-cache" } } }
101
+ context 'with custom headers' do
102
+ context 'with an extra header' do
103
+ let(:options) { { headers: { Rack::CACHE_CONTROL => 'no-cache' } } }
94
104
 
95
- it "should contain the default headers" do
105
+ it 'should contain the default headers' do
96
106
  expect(response.headers.keys).to include(Rack::CONTENT_TYPE)
97
107
  end
98
108
 
99
- it "should also contain the custom header" do
109
+ it 'should also contain the custom header' do
100
110
  expect(response.headers).to include(options[:headers])
101
111
  end
102
112
  end
103
113
 
104
- context "with a new content type" do
105
- let(:options) { { headers: { Rack::CONTENT_TYPE => "application/json" } } }
114
+ context 'with a new content type' do
115
+ let(:options) { { headers: { Rack::CONTENT_TYPE => 'application/json' } } }
106
116
 
107
- it "should override the Content-Type header" do
117
+ it 'should override the Content-Type header' do
108
118
  expect(response.headers).to include(options[:headers])
109
119
  end
110
120
  end
111
121
  end
112
122
 
113
- context "with custom status code" do
123
+ context 'with custom status code' do
114
124
  let(:options) { { status_code: 503 } }
115
125
 
116
- it "catches and transforms limit errors" do
126
+ it 'catches and transforms limit errors' do
117
127
  expect(response.status).to eq 503
118
- expect(response.body).to eq "Service Unavailable"
128
+ expect(response.body).to eq 'Service Unavailable'
119
129
  end
120
130
  end
121
131
  end
122
132
 
123
- context "with custom error type" do
133
+ context 'with custom error type' do
124
134
  before do
125
135
  app.use described_class
126
136
  expect(Berater::Limiter).to receive(:new).and_raise(IOError)
127
137
  end
128
138
 
129
- it "normally crashes the app" do
139
+ it 'normally crashes the app' do
130
140
  expect { response }.to raise_error(IOError)
131
141
  end
132
142
 
133
- context "when an error type is registered with middleware" do
143
+ context 'when an error type is registered with middleware' do
134
144
  around do |example|
135
- Rack::Berater::ERROR_TYPES << IOError
145
+ Rack::Berater::ERRORS << IOError
136
146
  example.run
137
- Rack::Berater::ERROR_TYPES.delete(IOError)
147
+ Rack::Berater::ERRORS.delete(IOError)
138
148
  end
139
149
 
140
- it "catches and transforms limit errors" do
150
+ it 'catches and transforms limit errors' do
141
151
  expect(response.status).to eq 429
142
152
  end
143
153
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-berater
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-03 00:00:00.000000000 Z
11
+ date: 2021-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: berater
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rspec
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,20 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
97
125
  - !ruby/object:Gem::Dependency
98
126
  name: simplecov
99
127
  requirement: !ruby/object:Gem::Requirement
@@ -116,9 +144,14 @@ extra_rdoc_files: []
116
144
  files:
117
145
  - lib/rack-berater.rb
118
146
  - lib/rack/berater.rb
147
+ - lib/rack/berater/prioritizer.rb
148
+ - lib/rack/berater/rails_prioritizer.rb
119
149
  - lib/rack/berater/railtie.rb
120
150
  - lib/rack/berater/version.rb
121
151
  - spec/limiter_spec.rb
152
+ - spec/prioritizer_spec.rb
153
+ - spec/rails_prioritizer_spec.rb
154
+ - spec/railtie_spec.rb
122
155
  - spec/rescuer_spec.rb
123
156
  homepage: https://github.com/dpep/rack-berater
124
157
  licenses:
@@ -139,10 +172,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
172
  - !ruby/object:Gem::Version
140
173
  version: '0'
141
174
  requirements: []
142
- rubygems_version: 3.1.4
175
+ rubygems_version: 3.1.6
143
176
  signing_key:
144
177
  specification_version: 4
145
178
  summary: Rack::Berater
146
179
  test_files:
180
+ - spec/rails_prioritizer_spec.rb
181
+ - spec/railtie_spec.rb
147
182
  - spec/rescuer_spec.rb
183
+ - spec/prioritizer_spec.rb
148
184
  - spec/limiter_spec.rb