diff --git a/tests/conftest.py b/tests/conftest.py index f516935..aefd054 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,7 @@ from routes.reports import router as reports_router from services.importers import ImportIngestionService from services.unit_of_work import UnitOfWork from services.session import AuthSession, SessionTokens +from tests.utils.security import random_password, random_token @pytest.fixture() @@ -85,7 +86,7 @@ def app(session_factory: sessionmaker) -> FastAPI: user = User( email="test-superuser@example.com", username="test-superuser", - password_hash=User.hash_password("test-password"), + password_hash=User.hash_password(random_password()), is_active=True, is_superuser=True, ) @@ -93,8 +94,12 @@ def app(session_factory: sessionmaker) -> FastAPI: user = uow.users.get(user.id, with_roles=True) def _override_auth_session(request: Request) -> AuthSession: - session = AuthSession(tokens=SessionTokens( - access_token="test", refresh_token="test")) + session = AuthSession( + tokens=SessionTokens( + access_token=random_token(), + refresh_token=random_token(), + ) + ) session.user = user request.state.auth_session = session return session diff --git a/tests/test_auth_repositories.py b/tests/test_auth_repositories.py index 9c656db..1a81fc5 100644 --- a/tests/test_auth_repositories.py +++ b/tests/test_auth_repositories.py @@ -15,6 +15,7 @@ from services.repositories import ( ensure_default_roles, ) from services.unit_of_work import UnitOfWork +from tests.utils.security import random_password @pytest.fixture() @@ -60,7 +61,7 @@ def test_user_repository_assign_and_revoke_role(session: Session) -> None: user = User( email="user@example.com", username="user", - password_hash=User.hash_password("secret"), + password_hash=User.hash_password(random_password()), ) user_repo.create(user) @@ -84,12 +85,14 @@ def test_default_role_and_admin_helpers(session: Session) -> None: assert {role.name for role in roles} == { "admin", "project_manager", "analyst", "viewer"} + admin_password = random_password() + ensure_admin_user( user_repo, role_repo, email="admin@example.com", username="admin", - password="SecurePass1!", + password=admin_password, ) admin = user_repo.get_by_email("admin@example.com", with_roles=True) @@ -103,7 +106,7 @@ def test_default_role_and_admin_helpers(session: Session) -> None: role_repo, email="admin@example.com", username="admin", - password="SecurePass1!", + password=admin_password, ) admin_again = user_repo.get_by_email("admin@example.com", with_roles=True) assert admin_again is not None @@ -125,7 +128,7 @@ def test_unit_of_work_exposes_auth_repositories(engine) -> None: uow.ensure_admin_user( email="uow-admin@example.com", username="uow-admin", - password="AnotherSecret1!", + password=random_password(), ) admin = uow.users.get_by_email( diff --git a/tests/test_auth_routes.py b/tests/test_auth_routes.py index d2ddbdf..6260cdc 100644 --- a/tests/test_auth_routes.py +++ b/tests/test_auth_routes.py @@ -14,6 +14,10 @@ from models import Role, User, UserRole from dependencies import get_auth_session, require_current_user from services.security import hash_password from services.session import AuthSession, SessionTokens +from tests.conftest import app +from tests.utils.security import random_password, random_token + +COOKIE_SOURCE = "cookie" @pytest.fixture() @@ -40,13 +44,14 @@ class TestRegistrationFlow: client: TestClient, db_session: Session, ) -> None: + password = random_password() response = client.post( "/register", data={ "username": "newuser", "email": "newuser@example.com", - "password": "ComplexP@ss1", - "confirm_password": "ComplexP@ss1", + "password": password, + "confirm_password": password, }, follow_redirects=False, ) @@ -78,13 +83,14 @@ class TestRegistrationFlow: self, client: TestClient, ) -> None: + password = random_password() first = client.post( "/register", data={ "username": "existing", "email": "existing@example.com", - "password": "ComplexP@ss1", - "confirm_password": "ComplexP@ss1", + "password": password, + "confirm_password": password, }, follow_redirects=False, ) @@ -95,8 +101,8 @@ class TestRegistrationFlow: data={ "username": "existing", "email": "existing@example.com", - "password": "ComplexP@ss1", - "confirm_password": "ComplexP@ss1", + "password": password, + "confirm_password": password, }, follow_redirects=False, ) @@ -111,7 +117,7 @@ class TestLoginFlow: client: TestClient, db_session: Session, ) -> None: - password = "MySecur3Pass!" + password = random_password() user = User( email="login@example.com", username="loginuser", @@ -153,10 +159,11 @@ class TestPasswordResetFlow: client: TestClient, db_session: Session, ) -> None: + old_password = random_password() user = User( email="reset@example.com", username="resetuser", - password_hash=hash_password("OldP@ssword1"), + password_hash=hash_password(old_password), is_active=True, ) db_session.add(user) @@ -179,12 +186,13 @@ class TestPasswordResetFlow: form_response = client.get(reset_location) assert form_response.status_code == 200 + new_password = random_password() submit_response = client.post( "/reset-password", data={ "token": token, - "password": "N3wP@ssword!", - "confirm_password": "N3wP@ssword!", + "password": new_password, + "confirm_password": new_password, }, follow_redirects=False, ) @@ -193,7 +201,7 @@ class TestPasswordResetFlow: assert "reset=1" in (submit_response.headers.get("location") or "") db_session.refresh(user) - assert user.verify_password("N3wP@ssword!") + assert user.verify_password(new_password) def test_password_reset_with_unknown_email_shows_generic_message( self, @@ -213,10 +221,11 @@ class TestPasswordResetFlow: client: TestClient, db_session: Session, ) -> None: + original_password = random_password() user = User( email="mismatch@example.com", username="mismatch", - password_hash=hash_password("OldP@ssword1"), + password_hash=hash_password(original_password), is_active=True, ) db_session.add(user) @@ -234,8 +243,8 @@ class TestPasswordResetFlow: "/reset-password", data={ "token": token, - "password": "NewPass123!", - "confirm_password": "Different123!", + "password": random_password(), + "confirm_password": random_password(), }, follow_redirects=False, ) @@ -250,10 +259,11 @@ class TestLogoutFlow: client: TestClient, db_session: Session, ) -> None: + logout_password = random_password() user = User( email="logout@example.com", username="logoutuser", - password_hash=hash_password("SecureP@ss1"), + password_hash=hash_password(logout_password), is_active=True, ) db_session.add(user) @@ -261,9 +271,9 @@ class TestLogoutFlow: session = AuthSession( tokens=SessionTokens( - access_token="access-token", - refresh_token="refresh-token", - access_token_source="cookie", + access_token=random_token(), + refresh_token=random_token(), + access_token_source=COOKIE_SOURCE, ), user=user, ) @@ -313,7 +323,7 @@ class TestLoginFlowEndToEnd: def test_login_success_redirects_to_dashboard_and_sets_session( self, client: TestClient, db_session: Session ) -> None: - password = "TestP@ss123" + password = random_password() user = User( email="e2e@example.com", username="e2euser", @@ -369,10 +379,11 @@ class TestLoginFlowEndToEnd: app.dependency_overrides.pop(get_auth_session, None) def test_login_inactive_user_shows_error(self, client: TestClient, db_session: Session) -> None: + password = random_password() user = User( email="inactive@example.com", username="inactiveuser", - password_hash=hash_password("TestP@ss123"), + password_hash=hash_password(password), is_active=False, ) db_session.add(user) @@ -384,7 +395,7 @@ class TestLoginFlowEndToEnd: try: response = client.post( "/login", - data={"username": "inactiveuser", "password": "TestP@ss123"}, + data={"username": "inactiveuser", "password": password}, follow_redirects=False, ) assert response.status_code == 400 diff --git a/tests/test_authorization_integration.py b/tests/test_authorization_integration.py index 825b414..bb03602 100644 --- a/tests/test_authorization_integration.py +++ b/tests/test_authorization_integration.py @@ -9,6 +9,7 @@ from fastapi import Request, status from dependencies import get_auth_session from models import MiningOperationType, Project, User from services.session import AuthSession, SessionTokens +from tests.utils.security import random_password, random_token @pytest.fixture() @@ -23,8 +24,12 @@ def auth_session_context(client): if user is None: session = AuthSession.anonymous() else: - session = AuthSession(tokens=SessionTokens( - access_token="token", refresh_token="refresh")) + session = AuthSession( + tokens=SessionTokens( + access_token=random_token(), + refresh_token=random_token(), + ) + ) session.user = user request.state.auth_session = session return session @@ -48,7 +53,7 @@ def _create_user(uow, *, roles: tuple[str, ...] = (), is_superuser: bool = False user = User( email=f"{_unique('user')}@example.com", username=_unique('user'), - password_hash=User.hash_password("password"), + password_hash=User.hash_password(random_password()), is_active=True, is_superuser=is_superuser, ) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index e0aa898..e5f5e3c 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,7 +1,6 @@ from __future__ import annotations from collections.abc import Callable - from typing import Any import pytest @@ -20,6 +19,7 @@ from services.bootstrap import ( ) from services.pricing import PricingMetadata from services.unit_of_work import UnitOfWork +from tests.utils.security import random_password @pytest.fixture() @@ -46,7 +46,7 @@ def _settings(**overrides: Any) -> AdminBootstrapSettings: defaults: dict[str, Any] = { "email": "admin@example.com", "username": "admin", - "password": "changeme", + "password": random_password(), "roles": ("admin", "viewer"), "force_reset": False, } @@ -103,11 +103,13 @@ def test_bootstrap_is_idempotent(unit_of_work_factory: Callable[[], UnitOfWork]) def test_bootstrap_respects_force_reset(unit_of_work_factory: Callable[[], UnitOfWork]) -> None: - base_settings = _settings(password="initial") + base_password = random_password() + base_settings = _settings(password=base_password) bootstrap_admin(settings=base_settings, unit_of_work_factory=unit_of_work_factory) - rotated_settings = _settings(password="rotated", force_reset=True) + rotated_password = random_password() + rotated_settings = _settings(password=rotated_password, force_reset=True) _, admin_result = bootstrap_admin( settings=rotated_settings, unit_of_work_factory=unit_of_work_factory, @@ -121,7 +123,7 @@ def test_bootstrap_respects_force_reset(unit_of_work_factory: Callable[[], UnitO assert users_repo is not None user = users_repo.get_by_email(rotated_settings.email) assert user is not None - assert user.verify_password("rotated") + assert user.verify_password(rotated_password) def test_bootstrap_pricing_creates_defaults(unit_of_work_factory: Callable[[], UnitOfWork]) -> None: diff --git a/tests/test_dependencies_guards.py b/tests/test_dependencies_guards.py index 40439f8..552264e 100644 --- a/tests/test_dependencies_guards.py +++ b/tests/test_dependencies_guards.py @@ -16,6 +16,7 @@ from dependencies import ( ) from models import Project, Scenario, User from services.session import AuthSession, SessionTokens +from tests.utils.security import random_password, random_token @pytest.fixture() @@ -40,7 +41,7 @@ def _create_user( user = User( email=f"{_unique('user')}@example.com", username=_unique('user'), - password_hash=User.hash_password("password"), + password_hash=User.hash_password(random_password()), is_active=is_active, is_superuser=is_superuser, ) @@ -76,11 +77,12 @@ def test_require_current_user_returns_authenticated_user(): user = User( email="user@example.com", username="user", - password_hash=User.hash_password("password"), + password_hash=User.hash_password(random_password()), is_active=True, ) - session = AuthSession(tokens=SessionTokens( - access_token="token", refresh_token=None)) + session = AuthSession( + tokens=SessionTokens(access_token=random_token(), refresh_token=None) + ) session.user = user result = require_current_user(session=session) @@ -99,10 +101,11 @@ def test_require_current_user_raises_when_session_missing(): def test_require_authenticated_user_blocks_inactive_users(): + inactive_password = random_password() user = User( email="user@example.com", username="user", - password_hash=User.hash_password("password"), + password_hash=User.hash_password(inactive_password), is_active=False, ) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/utils/security.py b/tests/utils/security.py new file mode 100644 index 0000000..e3335f0 --- /dev/null +++ b/tests/utils/security.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +import secrets + + +def random_password() -> str: + """Return a strong test password that satisfies complexity checks.""" + return f"Aa1!{secrets.token_urlsafe(16)}" + + +def random_token() -> str: + """Return a random token suitable for test session data.""" + return secrets.token_urlsafe(24)