-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathtest_send_async.py
More file actions
144 lines (117 loc) · 5.08 KB
/
Copy pathtest_send_async.py
File metadata and controls
144 lines (117 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import emails
from emails.backend.smtp.aio_backend import AsyncSMTPBackend
from .helpers import common_email_data
@pytest.fixture
def mock_smtp():
"""Patch aiosmtplib.SMTP so no real connection is made."""
with patch('emails.backend.smtp.aio_client.aiosmtplib.SMTP') as mock_cls:
instance = MagicMock()
instance.connect = AsyncMock()
instance.ehlo = AsyncMock()
instance.helo = AsyncMock()
instance._ehlo_or_helo_if_needed = AsyncMock()
instance.login = AsyncMock()
instance.quit = AsyncMock()
instance.close = MagicMock()
instance.mail = AsyncMock(return_value=MagicMock(code=250, message='OK'))
instance.rcpt = AsyncMock(return_value=MagicMock(code=250, message='OK'))
instance.data = AsyncMock(return_value=MagicMock(code=250, message='OK'))
instance.rset = AsyncMock()
instance.is_ehlo_or_helo_needed = True
instance.supports_esmtp = True
instance.supports_extension = MagicMock(return_value=False)
mock_cls.return_value = instance
yield instance
@pytest.mark.asyncio
async def test_send_async_with_dict(mock_smtp):
"""send_async(smtp={...}) creates backend, sends, and closes."""
msg = emails.html(**common_email_data(subject='Async dict test'))
response = await msg.send_async(smtp={'host': 'localhost', 'port': 2525})
assert response is not None
assert response.success
sent_message = mock_smtp.data.await_args.args[0]
assert b'\r\n' in sent_message
assert b'\n' not in sent_message.replace(b'\r\n', b'')
# Backend should have been closed (quit called)
mock_smtp.quit.assert_awaited_once()
@pytest.mark.asyncio
async def test_send_async_with_backend_object(mock_smtp):
"""send_async(smtp=AsyncSMTPBackend(...)) uses the provided backend."""
msg = emails.html(**common_email_data(subject='Async backend test'))
backend = AsyncSMTPBackend(host='localhost', port=2525)
response = await msg.send_async(smtp=backend)
assert response is not None
assert response.success
# Backend should NOT have been closed (caller manages lifecycle)
mock_smtp.quit.assert_not_awaited()
# Clean up
await backend.close()
@pytest.mark.asyncio
async def test_send_async_with_default_smtp(mock_smtp):
"""send_async() without smtp uses default localhost:25."""
msg = emails.html(**common_email_data(subject='Async default test'))
response = await msg.send_async()
assert response is not None
assert response.success
def test_sync_send_unchanged():
"""message.send() still works the sync path (uses SMTPBackend, not async)."""
msg = emails.html(**common_email_data(subject='Sync unchanged test'))
mock_backend = MagicMock()
mock_response = MagicMock(success=True)
mock_backend.sendmail.return_value = mock_response
response = msg.send(smtp=mock_backend)
assert response is mock_response
assert response.success
mock_backend.sendmail.assert_called_once()
@pytest.mark.asyncio
async def test_send_async_with_render(mock_smtp):
"""send_async() applies render data before sending."""
msg = emails.html(**common_email_data(subject='Render test'))
response = await msg.send_async(
smtp={'host': 'localhost', 'port': 2525},
render={'name': 'World'},
)
assert response is not None
assert response.success
@pytest.mark.asyncio
async def test_send_async_with_to_override(mock_smtp):
"""send_async(to=...) overrides mail_to."""
msg = emails.html(**common_email_data(subject='To override'))
response = await msg.send_async(
to='other@example.com',
smtp={'host': 'localhost', 'port': 2525},
)
assert response is not None
assert response.success
# Verify the override address was used as recipient
assert 'other@example.com' in response.to_addrs
@pytest.mark.asyncio
async def test_send_async_invalid_smtp_type():
"""send_async() raises ValueError for invalid smtp type."""
msg = emails.html(**common_email_data(subject='Invalid smtp'))
with pytest.raises(ValueError, match="smtp must be a dict"):
await msg.send_async(smtp=42)
@pytest.mark.asyncio
async def test_send_async_no_from_raises():
"""send_async() raises when no from address."""
msg = emails.html(
subject='No from',
mail_to='to@example.com',
html='<p>Hello</p>',
)
with pytest.raises((ValueError, TypeError)):
await msg.send_async(smtp={'host': 'localhost', 'port': 2525})
@pytest.mark.asyncio
async def test_send_async_closes_on_error(mock_smtp):
"""send_async(smtp={...}) closes backend even if sendmail fails."""
mock_smtp.mail.side_effect = Exception('send failed')
msg = emails.html(**common_email_data(subject='Error close test'))
with pytest.raises(Exception, match='send failed'):
await msg.send_async(
smtp={'host': 'localhost', 'port': 2525, 'fail_silently': False},
)
# Backend should still have been closed
mock_smtp.quit.assert_awaited()