Files
sure/test/services/api_rate_limiter_test.rb
RealDiligent c9c484010b fix: use REDIS_URL in ApiRateLimiter for hosted API requests (#2639)
* fix: resolve ApiRateLimiter connecting to wrong Redis instance

Use REDIS_URL for API key rate limiting so hosted deployments share the
configured Redis backend instead of defaulting to localhost.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: align ApiRateLimiter specs with configured redis_url

Use ApiRateLimiter.redis_url for test cleanup and assert the public
redis_url reader instead of inspecting private Redis client internals.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 09:59:28 +02:00

152 lines
4.4 KiB
Ruby

require "test_helper"
class ApiRateLimiterTest < ActiveSupport::TestCase
setup do
@user = users(:family_admin)
# Destroy any existing active API keys for this user
@user.api_keys.active.destroy_all
@api_key = ApiKey.create!(
user: @user,
name: "Rate Limiter Test Key",
scopes: [ "read" ],
display_key: "rate_limiter_test_#{SecureRandom.hex(8)}"
)
@rate_limiter = ApiRateLimiter.new(@api_key)
# Clear any existing rate limit data
clear_rate_limit_data(@api_key)
end
teardown do
# Clean up Redis data after each test
clear_rate_limit_data(@api_key)
end
test "should have default rate limit" do
assert_equal 100, @rate_limiter.rate_limit
end
test "should start with zero request count" do
assert_equal 0, @rate_limiter.current_count
end
test "should not be rate limited initially" do
assert_not @rate_limiter.rate_limit_exceeded?
end
test "should increment request count" do
assert_equal 0, @rate_limiter.current_count
@rate_limiter.increment_request_count!
assert_equal 1, @rate_limiter.current_count
@rate_limiter.increment_request_count!
assert_equal 2, @rate_limiter.current_count
end
test "should be rate limited when exceeding limit" do
# Simulate reaching the rate limit
100.times { @rate_limiter.increment_request_count! }
assert_equal 100, @rate_limiter.current_count
assert @rate_limiter.rate_limit_exceeded?
end
test "should provide correct usage info" do
5.times { @rate_limiter.increment_request_count! }
usage_info = @rate_limiter.usage_info
assert_equal 5, usage_info[:current_count]
assert_equal 100, usage_info[:rate_limit]
assert_equal 95, usage_info[:remaining]
assert_equal :standard, usage_info[:tier]
assert usage_info[:reset_time] > 0
assert usage_info[:reset_time] <= 3600
end
test "should calculate remaining requests correctly" do
10.times { @rate_limiter.increment_request_count! }
usage_info = @rate_limiter.usage_info
assert_equal 90, usage_info[:remaining]
end
test "should have zero remaining when at limit" do
100.times { @rate_limiter.increment_request_count! }
usage_info = @rate_limiter.usage_info
assert_equal 0, usage_info[:remaining]
end
test "should have zero remaining when over limit" do
105.times { @rate_limiter.increment_request_count! }
usage_info = @rate_limiter.usage_info
assert_equal 0, usage_info[:remaining]
end
test "class method usage_for should work without incrementing" do
5.times { @rate_limiter.increment_request_count! }
usage_info = ApiRateLimiter.usage_for(@api_key)
assert_equal 5, usage_info[:current_count]
# Should not increment when just checking usage
usage_info_again = ApiRateLimiter.usage_for(@api_key)
assert_equal 5, usage_info_again[:current_count]
end
test "should handle multiple API keys separately" do
# Create a different user for the second API key
other_user = users(:family_member)
other_api_key = ApiKey.create!(
user: other_user,
name: "Other API Key",
scopes: [ "read_write" ],
display_key: "rate_limiter_other_#{SecureRandom.hex(8)}"
)
other_rate_limiter = ApiRateLimiter.new(other_api_key)
@rate_limiter.increment_request_count!
other_rate_limiter.increment_request_count!
other_rate_limiter.increment_request_count!
assert_equal 1, @rate_limiter.current_count
assert_equal 2, other_rate_limiter.current_count
ensure
clear_rate_limit_data(other_api_key)
other_api_key.destroy
end
test "should calculate reset time correctly" do
reset_time = @rate_limiter.reset_time
# Reset time should be within the current hour
assert reset_time > 0
assert reset_time <= 3600
# Should be roughly the time until the next hour
current_time = Time.current.to_i
next_window = ((current_time / 3600) + 1) * 3600
expected_reset = next_window - current_time
assert_in_delta expected_reset, reset_time, 1
end
test "uses REDIS_URL for redis connection" do
with_env_overrides("REDIS_URL" => "redis://custom-host:6380/3") do
limiter = ApiRateLimiter.new(@api_key)
assert_equal "redis://custom-host:6380/3", limiter.redis_url
end
end
private
def clear_rate_limit_data(api_key)
Redis.new(url: ApiRateLimiter.redis_url).del("api_rate_limit:#{api_key.id}")
end
end