1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2025-04-19 02:42:15 +00:00
This commit is contained in:
Wolfgang Steitz 2020-08-02 22:21:08 -03:00 committed by GitHub
commit dfdc150f05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 447 additions and 0 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ tools/__pycache__/
externals/
.env
.vagrant
*.pyc

24
test/README.md Normal file
View File

@ -0,0 +1,24 @@
This is the mailinabox test suite. It uses the excellent pytest module to check the functionality of the different services.
# Usage
start-up a vagrant box
vagrant up
install test requirements (using a virtualenv is highly recommended)
pip install -r requirements.txt
run the tests
pytest
to just run a subset of the tests (e.g. the ssh related ones):
pytest test_ssh.py
# Contributing
pytest auto-discovers all tests in this directory. The test functions need to be named "test_..." and there needs to be at least one assert statement.

5
test/common.py Normal file
View File

@ -0,0 +1,5 @@
import uuid
def random_id():
return uuid.uuid4().hex[:8]

3
test/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pytest>=3.0.5
pyCardDAV==0.7.0
caldav==0.5

11
test/settings.py Normal file
View File

@ -0,0 +1,11 @@
import socket
TEST_SERVER = '127.0.0.1'
TEST_DOMAIN = 'mailinabox.lan'
TEST_PORT = 2222
TEST_PASSWORD = '1234'
TEST_USER = 'me'
TEST_ADDRESS = TEST_USER + '@' + TEST_DOMAIN
TEST_SENDER = "someone@example.com"
socket.setdefaulttimeout(5)

44
test/test_backup.py Normal file
View File

@ -0,0 +1,44 @@
import pytest
from time import sleep
from subprocess import check_call, check_output
import smtplib
from settings import *
from test_mail import new_message, check_imap_received
def test_backup_mail():
# send a mail, to ensure we have something to backup
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 587)
s.starttls()
s.login(TEST_ADDRESS, TEST_PASSWORD)
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
s.quit()
# trigger a backup
sleep(2)
cmd_ssh = "sshpass -p vagrant ssh vagrant@{} -p {} ".format(TEST_SERVER, TEST_PORT)
cmd_count = cmd_ssh + "ls -l /home/user-data/backup/encrypted | wc -l"
num_backup_files = int(check_output(cmd_count, shell=True))
cmd = cmd_ssh + "sudo /vagrant/management/backup.py"
check_call(cmd, shell=True)
num_backup_files_new = int(check_output(cmd_count, shell=True))
assert num_backup_files_new > num_backup_files
# delete mail
assert check_imap_received(subject)
assert not check_imap_received(subject)
# restore backup
path = "/home/user-data"
passphrase = "export PASSPHRASE=\$(sudo cat /home/user-data/backup/secret_key.txt) &&"
# extract to temp directory
restore = "sudo -E duplicity restore --force file://{0}/backup/encrypted {0}/restore &&".format(path)
# move restored backup using rsync, because it allows to overwrite files
move = "sudo rsync -av {0}/restore/* {0}/ &&".format(path)
rm = "sudo rm -rf {0}/restore/".format(path)
check_call(cmd_ssh + "\"" + passphrase + restore + move + rm + "\"", shell=True)
# check the mail is there again
assert check_imap_received(subject)

68
test/test_caldav.py Normal file
View File

