mirror of
https://github.com/Mailu/Mailu.git
synced 2025-01-14 02:34:22 +02:00
commit
eccadeca16
@ -18,7 +18,8 @@ install:
|
||||
before_script:
|
||||
- docker-compose -v
|
||||
- docker-compose -f tests/build.yml build
|
||||
|
||||
- sudo -- sh -c 'mkdir -p /mailu && cp -r tests/certs /mailu && chmod 600 /mailu/certs/*'
|
||||
|
||||
script:
|
||||
# test.py, test name and timeout between start and tests.
|
||||
- python tests/compose/test.py core 1
|
||||
|
29
tests/certs/cert.pem
Normal file
29
tests/certs/cert.pem
Normal file
@ -0,0 +1,29 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE/jCCAuagAwIBAgIJAKVnyadXS7SuMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDAeFw0xODEwMzExMDE1MzFaFw0yODEwMjgxMDE1MzFaMBQx
|
||||
EjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
|
||||
ggIBAOQ2ZDqR+YvW5FKykBXz/Ec+jSb0Lv7GYQkT5t+TB1NXuR+QH1LfNWFmXOo7
|
||||
YXcPVXlmcuLDuUldrctdS59fx8dnFu5gRRUqJwZuEQICypsX0rTDtsV6xqZB8c8y
|
||||
2+BztP9OHfPpZdnU1IBx2fDbjpdKUaoAMFMFvyTaEcIyp6aGAhejvJCwc3D8fIJI
|
||||
NhWA2O11sZQHUs7/MHzpu/IHpgutgk8EsNOUNLwB3+9p3IlOlTT6GilIXOYeTzoD
|
||||
hiI6B5BQqXHsRrkao3v0YL6Ekun4hOx3MYx09AZtmuyrlq1mkNueKS5JwKDrXXbq
|
||||
Ta0oyJ18UTZFRwVqApcuR4CA8vuhI9PsoDCvBQH1rW6FyiM4bhybatFJAYjQAODe
|
||||
gwh2p6JWux5C1gaBUubOrKO7o5ePI6s0MmK8ZxrL4PpBYt3B33ztFfjWmVbCTSvP
|
||||
GuQ2Ux73OY2NNxx2aNt4Th0IxrvMdsGLrZsdma2rWa5eTJTAuqbSjI/Wb1zjO0pi
|
||||
pwoxk6f1COFLopo2xgJj6+KKG1nKLfOzQFexcpdq/mpuulcVcLDPJzJTLX3qsgtD
|
||||
iBpm1ozNRT+M7XUavg8aHNfn6S+TcDb5hp+1yZ6obZq/VlA6atk0fuPzf+ndQ0fq
|
||||
YN1jlAIzZXt/Dpc+ObjS09WGDVQXobGesdwA6BH14OV+TxOHAgMBAAGjUzBRMB0G
|
||||
A1UdDgQWBBQy7kA8FbdcFpVU1AoFgzE7Fw1QqDAfBgNVHSMEGDAWgBQy7kA8Fbdc
|
||||
FpVU1AoFgzE7Fw1QqDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC
|
||||
AQBLFlQKxztxm7MtsHs01Pl8/FpKzWekWK1ksf15d8mHBT30OTs+NXaJDuHTGL4r
|
||||
rPeFf3NZ1PZkGRnJCEWur+8e8Y5KwuMAaagneSYXU0gcZfvTidvf865Jiml8xO5x
|
||||
PAo8qTZQCHmYcvJQwBXMkq/2sFJCYeMOLoJdXXbTTe2ZQ/N3MSQbpgWJ8pF7srKU
|
||||
biw2RkNH39QPq9GpWRQGx2gwvZDy2oFG8cM1hJYmz0Y9clpBE0mSqypvA1E8ufKC
|
||||
uaUc0tpPI5H4efeWv/ObnFAJ3DMEmzUnQ8hdM/7cpf6AL8VRm4Wrw112gK7SbSdd
|
||||
mMsUfFIDfyE9vsZ3OC8C8LqXKLwMcm7Fdq0ym0NINtoVW0ukmVJzB78CdWaJ7ux1
|
||||
WqitcnewgiMWuuwuepBmNurZtgDrg+zgMhNpuK0NzYyE+ZReoJIOJCub3SSEsWdl
|
||||
x5aJEYuFYJR5EvmxWeYv5p1GVOTL1TJqW7iRodzRoMc9u2vb0+tCbM5XSZVPul6P
|
||||
QimDui2Ogq0zYNbSkHaUGBpjGDvHYG0zXO2sWrdrAJQMHo8dGEe7FuSuAlWbQdb/
|
||||
xgN4uwejxV6B2e6rjT6YMni+r5Qw0EhNka+Xohw5E68bEcQSrCP8j64qQLAeipuz
|
||||
ImqBTNyyR4WTcL+1HIVM7ZIw3igHH55zo5qTvyjKyZX9Uw==
|
||||
-----END CERTIFICATE-----
|
52
tests/certs/key.pem
Normal file
52
tests/certs/key.pem
Normal file
@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
W3079UVfBB4hkfN2721fqyj+r/0z+R0=
|
||||
-----END PRIVATE KEY-----
|
4
tests/compose/core/00_create_users.sh
Executable file
4
tests/compose/core/00_create_users.sh
Executable file
@ -0,0 +1,4 @@
|
||||
echo "Creating users ..."
|
||||
docker-compose -f tests/compose/core/docker-compose.yml exec admin python manage.py admin admin mailu.io password || exit 1
|
||||
docker-compose -f tests/compose/core/docker-compose.yml exec admin python manage.py user --hash_scheme='SHA512-CRYPT' user mailu.io 'password' || exit 1
|
||||
echo "Admin and user successfully created!"
|
1
tests/compose/core/01_email_test.sh
Executable file
1
tests/compose/core/01_email_test.sh
Executable file
@ -0,0 +1 @@
|
||||
python3 tests/email_test.py message-core
|
@ -29,7 +29,7 @@ SECRET_KEY=HGZCYGVI6FVG31HS
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
@ -29,7 +29,7 @@ SECRET_KEY=JS48Q9KE3B6T97E6
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
6
tests/compose/filters/01_email_test.sh
Executable file
6
tests/compose/filters/01_email_test.sh
Executable file
@ -0,0 +1,6 @@
|
||||
python3 tests/email_test.py message-virus "tests/compose/filters/eicar.com"
|
||||
if [ $? -eq 99 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
1
tests/compose/filters/eicar.com
Normal file
1
tests/compose/filters/eicar.com
Normal file
@ -0,0 +1 @@
|
||||
X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
|
@ -29,7 +29,7 @@ SECRET_KEY=11H6XURLGE7GW3U1
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
@ -29,7 +29,7 @@ SECRET_KEY=V5J4SHRYVW9PZIQU
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
@ -29,7 +29,7 @@ SECRET_KEY=PGGO2JRQ59QV3DW7
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
@ -3,6 +3,7 @@ import os
|
||||
import time
|
||||
import docker
|
||||
from colorama import Fore, Style
|
||||
import subprocess
|
||||
|
||||
# Declare variables for service name and sleep time
|
||||
test_name=sys.argv[1]
|
||||
@ -17,21 +18,22 @@ containers = []
|
||||
# Stop containers
|
||||
def stop(exit_code):
|
||||
print_logs()
|
||||
os.system("docker-compose -f " + compose_file + " -p ${DOCKER_ORG:-mailu} down")
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output("docker-compose -f " + compose_file + " down", shell=True).decode())
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
# Sleep for a defined amount of time
|
||||
def sleep():
|
||||
print(Fore.LIGHTMAGENTA_EX + "Sleeping for " + str(timeout) + "m" + Style.RESET_ALL)
|
||||
time.sleep(timeout*60)
|
||||
|
||||
|
||||
def health_checks():
|
||||
exit_code = 0
|
||||
#Iterating trough all containers dictionary
|
||||
for container in client.containers(all=True):
|
||||
#Perform "docker container inspect" on container based on container ID and save output to a dictionary
|
||||
container_inspect = client.inspect_container(container['Id']) #Dict
|
||||
|
||||
|
||||
if "Health" in container_inspect['State'].keys():
|
||||
if container_inspect['State']['Health']['Status'] == "healthy":
|
||||
print(Fore.GREEN + "Health status for " + container_inspect['Name'].replace("/", "") + " : " + Fore.CYAN + container_inspect['State']['Health']['Status'] + Style.RESET_ALL)
|
||||
@ -46,41 +48,50 @@ def health_checks():
|
||||
if container_inspect['State']['Status'] != "running":
|
||||
print(Fore.RED + "Container " + container_inspect['Name'].replace("/", "") + " state is: " + Fore.YELLOW + container_inspect['State']['Status'] + Style.RESET_ALL)
|
||||
exit_code = 1
|
||||
|
||||
|
||||
#Saving Id, Name and state to a new dictionary
|
||||
containers_dict = {}
|
||||
containers_dict['Name'] = container_inspect['Name'].replace("/", "")
|
||||
containers_dict['Id'] = container_inspect['Id']
|
||||
containers_dict['State'] = container_inspect['State']
|
||||
|
||||
|
||||
#Adding the generated dictionary to a list
|
||||
containers.append(containers_dict)
|
||||
|
||||
|
||||
if exit_code != 0:
|
||||
stop(exit_code)
|
||||
|
||||
|
||||
def print_logs():
|
||||
print("Printing logs ...")
|
||||
#Iterating through docker container inspect list and print logs
|
||||
for container in containers:
|
||||
print(Fore.LIGHTMAGENTA_EX + "Printing logs for: " + Fore.GREEN + container['Name'] + Style.RESET_ALL)
|
||||
os.system('docker container logs ' + container['Name'])
|
||||
|
||||
#Iterating over hooks in test folder and running them
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output('docker container logs ' + container['Name'], shell=True).decode())
|
||||
|
||||
#Iterating over hooks in test folder and running them
|
||||
def hooks():
|
||||
print("Running hooks")
|
||||
print(Fore.LIGHTMAGENTA_EX + "Running hooks" + Style.RESET_ALL)
|
||||
for test_file in sorted(os.listdir(test_path)):
|
||||
if test_file.endswith(".py"):
|
||||
os.system("python3 " + test_path + test_file)
|
||||
elif test_file.endswith(".sh"):
|
||||
os.system("./" + test_path + test_file)
|
||||
|
||||
try:
|
||||
if test_file.endswith(".py"):
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output("python3 " + test_path + test_file, shell=True).decode())
|
||||
elif test_file.endswith(".sh"):
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output("./" + test_path + test_file, shell=True).decode())
|
||||
except subprocess.CalledProcessError as e:
|
||||
sys.stderr.write("[ERROR]: output = %s, error code = %s\n" % (e.output.decode(), e.returncode))
|
||||
stop(1)
|
||||
|
||||
# Start up containers
|
||||
os.system("docker-compose -f " + compose_file + " -p ${DOCKER_ORG:-mailu} up -d ")
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output("docker-compose -f " + compose_file + " up -d", shell=True).decode())
|
||||
print()
|
||||
sleep()
|
||||
print()
|
||||
os.system("docker ps -a")
|
||||
sys.stdout.flush()
|
||||
print(subprocess.check_output("docker ps -a", shell=True).decode())
|
||||
print()
|
||||
health_checks()
|
||||
print()
|
||||
|
@ -29,7 +29,7 @@ SECRET_KEY=XVDDSWOAGVF5J9QJ
|
||||
DOMAIN=mailu.io
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES=mail.mailu.io
|
||||
HOSTNAMES=localhost
|
||||
|
||||
# Postmaster local part (will append the main mail domain)
|
||||
POSTMASTER=admin
|
||||
|
59
tests/email_test.py
Executable file
59
tests/email_test.py
Executable file
@ -0,0 +1,59 @@
|
||||
import smtplib
|
||||
import imaplib
|
||||
import time
|
||||
import sys
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
import ntpath
|
||||
from email.mime.base import MIMEBase
|
||||
from email import encoders
|
||||
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = "admin@mailu.io"
|
||||
msg['To'] = "user@mailu.io"
|
||||
msg['Subject'] = "File Test"
|
||||
msg.attach(MIMEText(sys.argv[1], 'plain'))
|
||||
|
||||
if len(sys.argv) == 3:
|
||||
part = MIMEBase('application', 'octet-stream')
|
||||
part.set_payload((open(sys.argv[2], "rb")).read())
|
||||
encoders.encode_base64(part)
|
||||
part.add_header('Content-Disposition', "attachment; filename=%s" % ntpath.basename(sys.argv[2]))
|
||||
msg.attach(part)
|
||||
|
||||
try:
|
||||
smtp_server = smtplib.SMTP('localhost')
|
||||
smtp_server.set_debuglevel(1)
|
||||
smtp_server.connect('localhost', 587)
|
||||
smtp_server.ehlo()
|
||||
smtp_server.starttls()
|
||||
smtp_server.ehlo()
|
||||
smtp_server.login("admin@mailu.io", "password")
|
||||
|
||||
smtp_server.sendmail("admin@mailu.io", "user@mailu.io", msg.as_string())
|
||||
smtp_server.quit()
|
||||
except:
|
||||
sys.exit(25)
|
||||
|
||||
time.sleep(30)
|
||||
|
||||
try:
|
||||
imap_server = imaplib.IMAP4_SSL('localhost')
|
||||
imap_server.login('user@mailu.io', 'password')
|
||||
except:
|
||||
sys.exit(110)
|
||||
|
||||
stat, count = imap_server.select('inbox')
|
||||
try:
|
||||
stat, data = imap_server.fetch(count[0], '(UID BODY[TEXT])')
|
||||
except :
|
||||
sys.exit(99)
|
||||
|
||||
if sys.argv[1] in str(data[0][1]):
|
||||
print("Success sending and receiving email!")
|
||||
else:
|
||||
print("Failed receiving email with message %s" % sys.argv[1])
|
||||
sys.exit(99)
|
||||
|
||||
imap_server.close()
|
||||
imap_server.logout()
|
Loading…
Reference in New Issue
Block a user