File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ from typing import Protocol
2+ from django .core .mail import EmailMultiAlternatives
3+
4+
5+ class EmailSenderProtocol (Protocol ):
6+ def send_email (self , email : EmailMultiAlternatives ) -> None :
7+ ...
Original file line number Diff line number Diff line change 1+ import logging
2+ from django_email_learning .ports .email_sender_protocol import EmailSenderProtocol
3+ from django .core .mail import EmailMultiAlternatives
4+
5+ logger = logging .getLogger (__name__ )
6+
7+
8+ class DjangoEmailSender (EmailSenderProtocol ):
9+ def _mask_email (self , email_address : str ) -> str :
10+ """Mask email address for logging privacy."""
11+ try :
12+ username , domain = email_address .split ("@" )
13+ masked_username = username [0 ] + "***"
14+ return f"{ masked_username } @{ domain } "
15+ except ValueError :
16+ return "***@***"
17+
18+ def _mask_recipients (self , recipients : list [str ]) -> str :
19+ """Mask all recipient email addresses for logging."""
20+ if not recipients :
21+ return "no recipients"
22+ masked = [self ._mask_email (recipient ) for recipient in recipients ]
23+ return ", " .join (masked )
24+
25+ def send_email (self , email : EmailMultiAlternatives ) -> None :
26+ masked_recipients = self ._mask_recipients (email .to )
27+ try :
28+ logger .info (f"Sending email to { masked_recipients } " )
29+ email .send ()
30+ logger .info (f"Email sent successfully to { masked_recipients } " )
31+ except Exception as e :
32+ logger .error (f"Failed to send email to { masked_recipients } : { str (e )} " )
33+ raise
Original file line number Diff line number Diff line change 1+ from django_email_learning .services .deafults .email_sender import DjangoEmailSender
2+ from django .core .mail import EmailMultiAlternatives
3+ from unittest .mock import Mock
4+ import pytest
5+
6+
7+ @pytest .fixture
8+ def email_sender () -> DjangoEmailSender :
9+ return DjangoEmailSender ()
10+
11+
12+ @pytest .fixture
13+ def email_multi_alternatives () -> EmailMultiAlternatives :
14+ email = Mock (spec = EmailMultiAlternatives )
15+ email .to = ["recipient@example.com" ]
16+ return email
17+
18+
19+ def test_email_sender_logs_success_with_masked_recipients (
20+ email_sender : DjangoEmailSender ,
21+ email_multi_alternatives : EmailMultiAlternatives ,
22+ caplog ,
23+ ):
24+ with caplog .at_level ("INFO" ):
25+ email_sender .send_email (email_multi_alternatives )
26+ assert "Sending email to r***@example.com" in caplog .text
27+
28+
29+ def test_email_sender_logs_failure_with_masked_recipients (
30+ email_sender : DjangoEmailSender ,
31+ email_multi_alternatives : EmailMultiAlternatives ,
32+ caplog ,
33+ ):
34+ email_multi_alternatives .send .side_effect = Exception ("SMTP error" )
35+
36+ with caplog .at_level ("ERROR" ):
37+ with pytest .raises (Exception ):
38+ email_sender .send_email (email_multi_alternatives )
39+
40+ assert "Failed to send email to r***@example.com" in caplog .text
You can’t perform that action at this time.
0 commit comments