feat(auth): auto-send verification email on register; enforce invite email match
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
98
backend/tests/test_email_verification_autosend.py
Normal file
98
backend/tests/test_email_verification_autosend.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import pytest
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_auto_sends_verification_email(client, test_db):
|
||||
"""Fresh registration triggers send_email_verification_email."""
|
||||
with patch(
|
||||
"app.core.email.EmailService.send_email_verification_email",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_send:
|
||||
response = await client.post("/api/v1/auth/register", json={
|
||||
"email": "newshop@example.com",
|
||||
"password": "Verystrong1Pwd",
|
||||
"name": "New Shop",
|
||||
})
|
||||
assert response.status_code in (200, 201), response.json()
|
||||
mock_send.assert_called_once()
|
||||
kwargs = mock_send.call_args.kwargs
|
||||
assert kwargs["to_email"] == "newshop@example.com"
|
||||
assert "/verify-email?token=" in kwargs["verification_url"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_with_account_invite_code_email_mismatch_rejected(
|
||||
client, test_db, test_user
|
||||
):
|
||||
"""Invite code is for invited@example.com but user registers with a
|
||||
different email -> 400 invite_email_mismatch."""
|
||||
from app.models.account_invite import AccountInvite
|
||||
import uuid
|
||||
|
||||
invited_by_id = uuid.UUID(test_user["user_data"]["id"])
|
||||
account_id = uuid.UUID(test_user["user_data"]["account_id"])
|
||||
|
||||
invite = AccountInvite(
|
||||
account_id=account_id,
|
||||
invited_by_id=invited_by_id,
|
||||
email="invited@example.com",
|
||||
code="INVITECODE99",
|
||||
role="engineer",
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
|
||||
)
|
||||
test_db.add(invite)
|
||||
await test_db.commit()
|
||||
|
||||
response = await client.post("/api/v1/auth/register", json={
|
||||
"email": "wrong-email@example.com",
|
||||
"password": "Verystrong1Pwd",
|
||||
"name": "Wrong Email",
|
||||
"account_invite_code": "INVITECODE99",
|
||||
})
|
||||
assert response.status_code == 400, response.json()
|
||||
assert response.json()["detail"]["error"] == "invite_email_mismatch"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_with_account_invite_code_email_match_accepted(
|
||||
client, test_db, test_user
|
||||
):
|
||||
"""Invite code is for invited@example.com - registering with that email
|
||||
succeeds and joins the existing account."""
|
||||
from app.models.account_invite import AccountInvite
|
||||
from app.models.user import User
|
||||
import uuid
|
||||
|
||||
invited_by_id = uuid.UUID(test_user["user_data"]["id"])
|
||||
account_id = uuid.UUID(test_user["user_data"]["account_id"])
|
||||
|
||||
invite = AccountInvite(
|
||||
account_id=account_id,
|
||||
invited_by_id=invited_by_id,
|
||||
email="invited@example.com",
|
||||
code="INVITECODE100",
|
||||
role="engineer",
|
||||
expires_at=datetime.now(timezone.utc) + timedelta(days=7),
|
||||
)
|
||||
test_db.add(invite)
|
||||
await test_db.commit()
|
||||
|
||||
with patch(
|
||||
"app.core.email.EmailService.send_email_verification_email",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
response = await client.post("/api/v1/auth/register", json={
|
||||
"email": "invited@example.com",
|
||||
"password": "Verystrong1Pwd",
|
||||
"name": "Invited",
|
||||
"account_invite_code": "INVITECODE100",
|
||||
})
|
||||
assert response.status_code in (200, 201), response.json()
|
||||
|
||||
new_user = (await test_db.execute(
|
||||
select(User).where(User.email == "invited@example.com")
|
||||
)).scalar_one()
|
||||
assert new_user.account_id == account_id # joined existing account
|
||||
Reference in New Issue
Block a user