Compare commits

..

9 Commits

Author SHA1 Message Date
holger krekel
0885d8f72e apply nami's suggestions (chatmail SSH env var, running --slow in test.sh) 2023-10-16 17:48:15 +02:00
holger krekel
bb567b4fb7 also show the chatmail instance prominently in the test header 2023-10-16 17:09:45 +02:00
holger krekel
fdb01d269c - introduce pytest.mark.slow marker and "--slow" CLI option
- refactor login tests to allow running them against both imap/smtp
2023-10-16 17:00:39 +02:00
holger krekel
00af333694 test works by logging into remote machine and checking the dovecot quota log 2023-10-16 15:19:29 +02:00
holger krekel
c9fd133942 improved test but still not doing what it should 2023-10-16 15:19:29 +02:00
holger krekel
caed6a3754 some fixes but still not quite running through 2023-10-16 15:19:29 +02:00
holger krekel
f71d372491 add a quota test (inspired by nami's #21 ) and try to get postfix/dovecot to implement the limit and the test to pass (it doesn't yet) 2023-10-16 15:19:29 +02:00
missytake
6debf11f6f sieve is not installed and we don't need it 2023-10-16 15:19:29 +02:00
holger krekel
60e1671062 make quota work 2023-10-16 15:19:29 +02:00
8 changed files with 160 additions and 88 deletions

View File

@@ -4,15 +4,14 @@ protocols = imap lmtp
auth_mechanisms = plain auth_mechanisms = plain
auth_verbose = yes
auth_debug = yes
auth_debug_passwords = yes
auth_verbose_passwords = plain
auth_cache_size = 100M
mail_plugins = quota mail_plugins = quota
mail_debug = yes mail_debug = yes
# uncomment this if you want to debug authentication and user creation
#auth_verbose = yes
#auth_debug = yes
#auth_debug_passwords = yes
#auth_verbose_passwords = plain
# Authentication for system users. # Authentication for system users.
passdb { passdb {
driver = dict driver = dict
@@ -80,7 +79,7 @@ plugin {
quota_rule = *:storage=100M quota_rule = *:storage=100M
quota_max_mail_size=30M quota_max_mail_size=30M
quota_grace = 0 quota_grace = 0
quota_over_flag_value = TRUE # quota_over_flag_value = TRUE
} }

View File

@@ -37,6 +37,8 @@ mydestination =
relayhost = relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0 mailbox_size_limit = 0
# maximum 30MB sized messages
message_size_limit = 31457280
recipient_delimiter = + recipient_delimiter = +
inet_interfaces = all inet_interfaces = all
inet_protocols = all inet_protocols = all

View File

@@ -9,7 +9,7 @@
# service type private unpriv chroot wakeup maxproc command + args # service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100) # (yes) (yes) (no) (never) (100)
# ========================================================================== # ==========================================================================
smtp inet n - y - - smtpd smtp inet n - y - - smtpd -v
#smtp inet n - y - 1 postscreen #smtp inet n - y - 1 postscreen
#smtpd pass - - y - - smtpd #smtpd pass - - y - - smtpd
#dnsblog unix - - y - 0 dnsblog #dnsblog unix - - y - 0 dnsblog

View File

@@ -1,15 +1,47 @@
import os import os
import io import io
import random
import subprocess
import imaplib import imaplib
import smtplib import smtplib
import itertools import itertools
import pytest import pytest
import time
def pytest_addoption(parser):
parser.addoption(
"--slow", action="store_true", default=False, help="also run slow tests"
)
def pytest_runtest_setup(item):
markers = list(item.iter_markers(name="slow"))
if markers:
if not item.config.getoption("--slow"):
pytest.skip("skipping slow test, use --slow to run")
@pytest.fixture @pytest.fixture
def maildomain(): def maildomain():
return os.environ.get("CHATMAIL_DOMAIN", "c1.testrun.org") domain = os.environ.get("CHATMAIL_DOMAIN")
if not domain:
pytest.skip("set CHATMAIL_DOMAIN to a ssh-reachable chatmail instance")
return domain
@pytest.fixture
def chatmail_ssh(maildomain):
domain = os.environ.get("CHATMAIL_SSH")
if not domain:
domain = maildomain
return domain
def pytest_report_header():
domain = os.environ.get("CHATMAIL_DOMAIN")
if domain:
text = f"chatmail test instance: {domain}"
return ["-" * len(text), text, "-" * len(text)]
@pytest.fixture @pytest.fixture
@@ -18,6 +50,8 @@ def imap(maildomain):
class ImapConn: class ImapConn:
AuthError = imaplib.IMAP4.error
def __init__(self, host): def __init__(self, host):
self.host = host self.host = host
@@ -36,6 +70,8 @@ def smtp(maildomain):
class SmtpConn: class SmtpConn:
AuthError = smtplib.SMTPAuthenticationError
def __init__(self, host): def __init__(self, host):
self.host = host self.host = host
@@ -48,15 +84,24 @@ class SmtpConn:
self.conn.login(user, password) self.conn.login(user, password)
@pytest.fixture(params=["imap", "smtp"])
def imap_or_smtp(request):
return request.getfixturevalue(request.param)
@pytest.fixture @pytest.fixture
def gencreds(maildomain): def gencreds(maildomain):
prefix = str(time.time())
count = itertools.count() count = itertools.count()
next(count)
def gen(): def gen():
while 1: while 1:
num = next(count) num = next(count)
yield f"user{prefix}_{num}@{maildomain}", f"password{prefix}_{num}" alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
user = "".join(random.choices(alphanumeric, k=10))
user = f"ac{num}_{user}"
password = "".join(random.choices(alphanumeric, k=10))
yield f"{user}@{maildomain}", f"{password}"
return lambda: next(gen()) return lambda: next(gen())
@@ -79,7 +124,13 @@ class ChatmailTestProcess:
def get_liveconfig_producer(self): def get_liveconfig_producer(self):
while 1: while 1:
user, password = self.gencreds() user, password = self.gencreds()
config = {"addr": user, "mail_pw": password} config = {
"addr": user,
"mail_pw": password,
}
# speed up account configuration
config["mail_server"] = self.maildomain
config["send_server"] = self.maildomain
yield config yield config
def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path): def cache_maybe_retrieve_configured_db_files(self, cache_addr, db_target_path):
@@ -99,8 +150,21 @@ def cmfactory(request, maildomain, gencreds, tmpdir, data):
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testproc, data=data) am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testproc, data=data)
yield am yield am
if hasattr(request.node, "rep_call") and request.node.rep_call.failed: if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
if testprocess.pytestconfig.getoption("--extra-info"): if testproc.pytestconfig.getoption("--extra-info"):
logfile = io.StringIO() logfile = io.StringIO()
am.dump_imap_summary(logfile=logfile) am.dump_imap_summary(logfile=logfile)
print(logfile.getvalue()) print(logfile.getvalue())
# request.node.add_report_section("call", "imap-server-state", s) # request.node.add_report_section("call", "imap-server-state", s)
@pytest.fixture
def dovelogreader(chatmail_ssh):
def remote_reader():
popen = subprocess.Popen(
["ssh", f"root@{chatmail_ssh}", "journalctl -f -u dovecot"],
stdout=subprocess.PIPE,
)
while 1:
yield popen.stdout.readline()
return remote_reader

