feat: Implement user and role management with repositories
- Added RoleRepository and UserRepository for managing roles and users. - Implemented methods for creating, retrieving, and assigning roles to users. - Introduced functions to ensure default roles and an admin user exist in the system. - Updated UnitOfWork to include user and role repositories. - Created new security module for password hashing and JWT token management. - Added tests for authentication flows, including registration, login, and password reset. - Enhanced HTML templates for user registration, login, and password management with error handling. - Added a logo image to the static assets.
This commit is contained in:
135
tests/test_auth_repositories.py
Normal file
135
tests/test_auth_repositories.py
Normal file
@@ -0,0 +1,135 @@
|
||||
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
|
||||
from services.repositories import (
|
||||
RoleRepository,
|
||||
UserRepository,
|
||||
ensure_admin_user,
|
||||
ensure_default_roles,
|
||||
)
|
||||
from services.unit_of_work import UnitOfWork
|
||||
|
||||
|
||||
@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)
|
||||
db = TestingSession()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_role_repository_create_and_lookup(session: Session) -> None:
|
||||
repo = RoleRepository(session)
|
||||
role = Role(name="custom", display_name="Custom",
|
||||
description="Custom role")
|
||||
repo.create(role)
|
||||
|
||||
retrieved = repo.get(role.id)
|
||||
assert retrieved.name == "custom"
|
||||
assert repo.get_by_name("custom") is retrieved
|
||||
assert repo.list()[0].name == "custom"
|
||||
|
||||
|
||||
def test_user_repository_assign_and_revoke_role(session: Session) -> None:
|
||||
role_repo = RoleRepository(session)
|
||||
user_repo = UserRepository(session)
|
||||
|
||||
analyst = role_repo.create(
|
||||
Role(name="analyst", display_name="Analyst", description="Analyzes data")
|
||||
)
|
||||
user = User(
|
||||
email="user@example.com",
|
||||
username="user",
|
||||
password_hash=User.hash_password("secret"),
|
||||
)
|
||||
user_repo.create(user)
|
||||
|
||||
assignment = user_repo.assign_role(
|
||||
user_id=user.id, role_id=analyst.id, granted_by=None)
|
||||
assert assignment.role_id == analyst.id
|
||||
|
||||
refreshed = user_repo.get(user.id, with_roles=True)
|
||||
assert refreshed.roles[0].name == "analyst"
|
||||
|
||||
user_repo.revoke_role(user_id=user.id, role_id=analyst.id)
|
||||
refreshed = user_repo.get(user.id, with_roles=True)
|
||||
assert refreshed.roles == []
|
||||
|
||||
|
||||
def test_default_role_and_admin_helpers(session: Session) -> None:
|
||||
role_repo = RoleRepository(session)
|
||||
user_repo = UserRepository(session)
|
||||
|
||||
roles = ensure_default_roles(role_repo)
|
||||
assert {role.name for role in roles} == {
|
||||
"admin", "project_manager", "analyst", "viewer"}
|
||||
|
||||
ensure_admin_user(
|
||||
user_repo,
|
||||
role_repo,
|
||||
email="admin@example.com",
|
||||
username="admin",
|
||||
password="SecurePass1!",
|
||||
)
|
||||
|
||||
admin = user_repo.get_by_email("admin@example.com", with_roles=True)
|
||||
assert admin is not None
|
||||
assert admin.is_superuser
|
||||
assert {role.name for role in admin.roles} >= {"admin"}
|
||||
|
||||
# Idempotent behaviour on subsequent calls
|
||||
ensure_admin_user(
|
||||
user_repo,
|
||||
role_repo,
|
||||
email="admin@example.com",
|
||||
username="admin",
|
||||
password="SecurePass1!",
|
||||
)
|
||||
admin_again = user_repo.get_by_email("admin@example.com", with_roles=True)
|
||||
assert admin_again is not None
|
||||
assert len(admin_again.roles) == len(
|
||||
{role.name for role in admin_again.roles})
|
||||
|
||||
|
||||
def test_unit_of_work_exposes_auth_repositories(engine) -> None:
|
||||
TestingSession = sessionmaker(
|
||||
bind=engine, expire_on_commit=False, future=True)
|
||||
|
||||
with UnitOfWork(session_factory=TestingSession) as uow:
|
||||
assert uow.users is not None
|
||||
assert uow.roles is not None
|
||||
|
||||
roles = uow.ensure_default_roles()
|
||||
assert any(role.name == "admin" for role in roles)
|
||||
|
||||
uow.ensure_admin_user(
|
||||
email="uow-admin@example.com",
|
||||
username="uow-admin",
|
||||
password="AnotherSecret1!",
|
||||
)
|
||||
|
||||
admin = uow.users.get_by_email(
|
||||
"uow-admin@example.com", with_roles=True)
|
||||
assert admin is not None
|
||||
assert admin.is_superuser
|
||||
assert any(role.name == "admin" for role in admin.roles)
|
||||
Reference in New Issue
Block a user