mirror of
https://github.com/barkeser2002/offline-db.git
synced 2026-09-25 06:00:11 +03:00
- Added endpoint-specific `UserRateThrottle` subclasses (`WatchLogCreateThrottle`, `ReviewCreateThrottle`) to `users/views.py` and `content/api/views.py` that only limit `POST` requests. - Subclassed `TokenObtainPairView` to add a dedicated `AnonRateThrottle` for the login process. - Configured throttling rates in `aniscrap_core/settings.py` (e.g., `login: 5/minute`, `watchlog: 10/minute`, `review: 5/hour`). - Recorded learnings in `.jules/sentinel.md` to establish safe throttling patterns for future mutations. - Updated `.jules/development-plan.md` to reflect the completion of section 1.2 tasks. - Created `users/tests/test_api_rate_limits.py` to ensure HTTP 429 and `Retry-After` headers are correctly returned under excessive loads. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
72 lines
2.9 KiB
Python
72 lines
2.9 KiB
Python
from django.urls import reverse
|
|
from rest_framework.test import APITestCase
|
|
from rest_framework import status
|
|
from users.models import User
|
|
from content.models import Anime, Season, Episode
|
|
from django.core.cache import cache
|
|
import time
|
|
|
|
class APIRateLimitTests(APITestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(username='testuser', password='password123')
|
|
cache.clear() # Clear cache to reset rate limits
|
|
|
|
def test_login_rate_limit(self):
|
|
url = reverse('token_obtain_pair')
|
|
data = {'username': 'testuser', 'password': 'password123'}
|
|
|
|
# login throttle is 5/minute
|
|
for i in range(5):
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
|
|
# 6th should fail
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
|
|
self.assertIn('Retry-After', response.headers)
|
|
|
|
def test_watchlog_create_rate_limit(self):
|
|
self.client.force_authenticate(user=self.user)
|
|
url = reverse('watch-history-list')
|
|
|
|
# watchlog throttle is 10/minute
|
|
for i in range(10):
|
|
# Create a new episode for each log to avoid potential unique constraints or 400s
|
|
anime = Anime.objects.create(title=f"Test Anime {i}")
|
|
season = Season.objects.create(anime=anime, number=1)
|
|
episode = Episode.objects.create(season=season, number=1)
|
|
data = {'episode': episode.id, 'watch_time': 100, 'duration': 1200}
|
|
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
|
|
# 11th should fail
|
|
anime = Anime.objects.create(title=f"Test Anime 11")
|
|
season = Season.objects.create(anime=anime, number=1)
|
|
episode = Episode.objects.create(season=season, number=1)
|
|
data = {'episode': episode.id, 'watch_time': 100, 'duration': 1200}
|
|
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
|
|
self.assertIn('Retry-After', response.headers)
|
|
|
|
def test_review_create_rate_limit(self):
|
|
self.client.force_authenticate(user=self.user)
|
|
url = '/api/content/reviews/'
|
|
|
|
# review throttle is 5/hour
|
|
for i in range(5):
|
|
anime = Anime.objects.create(title=f"Test Review Anime {i}")
|
|
data = {'anime': anime.id, 'rating': 8, 'text': 'Good'}
|
|
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
|
|
# 6th should be throttled
|
|
anime = Anime.objects.create(title=f"Test Review Anime 6")
|
|
data = {'anime': anime.id, 'rating': 8, 'text': 'Good'}
|
|
|
|
response = self.client.post(url, data)
|
|
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
|
|
self.assertIn('Retry-After', response.headers)
|