View File

@@ -1,2 +1,3 @@
[pytest] [pytest]
addopts = -vrsx addopts = -vrsx --strict-markers
markers = slow: mark test as slow (requires --slow option to run)

View File

@@ -1,55 +1,36 @@
import pytest import pytest
import imaplib
import smtplib
class TestDovecot: def test_login_basic_functioning(imap_or_smtp, gencreds, lp):
def test_login_ok(self, imap, gencreds): """Test a) that an initial login creates a user automatically
user, password = gencreds() and b) verify we can also login a second time with the same password
imap.connect() and c) that using a different password fails the login."""
imap.login(user, password) user, password = gencreds()
# verify it works on another connection lp.sec(f"login first time with {user} {password}")
imap.connect() imap_or_smtp.connect()
imap.login(user, password) imap_or_smtp.login(user, password)
lp.indent("success")
def test_login_same_password(self, imap, gencreds): lp.sec(f"reconnect and login second time {user} {password}")
"""Test two different users logging in with the same password. imap_or_smtp.connect()
imap_or_smtp.login(user, password)
imap_or_smtp.connect()
lp.sec("success")
This ensures that authentication process does not confuse the users lp.sec("reconnect and verify wrong password fails {user} ")
by using only the password hash as a key. imap_or_smtp.connect()
""" with pytest.raises(imap_or_smtp.AuthError):
user1, password1 = gencreds() imap_or_smtp.login(user, password + "wrong")
user2, _password2 = gencreds()
imap.connect()
imap.login(user1, password1)
imap.connect()
imap.login(user2, password1)
def test_login_fail(self, imap, gencreds):
user, password = gencreds()
imap.connect()
imap.login(user, password)
imap.connect()
with pytest.raises(imaplib.IMAP4.error) as excinfo:
imap.login(user, password + "wrong")
assert "AUTHENTICATIONFAILED" in str(excinfo)
class TestPostfix: def test_login_same_password(imap_or_smtp, gencreds):
def test_login_ok(self, smtp, gencreds): """Test two different users logging in with the same password
user, password = gencreds() to ensure that authentication process does not confuse the users
smtp.connect() by using only the password hash as a key.
smtp.login(user, password) """
# verify it works on another connection user1, password1 = gencreds()
smtp.connect() user2, _ = gencreds()
smtp.login(user, password) imap_or_smtp.connect()
imap_or_smtp.login(user1, password1)
def test_login_fail(self, smtp, gencreds): imap_or_smtp.connect()
user, password = gencreds() imap_or_smtp.login(user2, password1)
smtp.connect()
smtp.login(user, password)
smtp.connect()
with pytest.raises(smtplib.SMTPAuthenticationError) as excinfo:
smtp.login(user, password + "wrong")
assert excinfo.value.smtp_code == 535
assert "authentication failed" in str(excinfo)

