From 842ad3ff446619278807367607048de578cedc3b Mon Sep 17 00:00:00 2001 From: Kyattsukuro Date: Thu, 2 Oct 2025 10:48:30 +0200 Subject: [PATCH] tast tow --- .vscode/settings.json | 3 +- README.md | 2 +- .../simple_chat_testprotokoll.pdf | Bin docs/unittest_sample.sqlite | Bin 0 -> 16384 bytes simple_chat_api/__init__.py | 3 +- simple_chat_api/db_handler/__init__.py | 0 .../{ => db_handler}/db_handler.py | 2 + simple_chat_api/db_handler/user_manager.py | 71 ++++++++++++++++++ simple_chat_api/endpoints/messages.py | 2 +- simple_chat_api/main.py | 2 +- simple_chat_api/tests/__init__.py | 0 .../tests/user_manager_unittest.py | 46 ++++++++++++ 12 files changed, 126 insertions(+), 5 deletions(-) rename simple_chat_testprotokoll.pdf => docs/simple_chat_testprotokoll.pdf (100%) create mode 100644 docs/unittest_sample.sqlite create mode 100644 simple_chat_api/db_handler/__init__.py rename simple_chat_api/{ => db_handler}/db_handler.py (98%) create mode 100644 simple_chat_api/db_handler/user_manager.py create mode 100644 simple_chat_api/tests/__init__.py create mode 100644 simple_chat_api/tests/user_manager_unittest.py diff --git a/.vscode/settings.json b/.vscode/settings.json index f29e369..31d3b37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,8 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix", - "python.testing.unittestArgs": ["-s", "simple_chat_api/tests", "-p", "*.py"], + "python.testing.unittestArgs": ["-s", ".", "-p", "*.py"], "python.testing.pytestEnabled": false, + "python.testing.cwd": "${workspaceFolder}", "python.testing.unittestEnabled": true } diff --git a/README.md b/README.md index bbc0e2a..7167364 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a simple one-room chat application with user management, developed as a **Not recommended for production use.** -A test protocol can be found here: [simple_chat_testprotokoll.pdf](simple_chat_testprotokoll.pdf) +A test protocol can be found here: [docs/simple_chat_testprotokoll.pdf](simple_chat_testprotokoll.pdf) ## Running with Docker diff --git a/simple_chat_testprotokoll.pdf b/docs/simple_chat_testprotokoll.pdf similarity index 100% rename from simple_chat_testprotokoll.pdf rename to docs/simple_chat_testprotokoll.pdf diff --git a/docs/unittest_sample.sqlite b/docs/unittest_sample.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..d8380bb24615fa29963bfb1a8aca4f9cea4f8e20 GIT binary patch literal 16384 zcmeI&!A{#S7zc2>g+VJ7@8%PdCsl$7LVr%4Nt(x_c2A?@g?a%q5UP0}Vo18&{R z>;ZTI?$geD0?r)ah!d2pXxdIl?C^Kwy0+Wz`|`V4$-#cpiEMG~2d<378hK49CGUkG zgfP|TR8Lz*eVLupbmJc*Bjw9Uf!&kr(l^5P*$*|KK>z{}fB*y_009U<00Izz00jOf zFkQ(m@a1Lt^-UxPBipsZP@dRfI?ipIx@PIZ(zcqqNLR&bKIaUD(X#Yi-4v}oOSC%8 z=39}^1-|c!u4Zo6H8Y+c`Cepu(d^P$Xa}?N$Wd`4>5kKY@&DSU@j)|tqM`T1sxvI* zOP6$kb55rRNuhC&F!{(lDKS}!>+_^*r&*pv+ZLpjfFEjiXMm@nv&t)a0Su?bg~G7u{}oP!5gWr=5Xr znQNnJyK(YyZ{7PG)H~%$-L1(aKwjMlWp{+#v1|67{d~M$@a~Wr0uX=z1Rwwb2tWV= z5P$##AOL~q5?IXgTyp-8pZ}|u{t3HzuF!}B0SG_<0uX=z1Rwwb2tWV=5P-n{Ch!0( CY?ayo literal 0 HcmV?d00001 diff --git a/simple_chat_api/__init__.py b/simple_chat_api/__init__.py index 043ae30..3db749d 100644 --- a/simple_chat_api/__init__.py +++ b/simple_chat_api/__init__.py @@ -1 +1,2 @@ -from simple_chat_api.main import main \ No newline at end of file +from simple_chat_api.main import main +from simple_chat_api.db_handler.db_handler import DbConnector \ No newline at end of file diff --git a/simple_chat_api/db_handler/__init__.py b/simple_chat_api/db_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simple_chat_api/db_handler.py b/simple_chat_api/db_handler/db_handler.py similarity index 98% rename from simple_chat_api/db_handler.py rename to simple_chat_api/db_handler/db_handler.py index 3d023dc..9c61af2 100644 --- a/simple_chat_api/db_handler.py +++ b/simple_chat_api/db_handler/db_handler.py @@ -34,6 +34,8 @@ class DbConnector: self.session = sessionmaker(bind=self.engine)() self._create_defaults() + def close(self): + self.session.close() def _create_optional_default_user(self): for user in optional_default_user: diff --git a/simple_chat_api/db_handler/user_manager.py b/simple_chat_api/db_handler/user_manager.py new file mode 100644 index 0000000..358bdc0 --- /dev/null +++ b/simple_chat_api/db_handler/user_manager.py @@ -0,0 +1,71 @@ + +from simple_chat_api.db_handler.db_handler import DbConnector +from simple_chat_api.config import JWT_SECRET, hash_context + +class UserManager: + """ + Class that handles authentication with database backend + """ + def __init__(self, db_con: DbConnector): + self.db_con = db_con + + + def create_user(self, username: str, password: str) -> bool: + """ + Create a new user in the database + :param username: logon name + :param password: password to save + :return: True if user was created + """ + try: + self.db_con.add_user(username, password) + except: + return False + return True + + def authenticate(self, username: str, password: str) -> bool: + """ + Authenticate against the database + :param username: username to check + :param password: password to check + :return: True if authenticated + """ + user = self.db_con.get_user(username) + if not user or not hash_context.verify(password, user.hash): + return False + return True + + def delete_user(self, username: str) -> bool: + """ + Delete a user from the database + :param username: username to delete + :return: True on success + """ + try: + self.db_con.delete_user(username) + except: + return False + return True + + def change_user_password(self, username: str, password: str) -> bool: + """ + Change the password for a user + :param username: username to change + :param password: new password + :return: True on success + """ + try: + self.db_con.change_password(username, hash_context.hash(password)) + except: + return False + return True + + def check_user_exists(self, username: str) -> bool: + """ + Check if a user exists in the database + :param username: logon name to look for + :return: True if user exists + """ + if self.db_con.get_user(username): + return True + return False diff --git a/simple_chat_api/endpoints/messages.py b/simple_chat_api/endpoints/messages.py index 3846e19..d6fdc2f 100644 --- a/simple_chat_api/endpoints/messages.py +++ b/simple_chat_api/endpoints/messages.py @@ -2,7 +2,7 @@ from bottle import Bottle, request, response from json import dumps, loads -from simple_chat_api.db_handler import User, Message +from simple_chat_api.db_handler.db_handler import User, Message from simple_chat_api.endpoints.auth import user_guard from simple_chat_api.utils import read_keys_from_request app = Bottle() diff --git a/simple_chat_api/main.py b/simple_chat_api/main.py index da885a9..90fc04d 100644 --- a/simple_chat_api/main.py +++ b/simple_chat_api/main.py @@ -1,6 +1,6 @@ from bottle import request, Bottle -from simple_chat_api.db_handler import DbConnector +from simple_chat_api.db_handler.db_handler import DbConnector import simple_chat_api.endpoints.auth as auth import simple_chat_api.endpoints.messages as messages diff --git a/simple_chat_api/tests/__init__.py b/simple_chat_api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simple_chat_api/tests/user_manager_unittest.py b/simple_chat_api/tests/user_manager_unittest.py new file mode 100644 index 0000000..0242344 --- /dev/null +++ b/simple_chat_api/tests/user_manager_unittest.py @@ -0,0 +1,46 @@ +import unittest +import os +import shutil + +from simple_chat_api.db_handler.db_handler import DbConnector +from simple_chat_api.db_handler.user_manager import UserManager + +DB_PATH = "./data/TEST.sqlite" + +EXISTING_USER = ("admin", "admin") + +class TestUserManagmentWrapper(unittest.TestCase): + + def setUp(self): + shutil.copyfile("docs/unittest_sample.sqlite", DB_PATH) + db = DbConnector(f"sqlite:///{DB_PATH}") + self.user_mgr = UserManager(db) + + def tearDown(self): + self.user_mgr.db_con.close() + os.remove(DB_PATH) + + def test_create_user(self): + user = ("someone", "some_pass") + ret = self.user_mgr.create_user(*user) + self.assertTrue(ret, f"user {user} faild to be created") + + def test_authenticate(self): + self.assertTrue(self.user_mgr.authenticate(*EXISTING_USER), + f"Existing user {EXISTING_USER} could not be authenticated") + self.assertFalse(self.user_mgr.authenticate(EXISTING_USER[0], "unknown_pass"), + f"Existing user {EXISTING_USER[0]} could be authenticated with wrong password") + self.assertFalse(self.user_mgr.authenticate("unknown_user", "unknown_pass"), + f"An unknown User was authenticated") + + def test_delete_user(self): + self.assertTrue(self.user_mgr.delete_user(EXISTING_USER[0]), f"The existing user {EXISTING_USER[0]} could not be removed") + self.assertFalse(self.user_mgr.delete_user("unknown_user"), "an unknown user could be deleted") + + def test_change_password(self): + self.assertTrue(self.user_mgr.change_user_password(EXISTING_USER[0], "new_pass"), f"Could not change password for existing user") + self.assertFalse(self.user_mgr.change_user_password("unknown_user", "new_pass"), "The password for an unknown user was changed") + + def test_check_user_exists(self): + self.assertTrue(self.user_mgr.check_user_exists(EXISTING_USER[0]), f"The {EXISTING_USER[0]} could not be found") + self.assertFalse(self.user_mgr.check_user_exists("unknown_user"), "An unknown user was found") \ No newline at end of file