Skip to content

Commit

Permalink
apply comments
Browse files Browse the repository at this point in the history
  • Loading branch information
GCorbel committed Nov 29, 2023
1 parent c1be7ea commit 3bd24c3
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 111 deletions.
40 changes: 9 additions & 31 deletions lib/x/errors/too_many_requests.rb
Original file line number Diff line number Diff line change
@@ -1,50 +1,28 @@
require_relative "client_error"
require_relative "../rate_limit"

module X
class TooManyRequests < ClientError
def limit
response["x-rate-limit-limit"].to_i
end

def remaining
response["x-rate-limit-remaining"].to_i
def rate_limits
@rate_limits ||= limit_names.to_h do |limit|
[limit, RateLimit.new(limit, response)]
end
end

def reset_at
Time.at(response["x-rate-limit-reset"].to_i)
rate_limits.values.select { |limit| limit.remaining.zero? }.map(&:reset_at).max || Time.at(0)
end

def reset_in
[(reset_at - Time.now).ceil, 0].max
end

def all_limits
@all_limits ||= limits.to_h do |limit|
[
limit,
{
"limit" => response["x-#{limit}-limit"].to_i,
"remaining" => response["x-#{limit}-remaining"].to_i,
"reset_at" => Time.at(response["x-#{limit}-reset"].to_i)
}
]
end
end

def next_available_at
all_limits.values.select { |v| (v["remaining"]).zero? }.map { |v| v["reset_at"] }.max
end

def next_available_in
[(next_available_at - Time.now).ceil, 0].max
end

alias_method :retry_after, :next_available_in
alias_method :retry_after, :reset_in

private

def limits
@limits ||= response.to_hash.keys.filter_map { |k| k.match(/x-(.*-limit.*)-limit/)&.captures&.first }
def limit_names
@limit_names ||= response.to_hash.keys.filter_map { |k| k.match(/x-(.*-limit.*)-(\w+)/)&.captures&.uniq&.first }
end
end
end
28 changes: 28 additions & 0 deletions lib/x/rate_limit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module X
class RateLimit
attr_accessor :type, :response

def initialize(type, response)
@type = type
@response = response
end

def limit
response["x-#{type}-limit"].to_i
end

def remaining
response["x-#{type}-remaining"].to_i
end

def reset_at
Time.at(response["x-#{type}-reset"].to_i)
end

def reset_in
[(reset_at - Time.now).ceil, 0].max
end

alias_method :retry_after, :reset_in
end
end
6 changes: 3 additions & 3 deletions test/x/error_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ def test_rate_limit
begin
@client.get("tweets")
rescue TooManyRequests => e
assert_equal 40_000, e.limit
assert_equal 39_999, e.remaining
assert_equal 40_000, e.rate_limits["rate-limit"].limit
assert_equal 39_999, e.rate_limits["rate-limit"].remaining
end
end

Expand All @@ -52,7 +52,7 @@ def test_rate_limit_reset_at
begin
@client.get("tweets")
rescue TooManyRequests => e
assert_equal Time.at(reset_time), e.reset_at
assert_equal Time.at(reset_time), e.rate_limits["rate-limit"].reset_at
end
end
end
Expand Down
38 changes: 38 additions & 0 deletions test/x/rate_limit_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require_relative "../test_helper"

module X
class RateLimitTest < Minitest::Test
cover RateLimit

def setup
Time.stub :now, Time.utc(1983, 11, 24) do
response = {
"x-rate-limit-limit" => "100",
"x-rate-limit-remaining" => "0",
"x-rate-limit-reset" => (Time.now.to_i + 60).to_s
}
@rate_limit = RateLimit.new("rate-limit", response)
end
end

def test_reset_at
Time.stub :now, Time.utc(1983, 11, 24) do
assert_equal Time.at(Time.now.to_i + 60), @rate_limit.reset_at
end
end

def test_reset_in
Time.stub :now, Time.utc(1983, 11, 24) do
assert_equal 60, @rate_limit.reset_in
end
end

def test_reset_in_minimum_value
Time.stub :now, Time.utc(1983, 11, 24) do
@rate_limit.response["x-rate-limit-reset"] = (Time.now.to_i - 60).to_s

assert_equal 0, @rate_limit.reset_in
end
end
end
end
2 changes: 1 addition & 1 deletion test/x/response_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_too_many_requests_with_headers
headers: {"x-rate-limit-remaining" => "0"})
exception = assert_raises(TooManyRequests) { @response_parser.parse(response: response) }

assert_predicate exception.remaining, :zero?
assert_predicate exception.rate_limits["rate-limit"].remaining, :zero?
end

def test_error_with_title_only
Expand Down
97 changes: 21 additions & 76 deletions test/x/too_many_requests_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,13 @@ module X
class TooManyRequestsTest < Minitest::Test
cover TooManyRequests