View File

@@ -1,45 +1,70 @@
import os.path
import random import random
import time import pytest
class TestMailSending: class TestEndToEndDeltaChat:
"Tests that use Delta Chat accounts on the chat mail instance."
def test_one_on_one(self, cmfactory, lp): def test_one_on_one(self, cmfactory, lp):
"""Test that a DC account can send a message to a second DC account
on the same chat-mail instance."""
ac1, ac2 = cmfactory.get_online_accounts(2) ac1, ac2 = cmfactory.get_online_accounts(2)
chat = cmfactory.get_accepted_chat(ac1, ac2) chat = cmfactory.get_accepted_chat(ac1, ac2)
lp.sec("ac1: prepare and send text message to ac2") lp.sec("ac1: prepare and send text message to ac2")
msg1 = chat.send_text("message0") chat.send_text("message0")
lp.sec("wait for ac2 to receive message") lp.sec("wait for ac2 to receive message")
msg2 = ac2._evtracker.wait_next_incoming_message() msg2 = ac2._evtracker.wait_next_incoming_message()
assert msg2.text == "message0" assert msg2.text == "message0"
def test_exceed_quota(self, cmfactory, lp, tmpdir): @pytest.mark.slow
def test_exceed_quota(self, cmfactory, lp, tmpdir, dovelogreader):
"""This is a very slow test as it needs to upload >100MB of mail data
before quota is exceeded, and thus depends on the speed of the upload.
"""
ac1, ac2 = cmfactory.get_online_accounts(2) ac1, ac2 = cmfactory.get_online_accounts(2)
chat = cmfactory.get_accepted_chat(ac1, ac2) chat = cmfactory.get_accepted_chat(ac1, ac2)
ac2.set_config("download_limit", 1024 * 2) # set download_limit to 2 KB avoid downloading all those 5MB files quota = 1024 * 1024 * 100
attachsize = 1 * 1024 * 1024
lp.sec("ac1: send 25 5 MB files to ac2") num_to_send = quota // attachsize + 2
lp.sec(f"ac1: send {num_to_send} large files to ac2")
lp.indent(f"per-user quota is assumed to be: {quota/(1024*1024)}MB")
alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890" alphanumeric = "abcdefghijklmnopqrstuvwxyz1234567890"
for i in range(25): msgs = []
for i in range(num_to_send):
attachment = tmpdir / f"attachment{i}" attachment = tmpdir / f"attachment{i}"
data = "".join(random.choice(alphanumeric) for i in range(1024))
with open(attachment, "w+") as f: with open(attachment, "w+") as f:
for j in range(1024 * 1024 * 5): for j in range(attachsize // len(data)):
f.write(random.choice(alphanumeric)) f.write(data)
print("Sent out msg", str(i)) msg = chat.send_file(str(attachment))
chat.send_file(str(attachment)) msgs.append(msg)
lp.indent(f"Sent out msg {i}, size {attachsize/(1024*1024)}MB")
ac2.wait_next_incoming_message() lp.sec("ac2: check messages are arriving until quota is reached")
lp.sec("ac2: check that at least one message failed")
failed = False addr = ac2.get_config("addr").lower()
for i in range(25): saved_ok = 0
if chat.get_messages()[i].is_out_failed(): for line in dovelogreader():
failed = True line = line.decode().lower().strip()
print(chat.get_messages()[i].get_message_info()) if addr not in line:
try: # print(line)
assert failed continue
except: if "quota" in line:
import pdb; pdb.set_trace() if "quota exceeded" in line:
if saved_ok < num_to_send // 2:
pytest.fail(
f"quota exceeded too early: after {saved_ok} messages already"
)
lp.indent("good, message sending failed because quota was exceeded")
return
if "saved mail to inbox" in line:
saved_ok += 1
print(f"{saved_ok}: {line}")
if saved_ok >= num_to_send:
break
pytest.fail("sending succeeded although messages should exceed quota")

View File

@@ -4,4 +4,4 @@ pushd chatmaild/src/chatmaild
../../venv/bin/pytest ../../venv/bin/pytest
popd popd
online-tests/venv/bin/pytest online-tests/ -vrx --durations=5 online-tests/venv/bin/pytest online-tests/ -vrx --durations=5 --slow