"""Unit tests for AuthService 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 AuthenticationError from tests.fakes import InMemoryRefreshTokenRepository, InMemoryUserRepository @pytest.fixture def env() -> tuple[AuthService, UserService, InMemoryUserRepository]: users = InMemoryUserRepository() refresh = InMemoryRefreshTokenRepository() hasher = Argon2PasswordHasher() tokens = JwtTokenService(Settings(jwt_secret="svc-test-secret")) auth = AuthService(users=users, refresh_tokens=refresh, hasher=hasher, tokens=tokens) user_svc = UserService(users=users, refresh_tokens=refresh, hasher=hasher) return auth, user_svc, users async def test_login_success_then_authenticate( env: tuple[AuthService, UserService, object], ) -> None: auth, user_svc, _ = env created = await user_svc.create_user(username="alice", password="password123") pair = await auth.login("alice", "password123") user = await auth.authenticate_access(pair.access.encoded) assert user.id == created.id assert user.username == "alice" async def test_login_wrong_password(env: tuple[AuthService, UserService, object]) -> None: auth, user_svc, _ = env await user_svc.create_user(username="alice", password="password123") with pytest.raises(AuthenticationError): await auth.login("alice", "nope") async def test_login_unknown_user(env: tuple[AuthService, UserService, object]) -> None: auth, _, _ = env with pytest.raises(AuthenticationError): await auth.login("ghost", "whatever") async def test_login_inactive_user(env: tuple[AuthService, UserService, object]) -> None: auth, user_svc, _ = env user = await user_svc.create_user(username="alice", password="password123") await user_svc.set_active(user.id, is_active=False) with pytest.raises(AuthenticationError): await auth.login("alice", "password123") async def test_refresh_rotates_and_invalidates_old( env: tuple[AuthService, UserService, object], ) -> None: auth, user_svc, _ = env await user_svc.create_user(username="alice", password="password123") pair = await auth.login("alice", "password123") new_pair = await auth.refresh(pair.refresh.encoded) assert new_pair.refresh.encoded != pair.refresh.encoded # Old refresh token is now revoked (rotation) — reuse must fail. with pytest.raises(AuthenticationError): await auth.refresh(pair.refresh.encoded) # New one still works. await auth.refresh(new_pair.refresh.encoded) async def test_access_token_not_accepted_as_refresh( env: tuple[AuthService, UserService, object], ) -> None: auth, user_svc, _ = env await user_svc.create_user(username="alice", password="password123") pair = await auth.login("alice", "password123") with pytest.raises(AuthenticationError): await auth.refresh(pair.access.encoded) async def test_logout_revokes_refresh(env: tuple[AuthService, UserService, object]) -> None: auth, user_svc, _ = env await user_svc.create_user(username="alice", password="password123") pair = await auth.login("alice", "password123") await auth.logout(pair.refresh.encoded) with pytest.raises(AuthenticationError): await auth.refresh(pair.refresh.encoded) async def test_logout_ignores_garbage(env: tuple[AuthService, UserService, object]) -> None: auth, _, _ = env await auth.logout("not-a-jwt") # must not raise