diff --git a/.gitignore b/.gitignore index 3e4c422..585383e 100644 --- a/.gitignore +++ b/.gitignore @@ -133,4 +133,3 @@ dmypy.json # VS code *.code-workspace -.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c79b2a6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "*_test.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} \ No newline at end of file diff --git a/backend/custom_exceptions.py b/backend/custom_exceptions.py index be9e831..49578ab 100644 --- a/backend/custom_exceptions.py +++ b/backend/custom_exceptions.py @@ -1,5 +1,7 @@ #-*- coding: utf-8 -*- +from typing import Any, Dict + class UsernameTaken(Exception): """The username is already taken""" api_response = {'error': 'UsernameTaken', 'result': {}, 'code': 400} @@ -43,7 +45,7 @@ class KeyNotFound(Exception): super().__init__(self.key) @property - def api_response(self): + def api_response(self) -> Dict[str, Any]: return {'error': 'KeyNotFound', 'result': {'key': self.key}, 'code': 400} class InvalidKeyValue(Exception): @@ -54,5 +56,5 @@ class InvalidKeyValue(Exception): super().__init__(self.key) @property - def api_response(self): + def api_response(self) -> Dict[str, Any]: return {'error': 'KeyNotFound', 'result': {'key': self.key, 'value': self.value}, 'code': 400} diff --git a/backend/security.py b/backend/security.py index 0991364..8cb6a96 100644 --- a/backend/security.py +++ b/backend/security.py @@ -9,7 +9,7 @@ def get_hash(salt: bytes, data: str) -> bytes: """Hash a string using the supplied salt Args: - salt (bytes): The salt to use wwhen hashing + salt (bytes): The salt to use when hashing data (str): The data to hash Returns: diff --git a/backend/users.py b/backend/users.py index 6a0d940..5030ffe 100644 --- a/backend/users.py +++ b/backend/users.py @@ -106,7 +106,7 @@ def register_user(username: str, password: str) -> int: Returns: user_id (int): The id of the new user. User registered successful """ - #check if username is valid + # Check if username is valid _check_username(username) cursor = get_db() diff --git a/frontend/api.py b/frontend/api.py index 5ff088e..a4e8a8b 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -6,13 +6,15 @@ from typing import Any, Tuple from flask import Blueprint, g, request -from backend.custom_exceptions import (AccessUnauthorized, InvalidKeyValue, InvalidTime, InvalidURL, - KeyNotFound, NotificationServiceInUse, NotificationServiceNotFound, ReminderNotFound, - UsernameInvalid, UsernameTaken, - UserNotFound) +from backend.custom_exceptions import (AccessUnauthorized, InvalidKeyValue, + InvalidTime, InvalidURL, KeyNotFound, + NotificationServiceInUse, + NotificationServiceNotFound, + ReminderNotFound, UsernameInvalid, + UsernameTaken, UserNotFound) from backend.notification_service import (NotificationService, NotificationServices) -from backend.reminders import reminder_handler, Reminders +from backend.reminders import Reminders, reminder_handler from backend.users import User, register_user api = Blueprint('api', __name__) diff --git a/tests/Noted_test.py b/tests/Noted_test.py new file mode 100644 index 0000000..add799b --- /dev/null +++ b/tests/Noted_test.py @@ -0,0 +1,20 @@ +import unittest + +from flask import Flask + +from frontend.api import api +from frontend.ui import ui +from Noted import _create_app + +class Test_Noted(unittest.TestCase): + def test_create_app(self): + result = _create_app() + self.assertIsInstance(result, Flask) + + self.assertEqual(result.blueprints.get('ui'), ui) + self.assertEqual(result.blueprints.get('api'), api) + + handlers = result.error_handler_spec[None].keys() + required_handlers = 404, 400, 405, 500 + for handler in required_handlers: + self.assertIn(handler, handlers) diff --git a/tests/api_test.py b/tests/api_test.py new file mode 100644 index 0000000..9ac73d8 --- /dev/null +++ b/tests/api_test.py @@ -0,0 +1,62 @@ +import unittest + +from flask import Blueprint + +from backend.custom_exceptions import * +from frontend.api import api, auth, error_handler, extract_key, return_api + +class Test_API(unittest.TestCase): + def test_blueprint(self): + self.assertIsInstance(api, Blueprint) + + def test_return_api(self): + for case in ({'result': {}, 'error': 'Error', 'code': 201}, + {'result': ''}): + result = return_api(**case) + self.assertEqual(result[0]['result'], case['result']) + if case.get('error'): + self.assertEqual(result[0]['error'], case['error']) + else: + self.assertIsNone(result[0]['error']) + if case.get('code'): + self.assertEqual(result[1], case['code']) + else: + self.assertEqual(result[1], 200) + + def test_auth(self): + method = lambda x: x + result = auth(method) + self.assertEqual(result.__name__, method.__name__) + + def _raise_exception(self, e, *args): + raise e(*args) + + def test_error_handler(self): + result = error_handler(self._raise_exception) + self.assertEqual(result.__name__, self._raise_exception.__name__) + self.assertEqual(result(UsernameTaken), return_api(**UsernameTaken.api_response)) + self.assertEqual(result(UsernameInvalid), return_api(**UsernameInvalid.api_response)) + self.assertEqual(result(UserNotFound), return_api(**UserNotFound.api_response)) + self.assertEqual(result(AccessUnauthorized), return_api(**AccessUnauthorized.api_response)) + self.assertEqual(result(ReminderNotFound), return_api(**ReminderNotFound.api_response)) + self.assertEqual(result(NotificationServiceNotFound), return_api(**NotificationServiceNotFound.api_response)) + self.assertEqual(result(NotificationServiceInUse), return_api(**NotificationServiceInUse.api_response)) + self.assertEqual(result(InvalidTime), return_api(**InvalidTime.api_response)) + self.assertEqual(result(InvalidURL), return_api(**InvalidURL.api_response)) + self.assertEqual(result(KeyNotFound, 'test'), return_api(**KeyNotFound('test').api_response)) + self.assertEqual(result(InvalidKeyValue, 'test', 'value'), return_api(**InvalidKeyValue('test', 'value').api_response)) + with self.assertRaises(TypeError): + result(TypeError) + with self.assertRaises(KeyError): + result(KeyError) + + def test_extract_key(self): + with self.assertRaises(KeyNotFound): + extract_key({'test': 'value'}, 'no_key') + self.assertIsNone(extract_key({'test': 'value'}, 'no_key', check_existence=False)) + with self.assertRaises(InvalidKeyValue): + extract_key({'time': ''}, 'time') + self.assertIsInstance(extract_key({'time': '1'}, 'time'), int) + + + \ No newline at end of file diff --git a/tests/custom_exceptions_test.py b/tests/custom_exceptions_test.py new file mode 100644 index 0000000..07547aa --- /dev/null +++ b/tests/custom_exceptions_test.py @@ -0,0 +1,18 @@ +import unittest +from inspect import getmembers, getmro, isclass +from sys import modules +from typing import List + +import backend.custom_exceptions + +class Test_Custom_Exceptions(unittest.TestCase): + def test_type(self): + defined_exceptions: List[Exception] = map(lambda c: c[1], getmembers(modules['backend.custom_exceptions'], isclass)) + for defined_exception in defined_exceptions: + self.assertEqual(getmro(defined_exception)[1], Exception) + result = defined_exception().api_response + self.assertIsInstance(result, dict) + result['error'] + result['result'] + result['code'] + self.assertIsInstance(result['code'], int) diff --git a/tests/db_test.py b/tests/db_test.py new file mode 100644 index 0000000..3540b3e --- /dev/null +++ b/tests/db_test.py @@ -0,0 +1,12 @@ +import unittest + +from backend.db import DBConnection +from Noted import DB_FILENAME + +class Test_DB(unittest.TestCase): + def test_foreign_key(self): + DBConnection.file = DB_FILENAME + instance = DBConnection(timeout=20.0) + self.assertEqual(instance.cursor().execute("PRAGMA foreign_keys;").fetchone()[0], 1) + + \ No newline at end of file diff --git a/tests/reminders_test.py b/tests/reminders_test.py new file mode 100644 index 0000000..7611754 --- /dev/null +++ b/tests/reminders_test.py @@ -0,0 +1,29 @@ +import unittest +from threading import Thread + +from backend.reminders import filter_function, ReminderHandler + +class Test_Reminder_Handler(unittest.TestCase): + def test_starting_stopping(self): + context = 'test' + instance = ReminderHandler(context) + self.assertIs(context, instance.context) + + self.assertIsInstance(instance.thread, Thread) + + self.assertFalse(instance.stop) + with self.assertRaises(RuntimeError): + instance.stop_handling() + self.assertTrue(instance.stop) + + def test_filter_function(self): + p = { + 'title': 'TITLE', + 'text': 'TEXT', + 'notification_service_title': 'NOTIFICATION_SERVICE_TITLE' + } + for test_case in ('', 'title', 'service', 'ex'): + self.assertTrue(filter_function(test_case, p)) + for test_case in (' ', 'Hello'): + self.assertFalse(filter_function(test_case, p)) + diff --git a/tests/security_test.py b/tests/security_test.py new file mode 100644 index 0000000..3fcddc5 --- /dev/null +++ b/tests/security_test.py @@ -0,0 +1,10 @@ +import unittest + +from backend.security import generate_salt_hash, get_hash + +class Test_Security(unittest.TestCase): + def test_hash(self): + for test_case in ('test', ''): + result = generate_salt_hash(test_case) + self.assertEqual(result[1], get_hash(result[0], test_case)) + \ No newline at end of file diff --git a/tests/ui_test.py b/tests/ui_test.py new file mode 100644 index 0000000..2d611c7 --- /dev/null +++ b/tests/ui_test.py @@ -0,0 +1,20 @@ +import unittest + +from flask import Blueprint, Flask + +from frontend.ui import methods, ui + +class Test_UI(unittest.TestCase): + def test_methods(self): + self.assertEqual(len(methods), 1) + self.assertEqual(methods[0], 'GET') + + def test_blueprint(self): + self.assertIsInstance(ui, Blueprint) + + def test_route_methods(self): + temp_app = Flask(__name__) + temp_app.register_blueprint(ui) + for rule in temp_app.url_map.iter_rules(): + self.assertEqual(len(rule.methods), 3) + self.assertIn(methods[0], rule.methods) diff --git a/tests/users_test.py b/tests/users_test.py new file mode 100644 index 0000000..f00e0ae --- /dev/null +++ b/tests/users_test.py @@ -0,0 +1,14 @@ +import unittest + +from backend.custom_exceptions import UsernameInvalid +from backend.users import ONEPASS_INVALID_USERNAMES, _check_username + +class Test_Users(unittest.TestCase): + def test_username_check(self): + for test_case in ('', 'test'): + _check_username(test_case) + + for test_case in (' ', ' ', '0', 'api', *ONEPASS_INVALID_USERNAMES): + with self.assertRaises(UsernameInvalid): + _check_username(test_case) + \ No newline at end of file