Merge pull request #531 from christianlouis/copilot/increase-test-coverage-local-auth
test(local_auth): increase coverage for app/utils/local_auth.py to 100%
This commit is contained in:
@@ -917,3 +917,245 @@ def test_send_forgot_username_email_no_smtp_raises():
|
|||||||
|
|
||||||
with pytest.raises(RuntimeError, match="SMTP"):
|
with pytest.raises(RuntimeError, match="SMTP"):
|
||||||
send_forgot_username_email("u@example.com", "myusername")
|
send_forgot_username_email("u@example.com", "myusername")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Unit tests: _smtp_send internals
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_no_email_host_raises():
|
||||||
|
"""_smtp_send raises RuntimeError when EMAIL_HOST is not configured."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = None
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="SMTP is not configured"):
|
||||||
|
_smtp_send("Subject", "<p>html</p>", "plain", "r@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_dns_resolution_failure():
|
||||||
|
"""_smtp_send raises RuntimeError when the SMTP host cannot be resolved."""
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "badhost.invalid"
|
||||||
|
mock_settings.email_sender = "sender@example.com"
|
||||||
|
mock_settings.email_username = None
|
||||||
|
mock_settings.email_password = None
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = False
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", side_effect=socket.gaierror("Name resolution failed")):
|
||||||
|
with pytest.raises(RuntimeError, match="Cannot resolve SMTP host"):
|
||||||
|
_smtp_send("Subject", "<p>html</p>", "plain", "r@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_success_no_tls_no_auth():
|
||||||
|
"""_smtp_send connects, skips TLS and auth when not configured, and sends the message."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
mock_server = MagicMock()
|
||||||
|
mock_smtp_cls = MagicMock()
|
||||||
|
mock_smtp_cls.return_value.__enter__ = MagicMock(return_value=mock_server)
|
||||||
|
mock_smtp_cls.return_value.__exit__ = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "smtp.example.com"
|
||||||
|
mock_settings.email_sender = "sender@example.com"
|
||||||
|
mock_settings.email_username = None
|
||||||
|
mock_settings.email_password = None
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = False
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", return_value="1.2.3.4"):
|
||||||
|
with patch("app.utils.local_auth.smtplib.SMTP", mock_smtp_cls):
|
||||||
|
_smtp_send("Test Subject", "<p>html</p>", "plain text", "recipient@example.com")
|
||||||
|
|
||||||
|
mock_server.starttls.assert_not_called()
|
||||||
|
mock_server.login.assert_not_called()
|
||||||
|
mock_server.send_message.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_with_tls():
|
||||||
|
"""_smtp_send calls starttls() when email_use_tls is True."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
mock_server = MagicMock()
|
||||||
|
mock_smtp_cls = MagicMock()
|
||||||
|
mock_smtp_cls.return_value.__enter__ = MagicMock(return_value=mock_server)
|
||||||
|
mock_smtp_cls.return_value.__exit__ = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "smtp.example.com"
|
||||||
|
mock_settings.email_sender = "sender@example.com"
|
||||||
|
mock_settings.email_username = None
|
||||||
|
mock_settings.email_password = None
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = True
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", return_value="1.2.3.4"):
|
||||||
|
with patch("app.utils.local_auth.smtplib.SMTP", mock_smtp_cls):
|
||||||
|
_smtp_send("Test Subject", "<p>html</p>", "plain text", "recipient@example.com")
|
||||||
|
|
||||||
|
mock_server.starttls.assert_called_once()
|
||||||
|
mock_server.send_message.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_with_auth():
|
||||||
|
"""_smtp_send calls login() when email_username and email_password are set."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
mock_server = MagicMock()
|
||||||
|
mock_smtp_cls = MagicMock()
|
||||||
|
mock_smtp_cls.return_value.__enter__ = MagicMock(return_value=mock_server)
|
||||||
|
mock_smtp_cls.return_value.__exit__ = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "smtp.example.com"
|
||||||
|
mock_settings.email_sender = None
|
||||||
|
mock_settings.email_username = "user@example.com"
|
||||||
|
mock_settings.email_password = "secret"
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = False
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", return_value="1.2.3.4"):
|
||||||
|
with patch("app.utils.local_auth.smtplib.SMTP", mock_smtp_cls):
|
||||||
|
_smtp_send("Subject", "<p>html</p>", "plain", "r@example.com")
|
||||||
|
|
||||||
|
mock_server.login.assert_called_once_with("user@example.com", "secret")
|
||||||
|
mock_server.send_message.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_uses_email_username_as_sender_fallback():
|
||||||
|
"""_smtp_send falls back to email_username when email_sender is not set."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
mock_server = MagicMock()
|
||||||
|
mock_smtp_cls = MagicMock()
|
||||||
|
mock_smtp_cls.return_value.__enter__ = MagicMock(return_value=mock_server)
|
||||||
|
mock_smtp_cls.return_value.__exit__ = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "smtp.example.com"
|
||||||
|
mock_settings.email_sender = None
|
||||||
|
mock_settings.email_username = "fallback@example.com"
|
||||||
|
mock_settings.email_password = None
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = False
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", return_value="1.2.3.4"):
|
||||||
|
with patch("app.utils.local_auth.smtplib.SMTP", mock_smtp_cls):
|
||||||
|
_smtp_send("Subject", "<p>html</p>", "plain", "r@example.com")
|
||||||
|
|
||||||
|
# The message should have been sent (sender was resolved from email_username)
|
||||||
|
mock_server.send_message.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_smtp_send_default_noreply_sender():
|
||||||
|
"""_smtp_send falls back to noreply@docuelevate.local when neither sender nor username is set."""
|
||||||
|
from app.utils.local_auth import _smtp_send
|
||||||
|
|
||||||
|
mock_server = MagicMock()
|
||||||
|
mock_smtp_cls = MagicMock()
|
||||||
|
mock_smtp_cls.return_value.__enter__ = MagicMock(return_value=mock_server)
|
||||||
|
mock_smtp_cls.return_value.__exit__ = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.settings") as mock_settings:
|
||||||
|
mock_settings.email_host = "smtp.example.com"
|
||||||
|
mock_settings.email_sender = None
|
||||||
|
mock_settings.email_username = None
|
||||||
|
mock_settings.email_password = None
|
||||||
|
mock_settings.email_port = 587
|
||||||
|
mock_settings.email_use_tls = False
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth.socket.gethostbyname", return_value="1.2.3.4"):
|
||||||
|
with patch("app.utils.local_auth.smtplib.SMTP", mock_smtp_cls) as smtp_cls:
|
||||||
|
_smtp_send("Subject", "<p>html</p>", "plain", "r@example.com")
|
||||||
|
|
||||||
|
# Verify SMTP was instantiated with correct host
|
||||||
|
smtp_cls.assert_called_once_with("smtp.example.com", 587, timeout=30)
|
||||||
|
mock_server.send_message.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Unit tests: send_verification_email and send_password_reset_email
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_send_verification_email_calls_smtp():
|
||||||
|
"""send_verification_email passes the verify URL in html_body and plain_body."""
|
||||||
|
from app.utils.local_auth import send_verification_email
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth._smtp_send") as mock_smtp:
|
||||||
|
send_verification_email(
|
||||||
|
email="user@example.com",
|
||||||
|
username="testuser",
|
||||||
|
token="abc123token",
|
||||||
|
base_url="https://app.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_smtp.assert_called_once()
|
||||||
|
subject, html_body, plain_body, recipient = mock_smtp.call_args[0]
|
||||||
|
assert "Verify" in subject
|
||||||
|
assert "https://app.example.com/verify-email?token=abc123token" in html_body
|
||||||
|
assert "https://app.example.com/verify-email?token=abc123token" in plain_body
|
||||||
|
assert "testuser" in html_body
|
||||||
|
assert "testuser" in plain_body
|
||||||
|
assert recipient == "user@example.com"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_send_password_reset_email_calls_smtp():
|
||||||
|
"""send_password_reset_email passes the reset URL in html_body and plain_body."""
|
||||||
|
from app.utils.local_auth import send_password_reset_email
|
||||||
|
|
||||||
|
with patch("app.utils.local_auth._smtp_send") as mock_smtp:
|
||||||
|
send_password_reset_email(
|
||||||
|
email="user@example.com",
|
||||||
|
username="testuser",
|
||||||
|
token="resettoken456",
|
||||||
|
base_url="https://app.example.com",
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_smtp.assert_called_once()
|
||||||
|
subject, html_body, plain_body, recipient = mock_smtp.call_args[0]
|
||||||
|
assert "Reset" in subject or "reset" in subject.lower()
|
||||||
|
assert "https://app.example.com/reset-password?token=resettoken456" in html_body
|
||||||
|
assert "https://app.example.com/reset-password?token=resettoken456" in plain_body
|
||||||
|
assert "testuser" in html_body
|
||||||
|
assert "testuser" in plain_body
|
||||||
|
assert recipient == "user@example.com"
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Unit tests: build_session_user edge cases
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_build_session_user_no_display_name_falls_back_to_username():
|
||||||
|
"""build_session_user uses username as 'name' when display_name is None."""
|
||||||
|
user = MagicMock()
|
||||||
|
user.email = "u@example.com"
|
||||||
|
user.username = "uname"
|
||||||
|
user.display_name = None
|
||||||
|
user.is_admin = True
|
||||||
|
|
||||||
|
with patch("app.auth.get_gravatar_url", return_value="https://gravatar.com/test"):
|
||||||
|
result = build_session_user(user)
|
||||||
|
|
||||||
|
assert result["name"] == "uname"
|
||||||
|
assert result["is_admin"] is True
|
||||||
|
assert result["auth_method"] == "local"
|
||||||
|
|||||||
Reference in New Issue
Block a user