From c53604f9f512cae250032a9d999cedc377c1d48e Mon Sep 17 00:00:00 2001 From: Roman Hergenreder Date: Thu, 2 Apr 2020 13:54:54 +0200 Subject: [PATCH] Unit Tests --- test/.gitignore | 110 ++++++++++++++++++++++++++++++++++++++++++ test/.htaccess | 1 + test/README.md | 39 +++++++++++++++ test/installTest.py | 87 +++++++++++++++++++++++++++++++++ test/requirements.txt | 2 + test/test.py | 97 +++++++++++++++++++++++++++++++++++++ 6 files changed, 336 insertions(+) create mode 100644 test/.gitignore create mode 100644 test/.htaccess create mode 100644 test/README.md create mode 100644 test/installTest.py create mode 100644 test/requirements.txt create mode 100644 test/test.py diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..aab7ea0 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,110 @@ + +# Created by https://www.gitignore.io/api/python +# Edit at https://www.gitignore.io/?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/python diff --git a/test/.htaccess b/test/.htaccess new file mode 100644 index 0000000..d3223d4 --- /dev/null +++ b/test/.htaccess @@ -0,0 +1 @@ +DENY FROM ALL diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..7f6122d --- /dev/null +++ b/test/README.md @@ -0,0 +1,39 @@ +## Web-Base Test Suite + +### Introduction + +This script performs database and API tests on a clean local environment. It assumes, the web-base is running on http://localhost/. The test tool can either +use an existing database or create a temporary database (recommended). + +### Usage + +To use this tool, some requirements must be installed. This can be done using: `pip install -r < requirements.txt` + +``` +usage: test.py [-h] [--username USERNAME] [--password PASSWORD] [--host HOST] + [--port PORT] [--database DATABASE] [--force] + DBMS + +Web-Base database test suite + +positional arguments: + DBMS the dbms to setup, must be one of: mysql, postgres, + oracle + +optional arguments: + -h, --help show this help message and exit + --username USERNAME, -u USERNAME + the username used for connecting to the dbms, default: + root + --password PASSWORD, -p PASSWORD + the password used for connecting to the dbms, default: + (empty) + --host HOST, -H HOST the host where the dbms is running on, default: + localhost + --port PORT, -P PORT the port where the dbms is running on, default: + (depends on dbms) + --database DATABASE, -d DATABASE + the name of the database for the test suite, default: + randomly chosen and created + --force Delete existing configuration files +``` diff --git a/test/installTest.py b/test/installTest.py new file mode 100644 index 0000000..724b2db --- /dev/null +++ b/test/installTest.py @@ -0,0 +1,87 @@ +import unittest +import requests +import json +import re +import string +import random + +class InstallTestCase(unittest.TestCase): + + def __init__(self, args): + super().__init__("test_install") + self.args = args + self.session = requests.Session() + self.url = "http://localhost/" + + keywords = ["Fatal error", "Warning", "Notice", "Parse error", "Deprecated"] + self.phpPattern = re.compile("(%s):" % ("|".join(keywords))) + + def randomString(self, length): + letters = string.ascii_lowercase + string.ascii_uppercase + string.digits + return ''.join(random.choice(letters) for i in range(length)) + + def httpError(self, res): + return "Server returned: %d %s" % (res.status_code, res.reason) + + def getPhpErrors(self, res): + return [line for line in res.text.split("\n") if self.phpPattern.search(line)] + + def test_install(self): + + # Test Connection + res = self.session.get(self.url) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + + # Database Setup + res = self.session.post(self.url, data=vars(self.args)) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + + # Create User + valid_username = self.randomString(16) + valid_password = self.randomString(16) + + # 1. Invalid username + for username in ["a", "a"*33]: + res = self.session.post(self.url, data={ "username": username, "password": "123456", "confirmPassword": "123456" }) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + obj = json.loads(res.text) + self.assertEquals(False, obj["success"]) + self.assertEquals("The username should be between 5 and 32 characters long", obj["msg"]) + + # 2. Invalid password + res = self.session.post(self.url, data={ "username": valid_username, "password": "1", "confirmPassword": "1" }) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + obj = json.loads(res.text) + self.assertEquals(False, obj["success"]) + self.assertEquals("The password should be at least 6 characters long", obj["msg"]) + + # 3. Passwords do not match + res = self.session.post(self.url, data={ "username": valid_username, "password": "1", "confirmPassword": "2" }) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + obj = json.loads(res.text) + self.assertEquals(False, obj["success"]) + self.assertEquals("The given passwords do not match", obj["msg"]) + + # 4. User creation OK + res = self.session.post(self.url, data={ "username": valid_username, "password": valid_password, "confirmPassword": valid_password }) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + obj = json.loads(res.text) + self.assertEquals(True, obj["success"]) + + # Mail: SKIP + res = self.session.post(self.url, data={ "skip": "true" }) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) + obj = json.loads(res.text) + self.assertEquals(True, obj["success"]) + + # Creation successful: + res = self.session.get(self.url) + self.assertEquals(200, res.status_code, self.httpError(res)) + self.assertEquals([], self.getPhpErrors(res)) diff --git a/test/requirements.txt b/test/requirements.txt new file mode 100644 index 0000000..eb910f7 --- /dev/null +++ b/test/requirements.txt @@ -0,0 +1,2 @@ +requests==2.23.0 +mysql_connector_repackaged==0.3.1 diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..fe61cca --- /dev/null +++ b/test/test.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import random +import string +import unittest +import mysql.connector + +from installTest import InstallTestCase + +CONFIG_FILES = ["../core/Configuration/Database.class.php","../core/Configuration/JWT.class.php","../core/Configuration/Mail.class.php"] + +def randomName(length): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(length)) + +def performTest(args): + suite = unittest.TestSuite() + suite.addTest(InstallTestCase(args)) + runner = unittest.TextTestRunner() + runner.run(suite) + +def testMysql(args): + + # Create a temporary database + cursor = None + database = None + connection = None + if args.database is None: + args.database = "webbase_test_%s" % randomName(6) + config = { + "host": args.host, + "user": args.username, + "passwd": args.password, + "port": args.port + } + + print("[ ] Connecting to dbms…") + connection = mysql.connector.connect(**config) + print("[+] Success") + cursor = connection.cursor() + print("[ ] Creating temporary databse %s" % args.database) + cursor.execute("CREATE DATABASE %s" % args.database) + print("[+] Success") + + # perform test + try: + args.type = "mysql" + performTest(args) + finally: + if cursor is not None: + print("[ ] Deleting temporary database") + cursor.execute("DROP DATABASE %s" % args.database) + cursor.close() + print("[+] Success") + + if connection is not None: + print("[ ] Closing connection…") + connection.close() + +if __name__ == "__main__": + + supportedDbms = { + "mysql": 3306, + "postgres": 5432, + "oracle": 1521 + } + + parser = argparse.ArgumentParser(description='Web-Base database test suite') + parser.add_argument('dbms', metavar='DBMS', type=str, help='the dbms to setup, must be one of: %s' % ", ".join(supportedDbms.keys())) + parser.add_argument('--username', '-u', type=str, help='the username used for connecting to the dbms, default: root', default='root') + parser.add_argument('--password', '-p', type=str, help='the password used for connecting to the dbms, default: (empty)', default='') + parser.add_argument('--host', '-H', type=str, help='the host where the dbms is running on, default: localhost', default='localhost') + parser.add_argument('--port', '-P', type=int, help='the port where the dbms is running on, default: (depends on dbms)') + parser.add_argument('--database', '-d', type=str, help='the name of the database for the test suite, default: randomly chosen and created') + parser.add_argument('--force', action='store_const', help='Delete existing configuration files', const=True) + + args = parser.parse_args() + if args.dbms not in supportedDbms: + print("Unsupported dbms. Supported values: %s" % ", ".join(supportedDbms.keys())) + exit(1) + + for f in CONFIG_FILES: + if os.path.isfile(f): + if not args.force: + print("File %s exists. The testsuite is required to perform tests on a clean environment. Specify --force to delete those files" % f) + exit(1) + else: + os.remove(f) + + if args.port is None: + args.port = supportedDbms[args.dbms] + + if args.dbms == "mysql": + testMysql(args)