def add_limit(response, name, limits)
response["x-#{name}-limit"] = limits[:limit].to_s
response["x-#{name}-remaining"] = limits[:remaining].to_s
response["x-#{name}-reset"] = limits[:reset_at].to_i.to_s
end

def setup
Time.stub :now, Time.utc(1983, 11, 24) do
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")

add_limit(response, "rate-limit", limit: 100, remaining: 0, reset_at: Time.now + 60)
add_limit(response, "app-limit-24hour", limit: 200, remaining: 199, reset_at: Time.now + 200)
add_limit(response, "user-limit-24hour", limit: 300, remaining: 299, reset_at: Time.now + 300)
response["x-rate-limit-limit"] = "100"
response["x-rate-limit-remaining"] = "0"
response["x-rate-limit-reset"] = (Time.now + 60).to_i.to_s

@exception = TooManyRequests.new(response: response)
end
Expand All @@ -26,97 +20,48 @@ def test_initialize_with_empty_response
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")
exception = TooManyRequests.new(response: response)

assert_equal 0, exception.limit
assert_equal 0, exception.remaining
assert_equal 0, exception.rate_limits.count
assert_equal Time.at(0).utc, exception.reset_at
assert_equal 0, exception.reset_in
assert_equal "Too Many Requests", @exception.message
end

def test_limit
assert_equal 100, @exception.limit
end

def test_limit_with_header
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")
response["x-rate-limit-limit"] = "100"
exception = TooManyRequests.new(response: response)

assert_equal 100, exception.limit
end

def test_remaining
assert_equal 0, @exception.remaining
assert_equal "Too Many Requests", exception.message
end

def test_remaining_with_header
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")
response["x-rate-limit-remaining"] = "5"
exception = TooManyRequests.new(response: response)

assert_equal 5, exception.remaining
end

def test_reset_at
def test_rate_limits
Time.stub :now, Time.utc(1983, 11, 24) do
assert_equal Time.now + 60, @exception.reset_at
end
end
@exception.response["x-app-limit-24hour-limit"] = "200"
limits = @exception.rate_limits

def test_reset_in
Time.stub :now, Time.utc(1983, 11, 24) do
assert_equal 60, @exception.reset_in
assert_equal 2, limits.count
assert_equal 100, limits["rate-limit"].limit
assert_equal 200, limits["app-limit-24hour"].limit
end
end

def test_retry_after
Time.stub :now, Time.utc(1983, 11, 24) do
assert_equal 60, @exception.retry_after
end
end

def test_all_limits
def test_reset_at
Time.stub :now, Time.utc(1983, 11, 24) do
excepted = {
"rate-limit" => {"limit" => 100, "remaining" => 0, "reset_at" => Time.at(Time.now.to_i + 60)},
"app-limit-24hour" => {"limit" => 200, "remaining" => 199, "reset_at" => Time.at(Time.now.to_i + 200)},
"user-limit-24hour" => {"limit" => 300, "remaining" => 299, "reset_at" => Time.at(Time.now.to_i + 300)}
}
@exception.response["x-app-limit-24hour-remaining"] = "0"
@exception.response["x-app-limit-24hour-reset"] = (Time.now + 200).to_i.to_s

assert_equal excepted, @exception.all_limits
assert_equal Time.at(Time.now.to_i + 200), @exception.reset_at
end
end

def test_next_available_at
def test_reset_in
Time.stub :now, Time.utc(1983, 11, 24) do
@exception.response["x-app-limit-24hour-remaining"] = "0"
@exception.response["x-app-limit-24hour-reset"] = (Time.now + 200).to_i.to_s

assert_equal Time.at(Time.now.to_i + 200), @exception.next_available_at
assert_equal 200, @exception.reset_in
end
end

def test_next_available_in
def test_retry_after
Time.stub :now, Time.utc(1983, 11, 24) do
@exception.response["x-app-limit-24hour-remaining"] = "0"
@exception.response["x-app-limit-24hour-reset"] = (Time.now + 200).to_i.to_s

assert_equal 200, @exception.next_available_in
assert_equal 200, @exception.retry_after
end
end

def test_reset_in_minimum_value
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")
response["x-rate-limit-reset"] = (Time.now - 60).to_i.to_s
exception = TooManyRequests.new(response: response)

assert_equal 0, exception.reset_in
end

def test_reset_in_ceil
response = Net::HTTPTooManyRequests.new("1.1", 429, "Too Many Requests")
response["x-rate-limit-reset"] = (Time.now + 61).to_i.to_s
exception = TooManyRequests.new(response: response)

assert_equal 61, exception.reset_in
end
end
end

0 comments on commit 3bd24c3

Please sign in to comment.