mirror of
https://github.com/datafolklabs/cement.git
synced 2026-02-06 13:42:03 +00:00
SMTP Extension Bug Fixes and Enhancements
- Resolves Pull Request 669 - Resolves Issue 667 - Resolves Issue 668
This commit is contained in:
parent
1232b8f317
commit
739c5cdcfc
28
.travis.yml
28
.travis.yml
@ -3,23 +3,47 @@ sudo: false
|
||||
script: ./scripts/travis.sh
|
||||
os:
|
||||
- linux
|
||||
|
||||
# env's are redundant, but at global scope additional jobs are created for
|
||||
# each env var which I'm sure has a purpose but don't like
|
||||
matrix:
|
||||
include:
|
||||
- python: "3.8"
|
||||
dist: "xenial"
|
||||
dist: "focal"
|
||||
sudo: true
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=v2.17.3
|
||||
- SMTP_HOST=localhost
|
||||
- SMTP_PORT=1025
|
||||
- python: "3.9"
|
||||
dist: "xenial"
|
||||
dist: "focal"
|
||||
sudo: true
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=v2.17.3
|
||||
- SMTP_HOST=localhost
|
||||
- SMTP_PORT=1025
|
||||
- python: "3.10"
|
||||
dist: "focal"
|
||||
sudo: true
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=v2.17.3
|
||||
- SMTP_HOST=localhost
|
||||
- SMTP_PORT=1025
|
||||
- python: "3.11"
|
||||
dist: "focal"
|
||||
sudo: true
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=v2.17.3
|
||||
- SMTP_HOST=localhost
|
||||
- SMTP_PORT=1025
|
||||
- python: "3.12"
|
||||
dist: "jammy"
|
||||
sudo: true
|
||||
env:
|
||||
- DOCKER_COMPOSE_VERSION=v2.17.3
|
||||
- SMTP_HOST=localhost
|
||||
- SMTP_PORT=1025
|
||||
services:
|
||||
- memcached
|
||||
- redis-server
|
||||
- docker
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@ -8,18 +8,27 @@ Bugs:
|
||||
- [Issue #310](https://github.com/datafolklabs/cement/issues/310)
|
||||
- `[core.foundation]` Quiet mode file is never closed
|
||||
- [Issue #653](https://github.com/datafolklabs/cement/issues/653)
|
||||
- `[ext.smtp]` Ability to Enable TLS without SSL
|
||||
- [Issue #667](https://github.com/datafolklabs/cement/issues/667)
|
||||
- `[ext.smtp]` Empty (wrong) addresses sent when CC/BCC is `None`
|
||||
- [Issue #668](https://github.com/datafolklabs/cement/issues/668)
|
||||
|
||||
|
||||
|
||||
Features:
|
||||
|
||||
- `[utils.fs]` Add Timestamp Support to fs.backup
|
||||
- [Issue #611](https://github.com/datafolklabs/cement/issues/611)
|
||||
- `[ext.smtp]` Support for sending file attachements.
|
||||
- [PR #669](https://github.com/datafolklabs/cement/pull/669)
|
||||
- `[ext.smtp]` Support for sending both Plain Text and HTML
|
||||
- [PR #669](https://github.com/datafolklabs/cement/pull/669)
|
||||
|
||||
|
||||
Refactoring:
|
||||
|
||||
- `[core.plugin]` Deprecate the use of `imp` in favor of `importlib`
|
||||
- [Issue #386](https://github.com/datafolklabs/cement/issues/386)
|
||||
- `[ext.smtp]` Actually test SMTP against a real server (replace mocks)
|
||||
|
||||
|
||||
Misc:
|
||||
@ -34,7 +43,7 @@ Misc:
|
||||
- `[dev]` Add `comply-typing` to make helpers, start working toward typing.
|
||||
- [Issue #599](https://github.com/datafolklabs/cement/issues/661)
|
||||
- [PR #628](https://github.com/datafolklabs/cement/pull/628)
|
||||
|
||||
- `[dev]` Add `mailpit` service to docker-compose development config.
|
||||
|
||||
Deprecations:
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
Cement smtp extension module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import smtplib
|
||||
from email.header import Header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
@ -10,8 +9,10 @@ from email.mime.base import MIMEBase
|
||||
from email.mime.text import MIMEText
|
||||
from email import encoders
|
||||
from ..core import mail
|
||||
from ..utils import fs
|
||||
from ..utils.misc import minimal_logger, is_true
|
||||
|
||||
|
||||
LOG = minimal_logger(__name__)
|
||||
|
||||
|
||||
@ -74,7 +75,10 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
configuration defaults (cc, bcc, etc).
|
||||
|
||||
Args:
|
||||
body: The message body to send
|
||||
body (list): The message body to send. List is treated as:
|
||||
``[<text>, <html>]``. If a single string is passed it will be
|
||||
converted to ``[<text>]``. At minimum, a text version is
|
||||
required.
|
||||
|
||||
Keyword Args:
|
||||
to (list): List of recipients (generally email addresses)
|
||||
@ -82,6 +86,9 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
cc (list): List of CC Recipients
|
||||
bcc (list): List of BCC Recipients
|
||||
subject (str): Message subject line
|
||||
subject_prefix (str): Prefix for message subject line (useful to
|
||||
override if you want to remove/change the default prefix).
|
||||
files (list): List of file paths to attach to the message.
|
||||
|
||||
Returns:
|
||||
bool:``True`` if message is sent successfully, ``False`` otherwise
|
||||
@ -107,7 +114,7 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
if is_true(params['ssl']):
|
||||
server = smtplib.SMTP_SSL(params['host'], params['port'],
|
||||
params['timeout'])
|
||||
LOG.debug("%s : initiating ssl" % self._meta.label)
|
||||
LOG.debug("%s : initiating smtp over ssl" % self._meta.label)
|
||||
|
||||
else:
|
||||
server = smtplib.SMTP(params['host'], params['port'],
|
||||
@ -143,45 +150,61 @@ class SMTPMailHandler(mail.MailHandler):
|
||||
else:
|
||||
subject = params['subject']
|
||||
msg['Subject'] = Header(subject)
|
||||
|
||||
# add body as text and or or as html
|
||||
partText = None
|
||||
partHtml = None
|
||||
if isinstance(body, str):
|
||||
partText = MIMEText(body)
|
||||
elif isinstance(body, list):
|
||||
# handle plain text
|
||||
if len(body) >= 1:
|
||||
partText = MIMEText(body[0], 'plain')
|
||||
|
||||
# handle html
|
||||
if len(body) >= 2:
|
||||
partHtml = MIMEText(body[1], 'html')
|
||||
elif isinstance(body, dict):
|
||||
if 'text' in body:
|
||||
partText = MIMEText(body['text'], 'plain')
|
||||
if 'html' in body:
|
||||
partHtml = MIMEText(body['html'], 'html')
|
||||
|
||||
if partText:
|
||||
msg.attach(partText)
|
||||
if partHtml:
|
||||
msg.attach(partHtml)
|
||||
# loop files
|
||||
|
||||
# attach files
|
||||
if params['files']:
|
||||
for path in params['files']:
|
||||
for in_path in params['files']:
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
# test filename for a seperate attachement disposition name
|
||||
|
||||
# support for alternative file name if its tuple
|
||||
# like (filename.ext=attname.ext)
|
||||
filename = os.path.basename(path)
|
||||
# test for divider in filename
|
||||
i = filename.find('=')
|
||||
# split attname from filename
|
||||
if i < 0:
|
||||
attname = filename
|
||||
if isinstance(in_path, tuple):
|
||||
attname = in_path[0]
|
||||
path = in_path[1]
|
||||
else:
|
||||
attname = filename[i+1:]
|
||||
filename = filename[0:i]
|
||||
# update the filename to read from
|
||||
path = os.path.dirname(path) + '/' + filename
|
||||
attname = in_path
|
||||
path = in_path
|
||||
|
||||
path = fs.abspath(path)
|
||||
|
||||
# filename = os.path.basename(path)
|
||||
|
||||
# # test for divider in filename
|
||||
# i = filename.find('=')
|
||||
|
||||
# # split attname from filename
|
||||
# if i < 0:
|
||||
# attname = filename
|
||||
# else:
|
||||
# attname = filename[i+1:]
|
||||
# filename = filename[0:i]
|
||||
|
||||
# # update the filename to read from
|
||||
# path = fs.join(os.path.dirname(path), filename)
|
||||
|
||||
# add attachment
|
||||
with open(path, 'rb') as file:
|
||||
part.set_payload(file.read())
|
||||
|
||||
# encode and name
|
||||
encoders.encode_base64(part)
|
||||
part.add_header(
|
||||
|
||||
@ -1,80 +1,104 @@
|
||||
version: "3"
|
||||
|
||||
volumes:
|
||||
mailpit-data:
|
||||
|
||||
services:
|
||||
cement: &DEFAULTS
|
||||
image: "cement:dev"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev
|
||||
hostname: cement
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- '.:/src'
|
||||
working_dir: '/src'
|
||||
links:
|
||||
- redis:redis
|
||||
- memcached:memcached
|
||||
environment:
|
||||
REDIS_HOST: redis
|
||||
MEMCACHED_HOST: memcached
|
||||
cement: &DEFAULTS
|
||||
image: "cement:dev"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev
|
||||
hostname: cement
|
||||
stdin_open: true
|
||||
tty: true
|
||||
volumes:
|
||||
- '.:/src'
|
||||
working_dir: '/src'
|
||||
links:
|
||||
- redis:redis
|
||||
- memcached:memcached
|
||||
environment:
|
||||
REDIS_HOST: redis
|
||||
MEMCACHED_HOST: memcached
|
||||
SMTP_HOST: mailpit
|
||||
SMTP_PORT: 1025
|
||||
|
||||
cement-py35:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py35"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py35
|
||||
profiles:
|
||||
- donotstart
|
||||
cement-py35:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py35"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py35
|
||||
profiles:
|
||||
- donotstart
|
||||
|
||||
cement-py36:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py36"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py36
|
||||
profiles:
|
||||
- donotstart
|
||||
cement-py36:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py36"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py36
|
||||
profiles:
|
||||
- donotstart
|
||||
|
||||
cement-py37:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py37"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py37
|
||||
|
||||
cement-py38:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py38"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py38
|
||||
|
||||
cement-py39:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py39"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py39
|
||||
|
||||
cement-py310:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py310"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py310
|
||||
cement-py37:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py37"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py37
|
||||
|
||||
cement-py38:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py38"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py38
|
||||
|
||||
cement-py39:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py39"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py39
|
||||
|
||||
cement-py310:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py310"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py310
|
||||
|
||||
cement-py311:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py311"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py311
|
||||
cement-py311:
|
||||
<<: *DEFAULTS
|
||||
image: "cement:dev-py311"
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev-py311
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
hostname: redis
|
||||
redis:
|
||||
image: redis:latest
|
||||
hostname: redis
|
||||
|
||||
memcached:
|
||||
image: memcached:latest
|
||||
hostname: memcached
|
||||
memcached:
|
||||
image: memcached:latest
|
||||
hostname: memcached
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit
|
||||
container_name: mailpit
|
||||
restart: always
|
||||
volumes:
|
||||
- mailpit-data:/data
|
||||
- ./docker/mailpit:/certificates
|
||||
ports:
|
||||
- 8025:8025
|
||||
- 1025:1025
|
||||
environment:
|
||||
MP_MAX_MESSAGES: 5000
|
||||
MP_DATA_FILE: /data/mailpit.db
|
||||
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
MP_SMTP_TLS_CERT: /certificates/dev-cert.pem
|
||||
MP_SMTP_TLS_KEY: /certificates/dev-key.pem
|
||||
|
||||
35
docker/mailpit/dev-cert.pem
Normal file
35
docker/mailpit/dev-cert.pem
Normal file
@ -0,0 +1,35 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
mjErtgV+K4j0gVg4kaci6kXx1g==
|
||||
-----END CERTIFICATE-----
|
||||
52
docker/mailpit/dev-key.pem
Normal file
52
docker/mailpit/dev-key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
mDpNhf5uD/yZ9rYY6wjJ4nQYBTTJpQ==
|
||||
-----END PRIVATE KEY-----
|
||||
@ -6,6 +6,10 @@ set -e
|
||||
# https://travis-ci.community/t/build-error-for-python-3-7-on-two-different-projects/12895/3
|
||||
# pip install -U importlib_metadata
|
||||
|
||||
docker-compose up -d mailpit 2>&1 >/dev/null
|
||||
|
||||
sleep 10
|
||||
|
||||
rm -f .coverage
|
||||
pip install -r requirements-dev.txt
|
||||
make test
|
||||
|
||||
@ -23,4 +23,4 @@ def key(request):
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def rando(request):
|
||||
yield _rando()
|
||||
yield _rando()[:12]
|
||||
|
||||
@ -1,8 +1,26 @@
|
||||
|
||||
import os
|
||||
import mock
|
||||
import requests
|
||||
import json
|
||||
from time import sleep
|
||||
from cement.utils.test import TestApp
|
||||
from cement.utils.misc import init_defaults
|
||||
|
||||
if 'SMTP_HOST' in os.environ.keys():
|
||||
smtp_host = os.environ['SMTP_HOST']
|
||||
else:
|
||||
smtp_host = 'mailpit'
|
||||
|
||||
|
||||
mailpit_api = f'http://{smtp_host}:8025/api/v1'
|
||||
defaults = init_defaults('mail.smtp')
|
||||
defaults['mail.smtp']['host'] = smtp_host
|
||||
defaults['mail.smtp']['port'] = 1025
|
||||
defaults['mail.smtp']['to'] = 'noreply@localhost'
|
||||
defaults['mail.smtp']['from_addr'] = 'nobody@localhost'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'UNIT TEST >'
|
||||
|
||||
|
||||
class SMTPApp(TestApp):
|
||||
class Meta:
|
||||
@ -10,14 +28,167 @@ class SMTPApp(TestApp):
|
||||
mail_handler = 'smtp'
|
||||
|
||||
|
||||
def test_smtp_defaults():
|
||||
def delete_msg(message_id):
|
||||
payload = {
|
||||
"ids": [
|
||||
message_id
|
||||
]
|
||||
}
|
||||
payload = json.dumps(payload)
|
||||
|
||||
headers = {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json',
|
||||
}
|
||||
requests.delete(f"{mailpit_api}/messages", data=payload, headers=headers)
|
||||
|
||||
|
||||
def test_smtp_send(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send(f"{rando}",
|
||||
to=[f'to-{rando}@localhost'],
|
||||
from_addr=f'from-{rando}@localhost')
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['From']['Address'] == f'from-{rando}@localhost'
|
||||
assert msg['To'][0]['Address'] == f'to-{rando}@localhost'
|
||||
assert msg['Subject'] == f'UNIT TEST > {rando}'
|
||||
assert msg['Attachments'] == 0
|
||||
assert msg['Cc'] == []
|
||||
assert msg['Bcc'] == []
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_html(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
app.mail.send([f"{rando}", f"<body>{rando}</body>"])
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
text_url = f"http://{smtp_host}:8025/view/{msg['ID']}.txt"
|
||||
res_text = requests.get(text_url)
|
||||
assert res_text.content.decode('utf-8') == rando
|
||||
|
||||
html_url = f"http://{smtp_host}:8025/view/{msg['ID']}.html"
|
||||
res_html = requests.get(html_url)
|
||||
assert res_html.content.decode('utf-8') == f"<body>{rando}</body>"
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_cc_bcc(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
cc = [f"cc1-{rando}@localhost", f"cc2-{rando}@localhost"]
|
||||
bcc = [f"bcc1-{rando}@localhost", f"bcc2-{rando}@localhost"]
|
||||
app.mail.send(f"{rando}",
|
||||
cc=cc,
|
||||
bcc=bcc)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
cc_verify = [x['Address'] for x in msg['Cc']]
|
||||
bcc_verify = [x['Address'] for x in msg['Bcc']]
|
||||
assert f"cc1-{rando}@localhost" in cc_verify
|
||||
assert f"cc2-{rando}@localhost" in cc_verify
|
||||
assert f"bcc1-{rando}@localhost" in bcc_verify
|
||||
assert f"bcc2-{rando}@localhost" in bcc_verify
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_files(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
files = []
|
||||
for iter in [1, 2, 3]:
|
||||
_file = f"{tmp.file}-{iter}"
|
||||
with open(_file, 'w') as _open_file:
|
||||
_open_file.write(f"{rando}-{iter}")
|
||||
files.append(_file)
|
||||
|
||||
app.mail.send(f"{rando}", files=files)
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['Attachments'] == 3
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_files_alt_name(rando, tmp):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
app.run()
|
||||
|
||||
files = [(f"alt-filename-{rando}", tmp.file)]
|
||||
app.mail.send(f"{rando}", files=files)
|
||||
sleep(3)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
assert msg['Attachments'] == 1
|
||||
|
||||
res_full = requests.get(f"{mailpit_api}/message/{msg['ID']}")
|
||||
data = res_full.json()
|
||||
assert data['Attachments'][0]['FileName'] == f"alt-filename-{rando}"
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
def test_smtp_tls(rando):
|
||||
defaults['mail.smtp']['subject'] = rando
|
||||
defaults['mail.smtp']['tls'] = True
|
||||
|
||||
with SMTPApp(config_defaults=defaults, debug=True) as app:
|
||||
app.run()
|
||||
app.mail.send(rando)
|
||||
res = requests.get(f"{mailpit_api}/search?query={rando}")
|
||||
data = res.json()
|
||||
assert len(data['messages']) == 1
|
||||
msg = data['messages'][0]
|
||||
|
||||
delete_msg(msg['ID'])
|
||||
|
||||
|
||||
# FIXME: need to replace old mocks with mailpit tests
|
||||
|
||||
def test_mock_smtp_defaults():
|
||||
defaults = init_defaults('mail.smtp')
|
||||
defaults['mail.smtp']['to'] = 'nobody@localhost'
|
||||
defaults['mail.smtp']['to'] = 'test_smtp_defaults@localhost'
|
||||
defaults['mail.smtp']['from_addr'] = 'nobody@localhost'
|
||||
defaults['mail.smtp']['cc'] = 'nobody@localhost'
|
||||
defaults['mail.smtp']['bcc'] = 'nobody@localhost'
|
||||
defaults['mail.smtp']['subject'] = 'Test Email To nobody@localhost'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'PREFIX >'
|
||||
defaults['mail.smtp']['subject'] = 'test_smtp_defaults'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'UNIT TEST >'
|
||||
|
||||
with mock.patch('smtplib.SMTP') as mock_smtp:
|
||||
with SMTPApp(config_defaults=defaults) as app:
|
||||
@ -28,17 +199,38 @@ def test_smtp_defaults():
|
||||
assert instance.send_message.call_count == 1
|
||||
|
||||
|
||||
def test_smtp_ssl_tls():
|
||||
def test_mock_smtp_ssl():
|
||||
defaults = init_defaults('mail.smtp')
|
||||
defaults['mail.smtp']['ssl'] = True
|
||||
defaults['mail.smtp']['tls'] = True
|
||||
defaults['mail.smtp']['port'] = 25
|
||||
defaults['mail.smtp']['subject'] = 'test_smtp_ssl'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'UNIT TEST >'
|
||||
|
||||
with mock.patch('smtplib.SMTP_SSL') as mock_smtp:
|
||||
with SMTPApp(config_defaults=defaults, debug=True) as app:
|
||||
app.run()
|
||||
app.mail.send('TEST MESSAGE',
|
||||
to=['me@localhost'],
|
||||
to=['test_smtp_ssl@localhost'],
|
||||
from_addr='noreply@localhost')
|
||||
|
||||
instance = mock_smtp.return_value
|
||||
assert instance.send_message.call_count == 1
|
||||
|
||||
|
||||
def test_mock_smtp_tls_no_ssl():
|
||||
defaults = init_defaults('mail.smtp')
|
||||
defaults['mail.smtp']['ssl'] = False
|
||||
defaults['mail.smtp']['tls'] = True
|
||||
defaults['mail.smtp']['port'] = 25
|
||||
defaults['mail.smtp']['subject'] = 'test_smtp_tls_no_ssl'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'UNIT TEST >'
|
||||
|
||||
with mock.patch('smtplib.SMTP') as mock_smtp:
|
||||
with SMTPApp(config_defaults=defaults, debug=True) as app:
|
||||
app.run()
|
||||
app.mail.send('TEST MESSAGE',
|
||||
to=['test_smtp_tls_no_ssl@localhost'],
|
||||
from_addr='noreply@localhost')
|
||||
|
||||
instance = mock_smtp.return_value
|
||||
@ -46,7 +238,27 @@ def test_smtp_ssl_tls():
|
||||
assert instance.starttls.call_count == 1
|
||||
|
||||
|
||||
def test_smtp_auth(rando):
|
||||
def test_mock_smtp_tls_over_ssl():
|
||||
defaults = init_defaults('mail.smtp')
|
||||
defaults['mail.smtp']['ssl'] = True
|
||||
defaults['mail.smtp']['tls'] = True
|
||||
defaults['mail.smtp']['port'] = 25
|
||||
defaults['mail.smtp']['subject'] = 'test_smtp_tls_over_ssl'
|
||||
defaults['mail.smtp']['subject_prefix'] = 'UNIT TEST >'
|
||||
|
||||
with mock.patch('smtplib.SMTP_SSL') as mock_smtp:
|
||||
with SMTPApp(config_defaults=defaults, debug=True) as app:
|
||||
app.run()
|
||||
app.mail.send('TEST MESSAGE',
|
||||
to=['test_smtp_tls_no_ssl@localhost'],
|
||||
from_addr='noreply@localhost')
|
||||
|
||||
instance = mock_smtp.return_value
|
||||
assert instance.send_message.call_count == 1
|
||||
assert instance.starttls.call_count == 1
|
||||
|
||||
|
||||
def test_mock_smtp_auth(rando):
|
||||
defaults = init_defaults(rando, 'mail.smtp')
|
||||
defaults[rando]['debug'] = True
|
||||
defaults['mail.smtp']['auth'] = True
|
||||
|
||||
Loading…
Reference in New Issue
Block a user