forked from cardosofelipe/fast-next-template
Enhance user management, improve API structure, add database optimizations, and update Docker setup
- Introduced endpoints for user management, including CRUD operations, pagination, and password management. - Added new schema validations for user updates, password strength, pagination, and standardized error responses. - Integrated custom exception handling for a consistent API error experience. - Refined CORS settings: restricted methods and allowed headers, added header exposure, and preflight caching. - Optimized database: added indexes on `is_active` and `is_superuser` fields, updated column types, enforced constraints, and set defaults. - Updated `Dockerfile` to improve security by using a non-root user and adding a health check for the application. - Enhanced tests for database initialization, user operations, and exception handling to ensure better coverage.
This commit is contained in:
223
backend/tests/test_init_db.py
Normal file
223
backend/tests/test_init_db.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# tests/test_init_db.py
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.init_db import init_db
|
||||
from app.models.user import User
|
||||
from app.schemas.users import UserCreate
|
||||
|
||||
|
||||
class TestInitDB:
|
||||
"""Tests for database initialization"""
|
||||
|
||||
def test_init_db_creates_superuser_when_not_exists(self, db_session, monkeypatch):
|
||||
"""Test that init_db creates superuser when it doesn't exist"""
|
||||
# Set environment variables
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_EMAIL", "admin@test.com")
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_PASSWORD", "TestPassword123!")
|
||||
|
||||
# Reload settings to pick up environment variables
|
||||
from app.core import config
|
||||
import importlib
|
||||
importlib.reload(config)
|
||||
from app.core.config import settings
|
||||
|
||||
# Mock user_crud to return None (user doesn't exist)
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
mock_crud.get_by_email.return_value = None
|
||||
|
||||
# Create a mock user to return from create
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
mock_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="admin@test.com",
|
||||
password_hash="hashed",
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
mock_crud.create.return_value = mock_user
|
||||
|
||||
# Call init_db
|
||||
user = init_db(db_session)
|
||||
|
||||
# Verify user was created
|
||||
assert user is not None
|
||||
assert user.email == "admin@test.com"
|
||||
assert user.is_superuser is True
|
||||
mock_crud.create.assert_called_once()
|
||||
|
||||
def test_init_db_returns_existing_superuser(self, db_session, monkeypatch):
|
||||
"""Test that init_db returns existing superuser without creating new one"""
|
||||
# Set environment variables
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_EMAIL", "existing@test.com")
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_PASSWORD", "TestPassword123!")
|
||||
|
||||
# Reload settings
|
||||
from app.core import config
|
||||
import importlib
|
||||
importlib.reload(config)
|
||||
|
||||
# Mock user_crud to return existing user
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
existing_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="existing@test.com",
|
||||
password_hash="hashed",
|
||||
first_name="Existing",
|
||||
last_name="User",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
mock_crud.get_by_email.return_value = existing_user
|
||||
|
||||
# Call init_db
|
||||
user = init_db(db_session)
|
||||
|
||||
# Verify existing user was returned
|
||||
assert user is not None
|
||||
assert user.email == "existing@test.com"
|
||||
# create should NOT be called
|
||||
mock_crud.create.assert_not_called()
|
||||
|
||||
def test_init_db_uses_defaults_when_env_not_set(self, db_session):
|
||||
"""Test that init_db uses default credentials when env vars not set"""
|
||||
# Mock settings to return None for superuser credentials
|
||||
with patch('app.init_db.settings') as mock_settings:
|
||||
mock_settings.FIRST_SUPERUSER_EMAIL = None
|
||||
mock_settings.FIRST_SUPERUSER_PASSWORD = None
|
||||
|
||||
# Mock user_crud
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
mock_crud.get_by_email.return_value = None
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
mock_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="admin@example.com",
|
||||
password_hash="hashed",
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
mock_crud.create.return_value = mock_user
|
||||
|
||||
# Call init_db
|
||||
with patch('app.init_db.logger') as mock_logger:
|
||||
user = init_db(db_session)
|
||||
|
||||
# Verify default email was used
|
||||
mock_crud.get_by_email.assert_called_with(db_session, email="admin@example.com")
|
||||
# Verify warning was logged since credentials not set
|
||||
assert mock_logger.warning.called
|
||||
|
||||
def test_init_db_handles_creation_error(self, db_session, monkeypatch):
|
||||
"""Test that init_db handles errors during user creation"""
|
||||
# Set environment variables
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_EMAIL", "admin@test.com")
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_PASSWORD", "TestPassword123!")
|
||||
|
||||
# Reload settings
|
||||
from app.core import config
|
||||
import importlib
|
||||
importlib.reload(config)
|
||||
|
||||
# Mock user_crud to raise an exception
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
mock_crud.get_by_email.return_value = None
|
||||
mock_crud.create.side_effect = Exception("Database error")
|
||||
|
||||
# Call init_db and expect exception
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
init_db(db_session)
|
||||
|
||||
assert "Database error" in str(exc_info.value)
|
||||
|
||||
def test_init_db_logs_superuser_creation(self, db_session, monkeypatch):
|
||||
"""Test that init_db logs appropriate messages"""
|
||||
# Set environment variables
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_EMAIL", "admin@test.com")
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_PASSWORD", "TestPassword123!")
|
||||
|
||||
# Reload settings
|
||||
from app.core import config
|
||||
import importlib
|
||||
importlib.reload(config)
|
||||
|
||||
# Mock user_crud
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
mock_crud.get_by_email.return_value = None
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
mock_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="admin@test.com",
|
||||
password_hash="hashed",
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
mock_crud.create.return_value = mock_user
|
||||
|
||||
# Call init_db with logger mock
|
||||
with patch('app.init_db.logger') as mock_logger:
|
||||
user = init_db(db_session)
|
||||
|
||||
# Verify info log was called
|
||||
assert mock_logger.info.called
|
||||
info_call_args = str(mock_logger.info.call_args)
|
||||
assert "Created first superuser" in info_call_args
|
||||
|
||||
def test_init_db_logs_existing_user(self, db_session, monkeypatch):
|
||||
"""Test that init_db logs when user already exists"""
|
||||
# Set environment variables
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_EMAIL", "existing@test.com")
|
||||
monkeypatch.setenv("FIRST_SUPERUSER_PASSWORD", "TestPassword123!")
|
||||
|
||||
# Reload settings
|
||||
from app.core import config
|
||||
import importlib
|
||||
importlib.reload(config)
|
||||
|
||||
# Mock user_crud to return existing user
|
||||
with patch('app.init_db.user_crud') as mock_crud:
|
||||
from datetime import datetime, timezone
|
||||
import uuid
|
||||
existing_user = User(
|
||||
id=uuid.uuid4(),
|
||||
email="existing@test.com",
|
||||
password_hash="hashed",
|
||||
first_name="Existing",
|
||||
last_name="User",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
updated_at=datetime.now(timezone.utc)
|
||||
)
|
||||
mock_crud.get_by_email.return_value = existing_user
|
||||
|
||||
# Call init_db with logger mock
|
||||
with patch('app.init_db.logger') as mock_logger:
|
||||
user = init_db(db_session)
|
||||
|
||||
# Verify info log was called
|
||||
assert mock_logger.info.called
|
||||
info_call_args = str(mock_logger.info.call_args)
|
||||
assert "already exists" in info_call_args.lower()
|
||||
Reference in New Issue
Block a user