Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/kernelbot/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,8 +1131,8 @@ async def admin_set_rate_limit(
if mode_category not in ("test", "leaderboard"):
raise HTTPException(status_code=400, detail="mode_category must be 'test' or 'leaderboard'")
max_per_hour = payload.get("max_submissions_per_hour")
if not isinstance(max_per_hour, int) or max_per_hour < 1:
raise HTTPException(status_code=400, detail="max_submissions_per_hour must be a positive integer")
if not isinstance(max_per_hour, int) or max_per_hour < 0:
raise HTTPException(status_code=400, detail="max_submissions_per_hour must be a non-negative integer")
try:
with db_context as db:
result = db.set_rate_limit(leaderboard_name, mode_category, max_per_hour)
Expand Down
28 changes: 28 additions & 0 deletions src/migrations/20260319_01_allow-zero-rate-limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Allow zero-submission rate limits.
"""

from yoyo import step

__depends__ = {"20260318_01_ban-user"}

steps = [
step(
"""
ALTER TABLE leaderboard.rate_limit
DROP CONSTRAINT rate_limit_max_submissions_per_hour_check;

ALTER TABLE leaderboard.rate_limit
ADD CONSTRAINT rate_limit_max_submissions_per_hour_check
CHECK (max_submissions_per_hour >= 0);
""",
"""
ALTER TABLE leaderboard.rate_limit
DROP CONSTRAINT rate_limit_max_submissions_per_hour_check;

ALTER TABLE leaderboard.rate_limit
ADD CONSTRAINT rate_limit_max_submissions_per_hour_check
CHECK (max_submissions_per_hour > 0);
""",
)
]
4 changes: 2 additions & 2 deletions tests/test_admin_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,11 +800,11 @@ def test_set_rate_limit_invalid_category(self, test_client):
assert response.status_code == 400

def test_set_rate_limit_invalid_count(self, test_client):
"""PUT /admin/leaderboards/{name}/rate-limits rejects non-positive count."""
"""PUT /admin/leaderboards/{name}/rate-limits rejects negative count."""
response = test_client.put(
"/admin/leaderboards/test-lb/rate-limits",
headers={"Authorization": "Bearer test_token"},
json={"mode_category": "test", "max_submissions_per_hour": 0},
json={"mode_category": "test", "max_submissions_per_hour": -1},
)
assert response.status_code == 400

Expand Down
10 changes: 10 additions & 0 deletions tests/test_leaderboard_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,16 @@ def test_check_rate_limit_no_config(database, submit_leaderboard):
assert result is None


def test_check_rate_limit_zero_limit_blocks_submissions(database, submit_leaderboard):
"""A zero rate limit keeps a leaderboard visible while rejecting submissions."""
with database as db:
db.set_rate_limit("submit-leaderboard", "leaderboard", 0)
result = db.check_rate_limit("submit-leaderboard", "123", "leaderboard")
assert result["allowed"] is False
assert result["current_count"] == 0
assert result["max_per_hour"] == 0


def test_check_rate_limit_under_limit(database, submit_leaderboard):
"""check_rate_limit allows submissions under the limit."""
with database as db:
Expand Down
Loading