fix(users): repair create delete and duplicate email handling

This commit is contained in:
Schubert Ferenc 2026-07-11 17:14:41 +02:00
parent 155fdbb16a
commit 0bbcaba211
22 changed files with 773 additions and 107 deletions

View file

@ -1,9 +1,11 @@
from __future__ import annotations
import json
import re
from datetime import date
from pathlib import Path
import pytest
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
@ -19,8 +21,9 @@ from app.services.validation_workflow import ValidationWorkflowService
from app.services.auth_service import AuthService
from app.services.reference_masterdata import ReferenceMasterdataImportService
from app.api.v1.auth import login as auth_login
from app.api.v1 import domain as domain_api
from app.schemas.auth import LoginRequest
from app.schemas.domain import ValidationCreate
from app.schemas.domain import UserCreate, UserUpdate, ValidationCreate
from app.modules.orion.service import OrionReportService
from app.modules.orion.assets import SCHUBAMED_LOGO_PATH, schubamed_logo_uri
from app.modules.orion.template_service import REPORT_SECTIONS, ReportTemplateService
@ -212,6 +215,319 @@ def test_user_model_supports_password_management_fields():
assert user.role == UserRole.PRUEFER
def test_admin_appears_in_user_list():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add(admin)
db.flush()
result = domain_api.list_users(session=db, _=admin, page=1, page_size=20, search=None, role=None, active=None, sort_by="created_at", sort_order="desc")
assert result["total"] == 1
assert result["pages"] == 1
assert any(item.id == admin.id for item in result["items"])
def test_user_list_pagination_and_filters_work():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
inactive = User(
email="inactive@schubamed.de",
first_name="Inactive",
last_name="User",
role=UserRole.LESER.value,
password_hash="hash",
is_active=False,
must_change_password=False,
)
db.add_all([admin, inactive])
db.flush()
result = domain_api.list_users(session=db, _=admin, page=1, page_size=1, search=None, role=None, active=None, sort_by="created_at", sort_order="desc")
assert result["total"] == 2
assert result["pages"] == 2
assert len(result["items"]) == 1
active_only = domain_api.list_users(session=db, _=admin, page=1, page_size=20, search=None, role=None, active=True, sort_by="created_at", sort_order="desc")
assert active_only["total"] == 1
assert all(item.is_active for item in active_only["items"])
inactive_only = domain_api.list_users(session=db, _=admin, page=1, page_size=20, search=None, role=None, active=False, sort_by="created_at", sort_order="desc")
assert inactive_only["total"] == 1
assert all(not item.is_active for item in inactive_only["items"])
role_filtered = domain_api.list_users(session=db, _=admin, page=1, page_size=20, search=None, role=UserRole.ADMIN.value, active=None, sort_by="created_at", sort_order="desc")
assert role_filtered["total"] == 1
assert role_filtered["items"][0].role == UserRole.ADMIN.value
def test_admin_can_create_users_with_supported_roles():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add(admin)
db.flush()
for role in (UserRole.MITARBEITER, UserRole.PRUEFER, UserRole.LESER):
response = domain_api.create_user(
UserCreate(
email=f"{role.value}@schubamed.de",
first_name="Test",
last_name="User",
role=role,
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
),
session=db,
_=admin,
)
payload = json.loads(response.body)
assert response.status_code == 201
assert payload["user"]["role"] == role.value
assert db.scalar(select(User).where(User.email == f"{role.value}@schubamed.de")) is not None
def test_create_user_trims_and_normalizes_email():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add(admin)
db.flush()
response = domain_api.create_user(
UserCreate(
email=" New.User@Schubamed.DE ",
first_name="New",
last_name="User",
role=UserRole.MITARBEITER,
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
),
session=db,
_=admin,
)
payload = json.loads(response.body)
assert response.status_code == 201
assert payload["user"]["email"] == "new.user@schubamed.de"
assert db.scalar(select(User).where(User.email == "new.user@schubamed.de")) is not None
def test_duplicate_email_returns_409_and_rolls_back_session():
from fastapi import HTTPException
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add(admin)
db.flush()
first = domain_api.create_user(
UserCreate(
email="duplicate@schubamed.de",
first_name="First",
last_name="User",
role=UserRole.MITARBEITER,
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
),
session=db,
_=admin,
)
assert first.status_code == 201
with pytest.raises(HTTPException) as exc_info:
domain_api.create_user(
UserCreate(
email="duplicate@schubamed.de",
first_name="Second",
last_name="User",
role=UserRole.PRUEFER,
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
),
session=db,
_=admin,
)
assert exc_info.value.status_code == 409
follow_up = domain_api.create_user(
UserCreate(
email="fresh@schubamed.de",
first_name="Fresh",
last_name="User",
role=UserRole.LESER,
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
),
session=db,
_=admin,
)
assert follow_up.status_code == 201
def test_update_user_keeps_same_email_without_false_duplicate():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
target = User(
email="target@schubamed.de",
first_name="Target",
last_name="User",
role=UserRole.MITARBEITER.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add_all([admin, target])
db.flush()
response = domain_api.update_user(
target.id,
UserUpdate(
first_name="Target",
last_name="User",
email="target@schubamed.de",
role=UserRole.MITARBEITER,
is_active=True,
must_change_password=False,
),
session=db,
me=admin,
)
assert response.email == "target@schubamed.de"
assert response.id == target.id
def test_delete_user_without_references_removes_row():
db = session()
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
user = User(
email="delete-me@schubamed.de",
first_name="Delete",
last_name="Me",
role=UserRole.MITARBEITER.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
db.add_all([admin, user])
db.flush()
response = domain_api.delete_user(user.id, session=db, me=admin)
assert response.status_code == 204
assert db.get(User, user.id) is None
def test_delete_user_with_validation_reference_soft_deletes_row():
db = session()
customer, location, device = seed(db)
admin = User(
email="admin@schubamed.de",
first_name="Admin",
last_name="User",
role=UserRole.ADMIN.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
examiner = User(
email="examiner@schubamed.de",
first_name="Examiner",
last_name="User",
role=UserRole.PRUEFER.value,
password_hash="hash",
is_active=True,
must_change_password=False,
)
validation = valid_validation(customer, location, device)
db.add_all([admin, examiner, validation])
db.flush()
validation.examiner_id = examiner.id
db.flush()
response = domain_api.delete_user(examiner.id, session=db, me=admin)
assert response.status_code == 204
db.refresh(examiner)
assert examiner.deleted_at is not None
assert examiner.is_active is False
listed = domain_api.list_users(session=db, _=admin, page=1, page_size=20, search=None, role=None, active=None, sort_by="created_at", sort_order="desc")
assert all(item.id != examiner.id for item in listed["items"])
def test_invalid_user_role_is_rejected_by_schema():
with pytest.raises(Exception):
UserCreate(
email="bad-role@schubamed.de",
first_name="Bad",
last_name="Role",
role="invalid-role", # type: ignore[arg-type]
is_active=True,
must_change_password=False,
temporary_password="TempPassword123!",
)
def test_login_updates_last_login_at():
from app.core.security import hash_password