rack-berater 0.1.0 → 0.3.2

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 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