Coverage for tests/utils/test_email.py: 100%

60 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-18 22:11 +0000

1import unittest 

2from unittest.mock import patch, MagicMock 

3from webapp.utils.emailer import Emailer, SMTPConfig, EmailerError 

4 

5 

6class TestEmailer(unittest.TestCase): 

7 def setUp(self): 

8 self.smtp_config = SMTPConfig( 

9 host="smtp.test.com", 

10 port=587, 

11 username="testuser@canonical.com", 

12 password="testpass", 

13 domain="example.com", 

14 ) 

15 self.emailer = Emailer(self.smtp_config) 

16 

17 @patch("webapp.utils.emailer.Thread") 

18 @patch("smtplib.SMTP") 

19 def test_send_email_success(self, mock_smtp, mock_thread): 

20 subject = "Test subject" 

21 body = "Test body" 

22 to_email = "recipient@test.com" 

23 

24 mock_server = MagicMock() 

25 mock_smtp.return_value.__enter__.return_value = mock_server 

26 

27 self.emailer.send_email(subject, body, to_email) 

28 

29 target = mock_thread.call_args.kwargs["target"] 

30 args = mock_thread.call_args.kwargs["args"] 

31 target(*args) 

32 

33 mock_smtp.assert_called_once_with( 

34 self.smtp_config.host, self.smtp_config.port 

35 ) 

36 mock_server.starttls.assert_called_once() 

37 mock_server.login.assert_called_with( 

38 self.smtp_config.username, self.smtp_config.password 

39 ) 

40 

41 @patch("smtplib.SMTP") 

42 def test_send_email_template_success(self, mock_smtp): 

43 with patch("webapp.utils.emailer.render_template") as mock_render: 

44 with patch("webapp.utils.emailer.Thread") as mock_thread: 

45 mock_render.return_value = "<h1>Hello</h1>" 

46 mock_server = MagicMock() 

47 mock_smtp.return_value.__enter__.return_value = mock_server 

48 

49 self.emailer.send_email_template( 

50 to_email="recipient@test.com", 

51 subject="Test Template", 

52 template_path="template.html", 

53 context={"name": "Test"}, 

54 ) 

55 

56 target = mock_thread.call_args.kwargs["target"] 

57 args = mock_thread.call_args.kwargs["args"] 

58 target(*args) 

59 

60 mock_render.assert_called_once_with( 

61 "template.html", **{"name": "Test"} 

62 ) 

63 mock_server.send_message.assert_called() 

64 

65 def test_validate_config_missing_fields(self): 

66 incomplete_config = SMTPConfig( 

67 host=None, 

68 port=587, 

69 username=None, 

70 password=None, 

71 domain="example.com", 

72 ) 

73 emailer = Emailer(incomplete_config) 

74 self.assertFalse(emailer.is_configured) 

75 

76 @patch("smtplib.SMTP") 

77 def test_send_raises_smtp_exception(self, mock_smtp): 

78 mock_smtp.side_effect = Exception("SMTP failure") 

79 subject = "Test" 

80 body = "Body" 

81 to_email = "to@example.com" 

82 

83 with self.assertRaises(EmailerError): 

84 self.emailer._send(subject, body, to_email) 

85 

86 def test_create_message_addresses(self): 

87 message = self.emailer._create_message( 

88 subject="Subject", 

89 body="Body", 

90 to_email=["a@test.com", "b@test.com"], 

91 ) 

92 self.assertIn("From", message) 

93 self.assertIn("To", message) 

94 self.assertEqual(message["From"], "testuser@canonical.com") 

95 

96 emailer2 = Emailer( 

97 SMTPConfig( 

98 host="host", 

99 port=587, 

100 username="simpleuser", 

101 password="pass", 

102 domain="example.com", 

103 ) 

104 ) 

105 message2 = emailer2._create_message( 

106 "Sub", "Body", "someone@example.com" 

107 ) 

108 self.assertTrue(message2["From"].startswith("noreply+")) 

109 

110 def test_send_email_no_configuration(self): 

111 no_config = SMTPConfig( 

112 host=None, 

113 port=587, 

114 username=None, 

115 password=None, 

116 domain="example.com", 

117 ) 

118 emailer = Emailer(no_config) 

119 emailer.send_email("subject", "body", "to@example.com") 

120 self.assertFalse(emailer.is_configured)