from __future__ import annotations from collections.abc import Iterator import pytest from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker from config.database import Base from models import Role, User, UserRole @pytest.fixture() def engine() -> Iterator: engine = create_engine("sqlite:///:memory:", future=True) Base.metadata.create_all(bind=engine) try: yield engine finally: Base.metadata.drop_all(bind=engine) @pytest.fixture() def session(engine) -> Iterator[Session]: TestingSession = sessionmaker( bind=engine, expire_on_commit=False, future=True) session = TestingSession() try: yield session finally: session.close() def test_user_password_helpers() -> None: user = User( email="user@example.com", username="example", password_hash=User.hash_password("initial"), ) user.set_password("new-secret") assert user.password_hash != "new-secret" assert user.verify_password("new-secret") assert not user.verify_password("wrong") def test_user_role_assignment(session: Session) -> None: grantor = User( email="admin@example.com", username="admin", password_hash=User.hash_password("admin-secret"), is_superuser=True, ) analyst_role = Role( name="analyst", display_name="Analyst", description="Can review project dashboards", ) analyst = User( email="analyst@example.com", username="analyst", password_hash=User.hash_password("analyst-secret"), ) assignment = UserRole(user=analyst, role=analyst_role, granted_by_user=grantor) session.add_all([grantor, analyst_role, analyst, assignment]) session.commit() session.refresh(analyst) session.refresh(analyst_role) # Relationship wrapper exposes the role without needing to traverse assignments manually assert len(analyst.role_assignments) == 1 assert analyst.role_assignments[0].granted_by_user is grantor assert len(analyst.roles) == 1 assert analyst.roles[0].name == "analyst" # Ensure reverse relationship exposes the user assert len(analyst_role.assignments) == 1 assert analyst_role.users[0].username == "analyst"