* Coverage for rate limit headers * Move rate limit headers methods to concern * Move throttle check to condition on before_action * Move match_data variable into method * Move utc timestamp to separate method * Move header setting into smaller methods * specs cleanupclosed-social-glitch-2
@ -0,0 +1,57 @@ | |||
# frozen_string_literal: true | |||
module RateLimitHeaders | |||
extend ActiveSupport::Concern | |||
included do | |||
before_action :set_rate_limit_headers, if: :rate_limited_request? | |||
end | |||
private | |||
def set_rate_limit_headers | |||
apply_header_limit | |||
apply_header_remaining | |||
apply_header_reset | |||
end | |||
def rate_limited_request? | |||
!request.env['rack.attack.throttle_data'].nil? | |||
end | |||
def apply_header_limit | |||
response.headers['X-RateLimit-Limit'] = rate_limit_limit | |||
end | |||
def rate_limit_limit | |||
api_throttle_data[:limit].to_s | |||
end | |||
def apply_header_remaining | |||
response.headers['X-RateLimit-Remaining'] = rate_limit_remaining | |||
end | |||
def rate_limit_remaining | |||
(api_throttle_data[:limit] - api_throttle_data[:count]).to_s | |||
end | |||
def apply_header_reset | |||
response.headers['X-RateLimit-Reset'] = rate_limit_reset | |||
end | |||
def rate_limit_reset | |||
(request_time + reset_period_offset).iso8601(6) | |||
end | |||
def api_throttle_data | |||
request.env['rack.attack.throttle_data']['api'] | |||
end | |||
def request_time | |||
@_request_time ||= Time.now.utc | |||
end | |||
def reset_period_offset | |||
api_throttle_data[:period] - request_time.to_i % api_throttle_data[:period] | |||
end | |||
end |
@ -0,0 +1,56 @@ | |||
# frozen_string_literal: true | |||
require 'rails_helper' | |||
describe ApplicationController do | |||
controller do | |||
include RateLimitHeaders | |||
def show | |||
head 200 | |||
end | |||
end | |||
before do | |||
routes.draw { get 'show' => 'anonymous#show' } | |||
end | |||
describe 'rate limiting' do | |||
context 'throttling is off' do | |||
before do | |||
request.env['rack.attack.throttle_data'] = nil | |||
end | |||
it 'does not apply rate limiting' do | |||
get 'show' | |||
expect(response.headers['X-RateLimit-Limit']).to be_nil | |||
expect(response.headers['X-RateLimit-Remaining']).to be_nil | |||
expect(response.headers['X-RateLimit-Reset']).to be_nil | |||
end | |||
end | |||
context 'throttling is on' do | |||
let(:start_time) { DateTime.new(2017, 1, 1, 12, 0, 0).utc } | |||
before do | |||
request.env['rack.attack.throttle_data'] = { 'api' => { limit: 100, count: 20, period: 10 } } | |||
travel_to start_time do | |||
get 'show' | |||
end | |||
end | |||
it 'applies rate limiting limit header' do | |||
expect(response.headers['X-RateLimit-Limit']).to eq '100' | |||
end | |||
it 'applies rate limiting remaining header' do | |||
expect(response.headers['X-RateLimit-Remaining']).to eq '80' | |||
end | |||
it 'applies rate limiting reset header' do | |||
expect(response.headers['X-RateLimit-Reset']).to eq (start_time + 10.seconds).iso8601(6) | |||
end | |||
end | |||
end | |||
end |