diff --git a/backend/app/routers/users.py b/backend/app/routers/users.py index fd5534e..63108ec 100644 --- a/backend/app/routers/users.py +++ b/backend/app/routers/users.py @@ -1,5 +1,8 @@ """User management router (admin only).""" +# Import logging +import logging + # Import uuid import uuid @@ -47,6 +50,8 @@ from app.services.password_setup_service import send_password_setup_email # Assign router = APIRouter(prefix="/users", tags=["users"]) router = APIRouter(prefix="/users", tags=["users"]) +logger = logging.getLogger(__name__) + # --------------------------------------------------------------------------- # PATCH /users/me/preferences — update current user preferences @@ -187,6 +192,17 @@ def create_user_route( # Reload ORM object attributes from the database db.refresh(user) + # Send the set-password email right away — best-effort. If the webhook + # isn't configured or rejects the request, the user is still created + # successfully; the admin can retry via the "Send Email" button, which + # surfaces the failure properly (unlike this automatic first attempt). + try: + with UnitOfWork(db) as uow: + send_password_setup_email(db, user) + uow.commit() + except Exception: + logger.warning("Automatic set-password email failed for new user %s", user.id, exc_info=True) + from app.services.notification_service import notify_roles_by_email notify_roles_by_email( db, roles=["admin"], diff --git a/backend/tests/test_password_setup_flow.py b/backend/tests/test_password_setup_flow.py index 3a257a3..47b1e48 100644 --- a/backend/tests/test_password_setup_flow.py +++ b/backend/tests/test_password_setup_flow.py @@ -18,6 +18,41 @@ def new_user_id(api, auth_headers): return resp.json()["id"] +def test_create_user_automatically_sends_set_password_email(api, db, auth_headers): + api( + "patch", "/api/v1/system/email-webhook-config", auth_headers, + json={"url": "https://example.com/power-automate-hook"}, + ) + + with patch("app.services.webhook_email_service.requests.post") as mock_post: + resp = api( + "post", "/api/v1/users", auth_headers, + json={"full_name": "Auto Send User", "email": "autosend@test.com", "role": "viewer"}, + ) + assert resp.status_code == 201, resp.text + mock_post.assert_called_once() + payload = mock_post.call_args.kwargs["json"] + assert payload["to"] == "autosend@test.com" + assert payload["subject"] == "Set Your Password" + assert "token=" in payload["body"] + + from app.models.password_setup_token import PasswordSetupToken + token_row = db.query(PasswordSetupToken).filter( + PasswordSetupToken.user_id == uuid.UUID(resp.json()["id"]), + ).first() + assert token_row is not None + + +def test_create_user_succeeds_even_if_auto_send_fails(api, auth_headers): + """No webhook configured yet — the automatic first-email attempt fails, + but user creation itself must still succeed.""" + resp = api( + "post", "/api/v1/users", auth_headers, + json={"full_name": "No Webhook User", "email": "nowebhook@test.com", "role": "viewer"}, + ) + assert resp.status_code == 201, resp.text + + def test_send_password_email_requires_webhook_configured(api, auth_headers, new_user_id): resp = api("post", f"/api/v1/users/{new_user_id}/send-password-email", auth_headers) assert resp.status_code == 400