feat: auth & admin

This commit is contained in:
2026-06-03 10:40:00 +03:00
parent 4bca90a50e
commit 93199a3095
34 changed files with 1634 additions and 119 deletions
+88
View File
@@ -0,0 +1,88 @@
"""Unit tests for UserService using in-memory ports."""
import pytest
from app.application.auth_service import AuthService
from app.application.user_service import UserService
from app.core.config import Settings
from app.core.security import Argon2PasswordHasher, JwtTokenService
from app.domain.errors import AlreadyExistsError, AuthenticationError, NotFoundError
from tests.fakes import InMemoryRefreshTokenRepository, InMemoryUserRepository
@pytest.fixture
def env() -> tuple[UserService, AuthService]:
users = InMemoryUserRepository()
refresh = InMemoryRefreshTokenRepository()
hasher = Argon2PasswordHasher()
tokens = JwtTokenService(Settings(jwt_secret="u-test-secret"))
user_svc = UserService(users=users, refresh_tokens=refresh, hasher=hasher)
auth = AuthService(users=users, refresh_tokens=refresh, hasher=hasher, tokens=tokens)
return user_svc, auth
async def test_create_user_duplicate_username(env: tuple[UserService, AuthService]) -> None:
user_svc, _ = env
await user_svc.create_user(username="bob", password="password123")
with pytest.raises(AlreadyExistsError):
await user_svc.create_user(username="bob", password="another-one")
async def test_get_unknown_user_raises(env: tuple[UserService, AuthService]) -> None:
import uuid
user_svc, _ = env
with pytest.raises(NotFoundError):
await user_svc.get_user(uuid.uuid4())
async def test_change_password_requires_current(env: tuple[UserService, AuthService]) -> None:
user_svc, auth = env
user = await user_svc.create_user(username="bob", password="password123")
with pytest.raises(AuthenticationError):
await user_svc.change_password(
user.id, current_password="wrong", new_password="newpassword1"
)
await user_svc.change_password(
user.id, current_password="password123", new_password="newpassword1"
)
# New password works, old one no longer.
await auth.login("bob", "newpassword1")
with pytest.raises(AuthenticationError):
await auth.login("bob", "password123")
async def test_change_password_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
user_svc, auth = env
user = await user_svc.create_user(username="bob", password="password123")
pair = await auth.login("bob", "password123")
await user_svc.change_password(
user.id, current_password="password123", new_password="newpassword1"
)
with pytest.raises(AuthenticationError):
await auth.refresh(pair.refresh.encoded)
async def test_reset_password_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
user_svc, auth = env
user = await user_svc.create_user(username="bob", password="password123")
pair = await auth.login("bob", "password123")
await user_svc.reset_password(user.id, new_password="adminset12")
with pytest.raises(AuthenticationError):
await auth.refresh(pair.refresh.encoded)
await auth.login("bob", "adminset12")
async def test_deactivate_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
user_svc, auth = env
user = await user_svc.create_user(username="bob", password="password123")
pair = await auth.login("bob", "password123")
deactivated = await user_svc.deactivate(user.id)
assert deactivated.is_active is False
with pytest.raises(AuthenticationError):
await auth.refresh(pair.refresh.encoded)