Files
fast-next-template/backend/tests/core/test_config_security.py
Felipe Cardoso c589b565f0 Add pyproject.toml for consolidated project configuration and replace Black, isort, and Flake8 with Ruff
- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest).
- Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting.
- Updated `requirements.txt` to include Ruff and remove replaced tools.
- Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
2025-11-10 11:55:15 +01:00

151 lines
4.1 KiB
Python

"""
Security tests for configuration validation (app/core/config.py).
Critical security tests covering:
- SECRET_KEY minimum length validation (prevents weak JWT signing keys)
These tests prevent security misconfigurations.
"""
import os
import pytest
from pydantic import ValidationError
class TestSecretKeySecurityValidation:
"""
Test SECRET_KEY security validation (config.py line 109).
Attack Prevention:
Short SECRET_KEYs can be brute-forced, compromising JWT token security.
System must enforce minimum 32-character requirement.
Covers: config.py:109
"""
def test_secret_key_too_short_rejected(self):
"""
Test that SECRET_KEY shorter than 32 characters is rejected.
Security Risk:
Short keys (e.g., "password123") can be brute-forced, allowing
attackers to forge JWT tokens.
Covers line 109.
"""
# Save original SECRET_KEY
original_secret = os.environ.get("SECRET_KEY")
try:
# Try to set a short SECRET_KEY (only 20 characters)
short_key = "a" * 20 # Too short!
os.environ["SECRET_KEY"] = short_key
# Import Settings class fresh (to pick up new env var)
# The ValidationError should be raised during reload when Settings() is instantiated
import importlib
from app.core import config
# Reload will raise ValidationError because Settings() is instantiated at module level
with pytest.raises(ValidationError, match="at least 32 characters"):
importlib.reload(config)
finally:
# Restore original SECRET_KEY
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
# Reload config to restore original settings
import importlib
from app.core import config
importlib.reload(config)
def test_secret_key_exactly_32_characters_accepted(self):
"""
Test that SECRET_KEY with exactly 32 characters is accepted.
Minimum secure length.
"""
original_secret = os.environ.get("SECRET_KEY")
try:
# Set exactly 32-character key
key_32 = "a" * 32
os.environ["SECRET_KEY"] = key_32
import importlib
from app.core import config
importlib.reload(config)
# Should work
settings = config.Settings()
assert len(settings.SECRET_KEY) == 32
finally:
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
import importlib
from app.core import config
importlib.reload(config)
def test_secret_key_long_enough_accepted(self):
"""
Test that SECRET_KEY with 32+ characters is accepted.
Sanity check that valid keys work.
"""
original_secret = os.environ.get("SECRET_KEY")
try:
# Set long key (64 characters)
key_64 = "a" * 64
os.environ["SECRET_KEY"] = key_64
import importlib
from app.core import config
importlib.reload(config)
# Should work
settings = config.Settings()
assert len(settings.SECRET_KEY) >= 32
finally:
if original_secret:
os.environ["SECRET_KEY"] = original_secret
else:
os.environ.pop("SECRET_KEY", None)
import importlib
from app.core import config
importlib.reload(config)
def test_default_secret_key_meets_requirements(self):
"""
Test that the default SECRET_KEY (if no env var) meets requirements.
Ensures our defaults are secure.
"""
from app.core.config import settings
# Current settings should have valid SECRET_KEY
assert len(settings.SECRET_KEY) >= 32, (
"Default SECRET_KEY must be at least 32 chars"
)