@ -0,0 +1,68 @@
import pytest
import caldav
from time import sleep
from settings import *
from common import random_id
def connect():
url = "https://" + TEST_DOMAIN + "/cloud/remote.php/dav/calendars/" + TEST_ADDRESS + "/personal/"
client = caldav.DAVClient(url, username=TEST_ADDRESS, password=TEST_PASSWORD, ssl_verify_cert=False)
principal = client.principal()
calendars = principal.calendars()
return client, calendars[0]
vcal = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
BEGIN:VEVENT
UID:{}
DTSTAMP:20170510T182145Z
DTSTART:20170512T170000Z
DTEND:20170512T180000Z
SUMMARY: this is a sample event
END:VEVENT
END:VCALENDAR
"""
def create_event():
uid = random_id()
event = vcal.format(uid)
return event, uid
def event_exists(uid):
c, cal = connect()
try:
event = cal.event(uid)
return True
except caldav.lib.error.NotFoundError:
return False
def test_addremove_event():
c, cal = connect()
event, uid = create_event()
cal.add_event(event)
assert event_exists(uid)
# now delete the event again
event = cal.event(uid)
event.delete()
sleep(3)
assert (not event_exists(uid))
def test_addremove_calendar():
c, _ = connect()
cal_id = random_id()
cal = c.principal().make_calendar(name="test", cal_id=cal_id)
matching = [calendar for calendar in c.principal().calendars() if cal_id in str(calendar.url)]
assert len(matching) == 1
c.delete(cal.url)
matching = [calendar for calendar in c.principal().calendars() if cal_id in str(calendar.url)]
assert len(matching) == 0

48
test/test_carddav.py Normal file
View File

@ -0,0 +1,48 @@
import pytest
from pycarddav import carddav
from settings import *
test_vcf = """
BEGIN:VCARD
VERSION:3.0
EMAIL;TYPE=PREF:foo@example.com
N:John Doe;;;;
FN:John Doe
REV:2012-08-02T21:16:14+00:00
PRODID:-//ownCloud//NONSGML Contacts 0.2//EN
UID:c292c7212b
END:VCARD
"""
def connect():
url = "https://" + TEST_DOMAIN + "/cloud/remote.php/carddav/addressbooks/" + TEST_ADDRESS + "/contacts/"
return carddav.PyCardDAV(url, user=TEST_ADDRESS, passwd=TEST_PASSWORD, verify=False, write_support=True)
def test_adddelete_contact():
c = connect()
abook = c.get_abook()
prev_len = len(abook)
url, etag = c.upload_new_card(test_vcf)
abook = c.get_abook()
assert len(abook) == prev_len + 1
c.delete_vcard(url, etag)
abook = c.get_abook()
assert len(abook) == prev_len
def test_update_contact():
c = connect()
url, etag = c.upload_new_card(test_vcf)
card = c.get_vcard(url)
new_card = card.replace("John Doe", "Jane Doe")
c.update_vcard(new_card, url, etag)
card = c.get_vcard(url)
assert "John Doe" not in card
assert "Jane Doe" in card

170
test/test_mail.py Normal file
View File

@ -0,0 +1,170 @@
from time import sleep
import requests
import os
import pytest
import imaplib
import poplib
import smtplib
from email.mime.text import MIMEText
from settings import *
from common import random_id
def new_message(from_email, to_email):
"""Creates an email (headers & body) with a random subject"""
msg = MIMEText('Testing')
msg['Subject'] = random_id()
msg['From'] = from_email
msg['To'] = to_email
return msg.as_string(), msg['subject']
def check_imap_received(subject):
"""Connects with IMAP and asserts the existence of an email, then deletes it"""
sleep(3)
# Login to IMAP
m = imaplib.IMAP4_SSL(TEST_DOMAIN, 993)
m.login(TEST_ADDRESS, TEST_PASSWORD)
m.select()
# check the message exists
typ, data = m.search(None, '(SUBJECT \"{}\")'.format(subject))
res = len(data[0].split()) == 1
if res:
m.store(data[0].strip(), '+FLAGS', '\\Deleted')
m.expunge()
m.close()
m.logout()
return res
def assert_pop3_received(subject):
"""Connects with POP3S and asserts the existence of an email, then deletes it"""
sleep(3)
# Login to POP3
mail = poplib.POP3_SSL(TEST_DOMAIN, 995)
mail.user(TEST_ADDRESS)
mail.pass_(TEST_PASSWORD)
# Assert the message exists
num = len(mail.list()[1])
resp, text, octets = mail.retr(num)
assert "Subject: " + subject in text
# Delete it and log out
mail.dele(num)
mail.quit()
def test_imap_requires_ssl():
"""IMAP without SSL is NOT available"""
with pytest.raises(socket.timeout):
imaplib.IMAP4(TEST_DOMAIN, 143)
def test_pop3_requires_ssl():
"""POP3 without SSL is NOT available"""
with pytest.raises(socket.timeout):
poplib.POP3(TEST_DOMAIN, 110)
def test_smtps():
"""Email sent from an MUA via SMTPS is delivered"""
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 587)
s.starttls()
s.login(TEST_ADDRESS, TEST_PASSWORD)
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
s.quit()
assert check_imap_received(subject)
def test_smtps_tag():
"""Email sent to address with tag is delivered"""
mail_address = TEST_ADDRESS.replace("@", "+sometag@")
msg, subject = new_message(TEST_ADDRESS, mail_address)
s = smtplib.SMTP(TEST_DOMAIN, 587)
s.starttls()
s.login(TEST_ADDRESS, TEST_PASSWORD)
s.sendmail(TEST_ADDRESS, [mail_address], msg)
s.quit()
assert check_imap_received(subject)
def test_smtps_requires_auth():
"""SMTPS with no authentication is rejected"""
import smtplib
s = smtplib.SMTP(TEST_DOMAIN, 587)
s.starttls()
#FIXME why does this work without login?
with pytest.raises(smtplib.SMTPRecipientsRefused):
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], 'Test')
s.quit()
def test_smtp():
"""Email sent from an MTA is delivered"""
import smtplib
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 25)
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
s.quit()
assert check_imap_received(subject)
def test_smtp_tls():
"""Email sent from an MTA via SMTP+TLS is delivered"""
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 25)
s.starttls()
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
s.quit()
assert check_imap_received(subject)
def test_smtp_headers():
"""Email sent from an MTA via SMTP+TLS has TLS headers"""
# Send a message to root
msg, subject = new_message(TEST_SENDER, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 25)
s.starttls()
s.sendmail(TEST_SENDER, [TEST_ADDRESS], msg)
s.quit()
sleep(3)
# Get the message
m = imaplib.IMAP4_SSL(TEST_DOMAIN, 993)
m.login(TEST_ADDRESS, TEST_PASSWORD)
m.select()
_, res = m.search(None, '(SUBJECT \"{}\")'.format(subject))
_, data = m.fetch(res[0], '(RFC822)')
assert 'ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)' in data[0][1]
# Clean up
m.store(res[0].strip(), '+FLAGS', '\\Deleted')
m.expunge()
m.close()
m.logout()
def test_pop3s():
"""Connects with POP3S and asserts the existance of an email"""
msg, subject = new_message(TEST_ADDRESS, TEST_ADDRESS)
s = smtplib.SMTP(TEST_DOMAIN, 587)
s.starttls()
s.login(TEST_ADDRESS, TEST_PASSWORD)
s.sendmail(TEST_ADDRESS, [TEST_ADDRESS], msg)
s.quit()
assert_pop3_received(subject)

13
test/test_ssh.py Normal file
View File

@ -0,0 +1,13 @@
import pytest
from settings import *
def test_ssh_banner():
"""SSH is responding with its banner"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TEST_SERVER, TEST_PORT))
data = s.recv(1024)
s.close()
assert data.startswith("SSH-2.0-OpenSSH")

