| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- """Editing a user can remove their email: an empty email clears it.
- Left out or null, the email stays as it is. With advanced authentication on,
- a user needs an email (password resets go there), so it can't be removed.
- """
- import pytest
- from httpx import AsyncClient
- from sqlalchemy import select
- from backend.app.models.settings import Settings
- from backend.app.models.user import User
- async def _user(db_session, email: str | None = "mia@example.invalid") -> User:
- from backend.app.core.auth import get_password_hash
- user = User(username="mia", password_hash=get_password_hash("Whatever1!"), email=email, role="user", is_active=True)
- db_session.add(user)
- await db_session.commit()
- return user
- async def _email(db_session, user_id: int) -> str | None:
- db_session.expire_all()
- return (await db_session.execute(select(User.email).where(User.id == user_id))).scalar_one()
- @pytest.mark.asyncio
- @pytest.mark.parametrize("email", ["", " "])
- async def test_an_empty_email_clears_it(async_client: AsyncClient, db_session, email):
- user = await _user(db_session)
- response = await async_client.patch(f"/api/v1/users/{user.id}", json={"email": email})
- assert response.status_code == 200, response.text
- assert response.json()["email"] is None
- assert await _email(db_session, user.id) is None
- @pytest.mark.asyncio
- @pytest.mark.parametrize("body", [{}, {"email": None}, {"role": "user"}])
- async def test_left_out_or_null_keeps_it(async_client: AsyncClient, db_session, body):
- user = await _user(db_session)
- response = await async_client.patch(f"/api/v1/users/{user.id}", json=body)
- assert response.status_code == 200, response.text
- assert await _email(db_session, user.id) == "mia@example.invalid"
- @pytest.mark.asyncio
- async def test_two_users_without_email_are_no_duplicate(async_client: AsyncClient, db_session):
- from backend.app.core.auth import get_password_hash
- db_session.add(User(username="other", password_hash=get_password_hash("Whatever1!"), email=None, role="user"))
- user = await _user(db_session)
- response = await async_client.patch(f"/api/v1/users/{user.id}", json={"email": ""})
- assert response.status_code == 200, response.text
- @pytest.mark.asyncio
- async def test_refused_while_advanced_authentication_is_on(async_client: AsyncClient, db_session):
- user = await _user(db_session)
- db_session.add(Settings(key="advanced_auth_enabled", value="true"))
- await db_session.commit()
- response = await async_client.patch(f"/api/v1/users/{user.id}", json={"email": ""})
- assert response.status_code == 400
- assert "advanced authentication" in response.json()["detail"]
- assert await _email(db_session, user.id) == "mia@example.invalid"
- @pytest.mark.asyncio
- async def test_a_user_without_email_can_still_be_edited_with_advanced_authentication(
- async_client: AsyncClient, db_session
- ):
- user = await _user(db_session, email=None)
- db_session.add(Settings(key="advanced_auth_enabled", value="true"))
- await db_session.commit()
- response = await async_client.patch(f"/api/v1/users/{user.id}", json={"email": "", "role": "user"})
- assert response.status_code == 200, response.text
- assert await _email(db_session, user.id) is None
- @pytest.mark.asyncio
- async def test_refused_while_the_user_signs_in_with_an_email_code(async_client: AsyncClient, db_session):
- """Email 2FA needs SMTP, not advanced authentication: the code goes to this address."""
- user = await _user(db_session)
- db_session.add(Settings(key=f"user_{user.id}_email_2fa_enabled", value="true"))
- await db_session.commit()
- response = await async_client.patch(f"/api/v1/users/{user.id}", json={"email": ""})
- assert response.status_code == 400
- assert "email" in response.json()["detail"].lower()
- assert await _email(db_session, user.id) == "mia@example.invalid"
|