Files
SaaS-PDF/backend/tests/test_rate_limiter.py
Your Name cfbcc8bd79 ميزة: إضافة مكوني ProcedureSelection و StepProgress لأداة مخططات التدفق بصيغة PDF
- تنفيذ مكون ProcedureSelection لتمكين المستخدمين من اختيار الإجراءات من قائمة، وإدارة الاختيارات، ومعالجة الإجراءات المرفوضة.

- إنشاء مكون StepProgress لعرض تقدم معالج متعدد الخطوات بشكل مرئي.

- تعريف أنواع مشتركة للإجراءات، وخطوات التدفق، ورسائل الدردشة في ملف types.ts.

- إضافة اختبارات وحدة لخطافات useFileUpload و useTaskPolling لضمان الأداء السليم ومعالجة الأخطاء.

- تنفيذ اختبارات واجهة برمجة التطبيقات (API) للتحقق من تنسيقات نقاط النهاية وضمان اتساق ربط الواجهة الأمامية بالخلفية.
2026-03-06 17:16:09 +02:00

101 lines
3.7 KiB
Python

"""Tests for rate limiting middleware."""
import pytest
from app import create_app
@pytest.fixture
def rate_limited_app(tmp_path):
"""App with rate limiting ENABLED.
TestingConfig sets RATELIMIT_ENABLED=False so the other 116 tests are
never throttled. Here we force the extension's internal flag back to
True *after* init_app so the decorator limits are enforced.
"""
app = create_app('testing')
app.config.update({
'TESTING': True,
'RATELIMIT_STORAGE_URI': 'memory://',
'UPLOAD_FOLDER': str(tmp_path / 'uploads'),
'OUTPUT_FOLDER': str(tmp_path / 'outputs'),
})
import os
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)
# flask-limiter 3.x returns from init_app immediately when
# RATELIMIT_ENABLED=False (TestingConfig default), so `initialized`
# stays False and no limits are enforced. We override the config key
# and call init_app a SECOND time so the extension fully initialises.
# It is safe to call twice — flask-limiter guards against duplicate
# before_request hook registration via app.extensions["limiter"].
from app.extensions import limiter as _limiter
app.config['RATELIMIT_ENABLED'] = True
_limiter.init_app(app) # second call — now RATELIMIT_ENABLED=True
yield app
# Restore so other tests are unaffected
_limiter.enabled = False
_limiter.initialized = False
@pytest.fixture
def rate_limited_client(rate_limited_app):
return rate_limited_app.test_client()
class TestRateLimiter:
def test_health_endpoint_not_rate_limited(self, client):
"""Health endpoint should handle many rapid requests."""
for _ in range(20):
response = client.get('/api/health')
assert response.status_code == 200
def test_rate_limit_header_present(self, client):
"""Response should include a valid HTTP status code."""
response = client.get('/api/health')
assert response.status_code == 200
class TestRateLimitEnforcement:
"""Verify that per-route rate limits actually trigger (429) when exceeded."""
def test_compress_rate_limit_triggers(self, rate_limited_client):
"""
POST /api/compress/pdf has @limiter.limit("10/minute").
After 10 requests (each returns 400 for missing file, but the limiter
still counts them), the 11th must get 429 Too Many Requests.
"""
blocked = False
for i in range(15):
r = rate_limited_client.post('/api/compress/pdf')
if r.status_code == 429:
blocked = True
break
assert blocked, (
"Expected a 429 Too Many Requests after exceeding 10/minute "
"on /api/compress/pdf"
)
def test_convert_pdf_to_word_rate_limit(self, rate_limited_client):
"""POST /api/convert/pdf-to-word is also rate-limited."""
blocked = False
for _ in range(15):
r = rate_limited_client.post('/api/convert/pdf-to-word')
if r.status_code == 429:
blocked = True
break
assert blocked, "Rate limit not enforced on /api/convert/pdf-to-word"
def test_different_endpoints_have_independent_limits(self, rate_limited_client):
"""
Exhausting the limit on /compress/pdf must not affect /api/health,
which has no rate limit.
"""
# Exhaust compress limit
for _ in range(15):
rate_limited_client.post('/api/compress/pdf')
# Health should still respond normally
r = rate_limited_client.get('/api/health')
assert r.status_code == 200