60
test/test_web.py Normal file
View File

@ -0,0 +1,60 @@
from time import sleep
import requests
import os
import pytest
from settings import *
def test_web_hosting_http():
"""web hosting is redirecting to https"""
url = 'http://' + TEST_DOMAIN
r = requests.get(url, verify=False)
# We should be redirected to https
assert r.history[0].status_code == 301
assert r.url == url.replace("http", "https") + "/"
assert r.status_code == 200
assert "this is a mail-in-a-box" in r.content
def test_admin_http():
"""Admin page is redirecting to https"""
url = 'http://' + TEST_DOMAIN + "/admin"
r = requests.get(url, verify=False)
# We should be redirected to https
assert r.history[0].status_code == 301
assert r.url == url.replace("http", "https")
assert r.status_code == 200
assert "Log in here for your Mail-in-a-Box control panel" in r.content
def test_webmail_http():
"""Webmail is redirecting to https and displaying login page"""
url = 'http://' + TEST_DOMAIN + "/mail"
r = requests.get(url, verify=False)
# We should be redirected to https
assert r.history[0].status_code == 301
assert r.url == url.replace("http", "https") + "/"
# 200 - We should be at the login page
assert r.status_code == 200
assert 'Welcome to ' + TEST_DOMAIN + ' Webmail' in r.content
def test_owncloud_http():
"""ownCloud is redirecting to https and displaying login page"""
url = 'http://' + TEST_DOMAIN + '/cloud'
r = requests.get(url, verify=False)
# We should be redirected to https
assert r.history[0].status_code == 301
assert r.url == url.replace("http", "https") + "/index.php/login"
# 200 - We should be at the login page
assert r.status_code == 200
assert 'ownCloud' in r.content