2026-07-14 11:51:32 +02:00
|
|
|
"""Direct unit tests for auth_service functions."""
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from sqlalchemy.ext.asyncio import (
|
|
|
|
|
AsyncSession,
|
|
|
|
|
async_sessionmaker,
|
|
|
|
|
create_async_engine,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
from app.database import Base
|
|
|
|
|
from app.models.user import User, UserRole
|
|
|
|
|
from app.services.auth_service import (
|
|
|
|
|
authenticate_user,
|
|
|
|
|
create_user,
|
|
|
|
|
deactivate_user,
|
|
|
|
|
generate_token_pair,
|
|
|
|
|
get_user_by_email,
|
|
|
|
|
get_user_by_id,
|
|
|
|
|
hash_password,
|
|
|
|
|
list_users,
|
|
|
|
|
refresh_access_token,
|
|
|
|
|
update_user,
|
|
|
|
|
verify_password,
|
|
|
|
|
)
|
|
|
|
|
from app.utils.jwt import create_refresh_token
|
|
|
|
|
|
|
|
|
|
TEST_DATABASE_URL = (
|
|
|
|
|
"postgresql+asyncpg://erp_test_user:testpass@localhost:5432/erp_test"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def event_loop():
|
|
|
|
|
import asyncio
|
2026-07-17 21:28:58 +02:00
|
|
|
|
2026-07-14 11:51:32 +02:00
|
|
|
loop = asyncio.new_event_loop()
|
|
|
|
|
yield loop
|
|
|
|
|
loop.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
async def engine():
|
|
|
|
|
eng = create_async_engine(TEST_DATABASE_URL, echo=False, pool_pre_ping=True)
|
|
|
|
|
async with eng.begin() as conn:
|
|
|
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
|
yield eng
|
|
|
|
|
async with eng.begin() as conn:
|
|
|
|
|
await conn.run_sync(Base.metadata.drop_all)
|
|
|
|
|
await eng.dispose()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
async def session_factory(engine):
|
|
|
|
|
return async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
async def session(session_factory):
|
|
|
|
|
async with session_factory() as s:
|
|
|
|
|
yield s
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_hash_and_verify_password():
|
|
|
|
|
hashed = hash_password("mypassword")
|
|
|
|
|
assert hashed != "mypassword"
|
|
|
|
|
assert verify_password("mypassword", hashed) is True
|
|
|
|
|
assert verify_password("wrongpassword", hashed) is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_user_by_email_found(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="findme@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name="Find Me",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
await session.refresh(user)
|
|
|
|
|
|
|
|
|
|
found = await get_user_by_email(session, "findme@test.com")
|
|
|
|
|
assert found is not None
|
|
|
|
|
assert found.email == "findme@test.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_user_by_email_not_found(session):
|
|
|
|
|
found = await get_user_by_email(session, "nobody@test.com")
|
|
|
|
|
assert found is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_user_by_id_found(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="byid@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name="By ID",
|
|
|
|
|
role=UserRole.verkaeufer,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
await session.refresh(user)
|
|
|
|
|
|
|
|
|
|
found = await get_user_by_id(session, user.id)
|
|
|
|
|
assert found is not None
|
|
|
|
|
assert found.email == "byid@test.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_get_user_by_id_not_found(session):
|
|
|
|
|
found = await get_user_by_id(session, uuid.uuid4())
|
|
|
|
|
assert found is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_authenticate_user_valid(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="auth@test.com",
|
|
|
|
|
password_hash=hash_password("Auth12345!"),
|
|
|
|
|
full_name="Auth User",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
is_active=True,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
await session.refresh(user)
|
|
|
|
|
|
|
|
|
|
result = await authenticate_user(session, "auth@test.com", "Auth12345!")
|
|
|
|
|
assert result is not None
|
|
|
|
|
assert result.email == "auth@test.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_authenticate_user_wrong_password(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="wrongpw@test.com",
|
|
|
|
|
password_hash=hash_password("Correct123!"),
|
|
|
|
|
full_name="Wrong PW",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
is_active=True,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
result = await authenticate_user(session, "wrongpw@test.com", "Wrong123!")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_authenticate_user_inactive(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="inactive2@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name="Inactive",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
is_active=False,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
result = await authenticate_user(session, "inactive2@test.com", "Test12345!")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_authenticate_user_nonexistent(session):
|
|
|
|
|
result = await authenticate_user(session, "ghost@test.com", "Test12345!")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_generate_token_pair(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="token@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name="Token User",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
language="de",
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
await session.refresh(user)
|
|
|
|
|
|
|
|
|
|
tokens = generate_token_pair(user)
|
|
|
|
|
assert "access_token" in tokens
|
|
|
|
|
assert "refresh_token" in tokens
|
|
|
|
|
assert tokens["token_type"] == "bearer"
|
|
|
|
|
assert tokens["expires_in"] > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_refresh_access_token_valid(session):
|
|
|
|
|
user = User(
|
|
|
|
|
email="refresh@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name="Refresh User",
|
|
|
|
|
role=UserRole.admin,
|
|
|
|
|
language="de",
|
|
|
|
|
is_active=True,
|
|
|
|
|
)
|
|
|
|
|
session.add(user)
|
|
|
|
|
await session.commit()
|
|
|
|
|
await session.refresh(user)
|
|
|
|
|
|
|
|
|
|
rt = create_refresh_token(
|
|
|
|
|
user_id=str(user.id), role="admin", email=user.email, lang="de"
|
|
|
|
|
)
|
|
|
|
|
tokens = await refresh_access_token(session, rt)
|
|
|
|
|
assert tokens is not None
|
|
|
|
|
assert "access_token" in tokens
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_refresh_access_token_invalid(session):
|
|
|
|
|
result = await refresh_access_token(session, "invalid.token.here")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_refresh_access_token_nonexistent_user(session):
|
|
|
|
|
fake_id = uuid.uuid4()
|
|
|
|
|
rt = create_refresh_token(
|
|
|
|
|
user_id=str(fake_id), role="admin", email="ghost@test.com", lang="de"
|
|
|
|
|
)
|
|
|
|
|
result = await refresh_access_token(session, rt)
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_create_user_success(session):
|
|
|
|
|
user = await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="new@test.com",
|
|
|
|
|
password="NewUser123!",
|
|
|
|
|
full_name="New User",
|
|
|
|
|
role="verkaeufer",
|
|
|
|
|
language="en",
|
|
|
|
|
)
|
|
|
|
|
assert user.email == "new@test.com"
|
|
|
|
|
assert user.full_name == "New User"
|
|
|
|
|
assert user.role == UserRole.verkaeufer
|
|
|
|
|
assert user.language == "en"
|
|
|
|
|
assert user.is_active is True
|
|
|
|
|
assert user.password_hash != "NewUser123!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_create_user_duplicate(session):
|
|
|
|
|
await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="dup@test.com",
|
|
|
|
|
password="First123!",
|
|
|
|
|
full_name="First",
|
|
|
|
|
)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
|
|
|
await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="dup@test.com",
|
|
|
|
|
password="Second123!",
|
|
|
|
|
full_name="Second",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_list_users_pagination(session):
|
|
|
|
|
for i in range(5):
|
|
|
|
|
u = User(
|
|
|
|
|
email=f"list{i}@test.com",
|
|
|
|
|
password_hash=hash_password("Test12345!"),
|
|
|
|
|
full_name=f"User {i}",
|
|
|
|
|
role=UserRole.verkaeufer,
|
|
|
|
|
)
|
|
|
|
|
session.add(u)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
users, total = await list_users(session, page=1, page_size=3)
|
|
|
|
|
assert total == 5
|
|
|
|
|
assert len(users) == 3
|
|
|
|
|
|
|
|
|
|
users2, total2 = await list_users(session, page=2, page_size=3)
|
|
|
|
|
assert total2 == 5
|
|
|
|
|
assert len(users2) == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_update_user_success(session):
|
|
|
|
|
user = await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="update@test.com",
|
|
|
|
|
password="Update123!",
|
|
|
|
|
full_name="Before Update",
|
|
|
|
|
)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
updated = await update_user(
|
|
|
|
|
session, user.id, full_name="After Update", language="en"
|
|
|
|
|
)
|
|
|
|
|
assert updated is not None
|
|
|
|
|
assert updated.full_name == "After Update"
|
|
|
|
|
assert updated.language == "en"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_update_user_not_found(session):
|
|
|
|
|
result = await update_user(session, uuid.uuid4(), full_name="Nobody")
|
|
|
|
|
assert result is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_update_user_role(session):
|
|
|
|
|
user = await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="role@test.com",
|
|
|
|
|
password="Role12345!",
|
|
|
|
|
full_name="Role User",
|
|
|
|
|
role="verkaeufer",
|
|
|
|
|
)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
updated = await update_user(session, user.id, role="admin")
|
|
|
|
|
assert updated is not None
|
|
|
|
|
assert updated.role == UserRole.admin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_deactivate_user_success(session):
|
|
|
|
|
user = await create_user(
|
|
|
|
|
session,
|
|
|
|
|
email="deactivate@test.com",
|
|
|
|
|
password="Deact123!",
|
|
|
|
|
full_name="Deactivate Me",
|
|
|
|
|
)
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
result = await deactivate_user(session, user.id)
|
|
|
|
|
assert result is not None
|
|
|
|
|
assert result.is_active is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_deactivate_user_not_found(session):
|
|
|
|
|
result = await deactivate_user(session, uuid.uuid4())
|
|
|
|
|
assert result is None
|