diff --git a/.pylintrc b/.pylintrc index 334478f..912c0fd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,6 @@ # This Pylint rcfile contains a best-effort configuration to uphold the # best-practices and style described in the Google Python style guide. -# some sections that should not be used, and some where the defautl value is ok, have been removed +# some sections that should not be used, and some where the default value is ok, have been removed # For detailed documentation of all options see: https://pylint.pycqa.org/en/latest/user_guide/configuration/all-options.html # # The original file can be found here: diff --git a/.vscode/settings.json b/.vscode/settings.json index 3a42483..1f383f9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,6 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.analysis.typeCheckingMode": "standard" } diff --git a/coding-guidelines/README.md b/coding-guidelines/README.md index 432fad2..a9b80c2 100644 --- a/coding-guidelines/README.md +++ b/coding-guidelines/README.md @@ -2,7 +2,7 @@ ## Coding Style -For code formating, and naming conventions [PEP8](https://www.python.org/dev/peps/pep-0008/) is applicable. +For code formatting, and naming conventions [PEP8](https://www.python.org/dev/peps/pep-0008/) is applicable. The following rule is defined as deviating: @@ -14,11 +14,11 @@ The following rule is defined as deviating: For static code analysis and to check the code style, [pylint](https://pypi.org/project/pylint/) shall be used. -Ohther common tools that can be used optionally in addition: +Other common tools that can be used optionally in addition: - [flake8](https://flake8.pycqa.org/en/latest/index.html) with more focus on style check and support for plugins, e.g.: - [flake8-docstrings](https://github.com/pycqa/flake8-docstrings) - - [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) finding likly bugs and design problems + - [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) finding likely bugs and design problems - an Overview of additional plugins can be found in [flake8 plugin list](https://github.com/DmytroLitvinov/awesome-flake8-extensions?tab=readme-ov-file#docstrings) - [mypy](https://github.com/python/mypy) static typing check for python - [bandits](https://bandit.readthedocs.io/en/latest/) find common security issues in python @@ -33,7 +33,7 @@ Some examples from PEP8 for naming conventions: - Functions: "function","example_function",... - Variables: "variable","example_variable",... -### Auto formating +### Auto formatting [autopep8](https://pypi.org/project/autopep8/) is used for the automatic formatting of the source code @@ -44,7 +44,7 @@ Versions of component packages are defined in requirements.txt ## Documentation -For documentation [python docstring](https://peps.python.org/pep-0257/). There are different options for formating the docstring in detail. There the [Google styleguide](https://google.github.io/styleguide/pyguide.html) applies. +For documentation [python docstring](https://peps.python.org/pep-0257/). There are different options for formatting the docstring in detail. There the [Google style guide](https://google.github.io/styleguide/pyguide.html) applies. Based on docstrings, Source Code documentation can be generated by sphinx. @@ -63,7 +63,7 @@ pip install sphinx_rtd_theme pip install sphinx-favicon ``` -[ReadTheDocs](https://sphinx-rtd-theme.readthedocs.io/en/stable/) is a popular theme, used wiht spingx +[ReadTheDocs](https://sphinx-rtd-theme.readthedocs.io/en/stable/) is a popular theme, used with Sphinx A sphinx template is already prepared in the [doc/detailed-design](./doc/detailed-design/) folder diff --git a/src/pyProfileMgr/cmd_profile.py b/src/pyProfileMgr/cmd_profile.py index 40b82e1..c4552d2 100644 --- a/src/pyProfileMgr/cmd_profile.py +++ b/src/pyProfileMgr/cmd_profile.py @@ -380,6 +380,8 @@ def _update_profile(args) -> Ret.CODE: Returns: Ret.CODE: Status code indicating the success or failure of the profile update. """ + ret_status = Ret.CODE.RET_OK + # Update cert if args.cert is not None: profile_mgr = ProfileMgr() diff --git a/src/pyProfileMgr/profile_mgr.py b/src/pyProfileMgr/profile_mgr.py index b895e6d..117d5d5 100644 --- a/src/pyProfileMgr/profile_mgr.py +++ b/src/pyProfileMgr/profile_mgr.py @@ -35,12 +35,13 @@ # Imports ################################################################################ -import os import json import logging +import os +from typing import Optional from dataclasses import dataclass try: - from enum import StrEnum # Available in Python 3.11+ + from enum import StrEnum # type: ignore # Available in Python 3.11+ except ImportError: from enum import Enum @@ -70,21 +71,45 @@ class StrEnum(str, Enum): @dataclass class ProfileType(StrEnum): """ The profile types.""" - JIRA = 'jira' - POLARION = 'polarion' - SUPERSET = 'superset' + JIRA = 'jira' # type: ignore + POLARION = 'polarion' # type: ignore + SUPERSET = 'superset' # type: ignore ################################################################################ -# Classes +# Functions ################################################################################ +def prepare_profiles_folder() -> str: + """ Prepares the profiles storage folder and returns the path to it. + + Profile data is stored under the users home directory. + + Returns: + str: The path to the profiles folder. + """ + + profiles_storage_path = os.path.expanduser( + "~") + PATH_TO_PROFILES_FOLDER + + # Create the profiles storage folder if it does not exist. + if not os.path.exists(profiles_storage_path): + os.makedirs(profiles_storage_path) + + return profiles_storage_path + + +################################################################################ +# Classes +################################################################################ class ProfileMgr: """ The ProfileMgr class handles all processes regarding server profiles. This includes adding, deleting or configuring profile data. """ + profiles_storage_path = prepare_profiles_folder() + # pylint: disable=R0902 def __init__(self): self._profile_name = None @@ -95,18 +120,16 @@ def __init__(self): self._profile_password = None self._profile_cert = None - self._profiles_storage_path = self._prepare_profiles_folder() - # pylint: disable=R0912, R0913, R0917 def add(self, profile_name: str, profile_type: ProfileType, server_url: str, - token: str, - user: str, - password: str, - cert_path: str) -> Ret.CODE: + token: Optional[str], + user: Optional[str], + password: Optional[str], + cert_path: Optional[str]) -> Ret.CODE: """ Adds a new profile with the provided details. Args: @@ -144,7 +167,7 @@ def add(self, else: return Ret.CODE.RET_ERROR_MISSING_CREDENTIALS - profile_path = self._profiles_storage_path + f"{profile_name}/" + profile_path = self.profiles_storage_path + f"{profile_name}/" if not os.path.exists(profile_path): os.mkdir(profile_path) @@ -171,7 +194,7 @@ def add(self, LOG.info(msg) print(msg) else: - LOG.info("Adding profile '%s' has bene canceled.", profile_name) + LOG.info("Adding profile '%s' has been canceled.", profile_name) return ret_status @@ -187,7 +210,7 @@ def add_certificate(self, profile_name: str, cert_path: str) -> Ret.CODE: """ ret_status = Ret.CODE.RET_OK - profile_path = self._profiles_storage_path + f"{profile_name}/" + profile_path = self.profiles_storage_path + f"{profile_name}/" if not os.path.exists(profile_path): return Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND @@ -221,13 +244,9 @@ def add_token(self, profile_name: str, api_token: str) -> Ret.CODE: Returns: Ret.CODE: A status code indicating the result of the operation. """ - ret_status = Ret.CODE.RET_OK - - profile_path = self._profiles_storage_path + f"{profile_name}/" - if not os.path.exists(profile_path): - return Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND - - self.load(profile_name) + ret_status = self.load(profile_name) + if ret_status != Ret.CODE.RET_OK: + return ret_status write_dict = { TYPE_KEY: self._profile_type, @@ -235,12 +254,11 @@ def add_token(self, profile_name: str, api_token: str) -> Ret.CODE: TOKEN_KEY: api_token } - os.remove(profile_path + DATA_FILE) - - profile_data = json.dumps(write_dict, indent=4) + profile_path = self.profiles_storage_path + f"{profile_name}/" try: with self._open_file(profile_path + DATA_FILE, 'w') as data_file: + profile_data = json.dumps(write_dict, indent=4) data_file.write(profile_data) self._profile_token = api_token @@ -249,10 +267,51 @@ def add_token(self, profile_name: str, api_token: str) -> Ret.CODE: print(msg) except IOError: - ret_status = Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND + ret_status = Ret.CODE.RET_ERROR_FILE_OPEN_FAILED return ret_status + def delete(self, profile_name: str) -> None: + """ Deletes the profile with the specified name. + + The method will remove the profile folder and all its content. + + Args: + profile_name (str): _description_ + """ + profile_path = self.profiles_storage_path + f"{profile_name}/" + + if os.path.exists(profile_path): + if os.path.exists(profile_path + DATA_FILE): + os.remove(profile_path + DATA_FILE) + + if os.path.exists(profile_path + CERT_FILE): + os.remove(profile_path + CERT_FILE) + + os.rmdir(profile_path) + + msg = f"Successfully removed profile '{profile_name}'." + LOG.info(msg) + print(msg) + + else: + LOG.error("Folder for profile '%s' does not exist", profile_name) + + def get_profiles(self) -> list[str]: + """ Gets a list of all stored profiles. + + Returns: + [str]: List of all stored profiles. + """ + + profile_names = [] + + for file_name in os.listdir(self.profiles_storage_path): + if os.path.isfile(os.path.join(self.profiles_storage_path, file_name)) is False: + profile_names.append(file_name) + + return profile_names + def load(self, profile_name: str) -> Ret.CODE: """ Loads the profile with the specified name. @@ -262,9 +321,11 @@ def load(self, profile_name: str) -> Ret.CODE: Returns: Ret.CODE: Status code indicating the success or failure of the load operation. """ + self._reset() + ret_status = Ret.CODE.RET_OK - profile_path = self._profiles_storage_path + f"{profile_name}/" + profile_path = self.profiles_storage_path + f"{profile_name}/" try: with self._open_file(profile_path + DATA_FILE, 'r') as data_file: @@ -297,48 +358,7 @@ def load(self, profile_name: str) -> Ret.CODE: return ret_status - def delete(self, profile_name: str) -> None: - """ Deletes the profile with the specified name. - - The method will remove the profile folder and all its content. - - Args: - profile_name (str): _description_ - """ - profile_path = self._profiles_storage_path + f"{profile_name}/" - - if os.path.exists(profile_path): - if os.path.exists(profile_path + DATA_FILE): - os.remove(profile_path + DATA_FILE) - - if os.path.exists(profile_path + CERT_FILE): - os.remove(profile_path + CERT_FILE) - - os.rmdir(profile_path) - - msg = f"Successfully removed profile '{profile_name}'." - LOG.info(msg) - print(msg) - - else: - LOG.error("Folder for profile '%s' does not exist", profile_name) - - def get_profiles(self) -> list[str]: - """ Gets a list of all stored profiles. - - Returns: - [str]: List of all stored profiles. - """ - - profile_names = [] - - for file_name in os.listdir(self._profiles_storage_path): - if os.path.isfile(os.path.join(self._profiles_storage_path, file_name)) is False: - profile_names.append(file_name) - - return profile_names - - def get_name(self) -> str: + def get_name(self) -> Optional[str]: """ Returns the name of the loaded profile. Returns: @@ -346,7 +366,7 @@ def get_name(self) -> str: """ return self._profile_name - def get_type(self) -> ProfileType: + def get_type(self) -> Optional[ProfileType]: """ Returns the type of the loaded profile. Returns: @@ -354,7 +374,7 @@ def get_type(self) -> ProfileType: """ return self._profile_type - def get_server_url(self) -> str: + def get_server_url(self) -> Optional[str]: """ Retrieves the server URL associated with the profile. Returns: @@ -362,7 +382,7 @@ def get_server_url(self) -> str: """ return self._profile_server_url - def get_api_token(self) -> str: + def get_api_token(self) -> Optional[str]: """ Retrieves the API token associated with the profile. Returns: @@ -370,7 +390,7 @@ def get_api_token(self) -> str: """ return self._profile_token - def get_user(self) -> str: + def get_user(self) -> Optional[str]: """ Retrieves the username associated with the profile. Returns: @@ -378,7 +398,7 @@ def get_user(self) -> str: """ return self._profile_user - def get_password(self) -> str: + def get_password(self) -> Optional[str]: """ Retrieves the password associated with the profile. Returns: @@ -386,7 +406,7 @@ def get_password(self) -> str: """ return self._profile_password - def get_cert_path(self) -> str: + def get_cert_path(self) -> Optional[str]: """ Retrieves the file path to the server certificate. Returns: @@ -394,25 +414,21 @@ def get_cert_path(self) -> str: """ return self._profile_cert - def _prepare_profiles_folder(self) -> str: - """ Prepares the profiles storage folder and returns the path to it. - - Profile data is stored under the users home directory. - - Returns: - str: The path to the profiles folder. - """ - - profiles_storage_path = os.path.expanduser( - "~") + PATH_TO_PROFILES_FOLDER - - # Create the profiles storage folder if it does not exist. - if not os.path.exists(profiles_storage_path): - os.makedirs(profiles_storage_path) + def get_profiles_folder(self) -> str: + """Returns the path to the profiles storage folder.""" + return self.profiles_storage_path - return profiles_storage_path + def _reset(self): + """ Initializes instance attributes. """ + self._profile_name = None + self._profile_type = None + self._profile_server_url = None + self._profile_token = None + self._profile_user = None + self._profile_password = None + self._profile_cert = None - def _add_new_profile(self, write_dict: dict, profile_name: str, cert_path: str) -> Ret.CODE: + def _add_new_profile(self, write_dict: dict, profile_name: str, cert_path: Optional[str]) -> Ret.CODE: """ Adds a new server profile to the configuration. Args: @@ -426,7 +442,7 @@ def _add_new_profile(self, write_dict: dict, profile_name: str, cert_path: str) ret_status = Ret.CODE.RET_OK - profile_path = self._profiles_storage_path + f"{profile_name}/" + profile_path = self.profiles_storage_path + f"{profile_name}/" profile_data = json.dumps(write_dict, indent=4) try: @@ -436,13 +452,13 @@ def _add_new_profile(self, write_dict: dict, profile_name: str, cert_path: str) except IOError: ret_status = Ret.CODE.RET_ERROR_FILEPATH_INVALID - if cert_path is not None: + if cert_path: ret_status = self.add_certificate(profile_name, cert_path) return ret_status # pylint: disable=R1732 - def _open_file(self, file_path: str, mode: str) -> any: + def _open_file(self, file_path: str, mode: str): """ Opens a file (encoding="UTF-8") in the given mode. Args: diff --git a/tests/test_01_profile.py b/tests/test_01_profile.py index 23b9900..db49756 100644 --- a/tests/test_01_profile.py +++ b/tests/test_01_profile.py @@ -33,9 +33,13 @@ # Imports ################################################################################ +import json import os +import stat -from pyProfileMgr.profile_mgr import ProfileMgr, ProfileType +import pytest + +from pyProfileMgr.profile_mgr import ProfileMgr, ProfileType, DATA_FILE from pyProfileMgr.ret import Ret @@ -43,6 +47,15 @@ # Variables ################################################################################ +TEST_PROFILE_NAME = 'test_profile' +TEST_SERVER = 'testServer' +TEST_TOKEN = 'testToken' +TEST_USER = 'testUser' +TEST_PASSWORD = 'testPassword' +TEST_CERT_PATH = TEST_CERT_PATH = os.path.dirname(os.path.realpath(__file__)) + \ + "/test_data/testCertificate.cert" + + ################################################################################ # Classes ################################################################################ @@ -51,135 +64,188 @@ # Functions ################################################################################ -def test_add_profile(): - """Tests the creation of a new profile.""" - sut = ProfileMgr() +# pylint: disable=W0621 +@pytest.fixture(autouse=True) +def profile_mgr(): + ''' Manages setup and teardown of the ProfileManager. ''' - # Delete the profile if it already exists. - sut.delete("test_profile") + # Setup: Create profile manager and delete the test profile from previous runs + # (if it exists). + profile_manager = ProfileMgr() + profile_manager.delete(TEST_PROFILE_NAME) + assert profile_manager.load( + TEST_PROFILE_NAME) is Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND - # Add a new profile and check if it was created successfully. - assert sut.add("test_profile", ProfileType.JIRA, "testServer", - "testToken", "testUser", "testPassword", None) is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK + yield profile_manager - # Delete the profile and check if it was deleted successfully. - sut.delete("test_profile") - assert sut.load("test_profile") is not Ret.CODE.RET_OK + # Teardown code goes here. -def test_add_certificate(): - """Tests the extension of an existing profile with a certificate.""" +def test_add_profile(profile_mgr: ProfileMgr, monkeypatch): + """Tests the creation of a new profile.""" + + # TC: Fail to add a profile without credentials (neither token, nor user/password). + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.JIRA, TEST_SERVER, + None, None, None, None) is Ret.CODE.RET_ERROR_MISSING_CREDENTIALS - sut = ProfileMgr() + # TC: All OK - add a new profile and check if it was created successfully. + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.JIRA, TEST_SERVER, + TEST_TOKEN, TEST_USER, TEST_PASSWORD, TEST_CERT_PATH) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK - # Delete the profile if it already exists. - sut.delete("test_profile") + # TC: All OK - overwrite existing profile. + monkeypatch.setattr('builtins.input', lambda _: "y") + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.POLARION, TEST_SERVER, + TEST_TOKEN, TEST_USER, TEST_PASSWORD, TEST_CERT_PATH) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + assert profile_mgr.get_type() == ProfileType.POLARION + + # TC: All OK - do not overwrite existing profile (type remains 'polarion'). + monkeypatch.setattr('builtins.input', lambda _: "n") + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.SUPERSET, TEST_SERVER, + TEST_TOKEN, TEST_USER, TEST_PASSWORD, None) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + assert profile_mgr.get_type() == ProfileType.POLARION + + +def test_add_certificate(profile_mgr: ProfileMgr): + """Tests the extension of an existing profile with a certificate.""" # TC: Fail to add a certificate to a non-existing profile. - assert sut.add_certificate("test_profile", os.path.dirname(os.path.realpath(__file__)) - + "/test_data/testCertificate.cert") is Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND + assert profile_mgr.add_certificate( + TEST_PROFILE_NAME, TEST_CERT_PATH) is Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND # TC: Fail to add a non-existing certificate file to the profile. # Add a new profile (without certificate) and check if it was created successfully. - assert sut.add("test_profile", ProfileType.JIRA, "testServer", - "testToken", "testUser", "testPassword", None) is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.JIRA, TEST_SERVER, + TEST_TOKEN, TEST_USER, TEST_PASSWORD, None) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK - assert sut.add_certificate("test_profile", os.path.dirname(os.path.realpath(__file__)) - + "/test_data/doesnotexist.cert") is Ret.CODE.RET_ERROR_FILEPATH_INVALID + assert profile_mgr.add_certificate(TEST_PROFILE_NAME, os.path.dirname(os.path.realpath(__file__)) + + "/test_data/doesnotexist.cert") is Ret.CODE.RET_ERROR_FILEPATH_INVALID - # TC: Add an existing certificate to the profile and check if it was added successfully. - assert sut.add_certificate("test_profile", os.path.dirname(os.path.realpath(__file__)) - + "/test_data/testCertificate.cert") is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK - assert sut.get_cert_path() is not None + # TC: All OK - add an existing certificate to the profile and check if it was added successfully. + assert profile_mgr.add_certificate( + TEST_PROFILE_NAME, TEST_CERT_PATH) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + assert profile_mgr.get_cert_path() is not None -def test_add_token(): - """Tests the extension of an existing profile with a token.""" +NO_USER_WRITING = ~stat.S_IWUSR +NO_GROUP_WRITING = ~stat.S_IWGRP +NO_OTHER_WRITING = ~stat.S_IWOTH +NO_WRITING = NO_USER_WRITING & NO_GROUP_WRITING & NO_OTHER_WRITING - sut = ProfileMgr() - # Delete the profile if it already exists. - sut.delete("test_profile") +def test_add_token(profile_mgr: ProfileMgr): + """Tests the extension of an existing profile with a token.""" # TC: Fail to add a token to a non-existing profile. - assert sut.add_token( - "test_profile", "testToken") is Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND - - # TC: Add a token to the profile and check if it was added successfully. + assert profile_mgr.add_token( + TEST_PROFILE_NAME, TEST_TOKEN) is Ret.CODE.RET_ERROR_PROFILE_NOT_FOUND # Add a new profile (without token) and check if it was created successfully. - assert sut.add("test_profile", ProfileType.JIRA, "testServer", - None, "testUser", "testPassword", None) is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK - - assert sut.add_token("test_profile", "testToken") is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK - assert sut.get_api_token() == "testToken" + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.JIRA, TEST_SERVER, + None, TEST_USER, TEST_PASSWORD, None) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + # TC: All OK - Add a token to the profile and check if it was added successfully. -def test_delete_profile(): - """Tests the deletion of a new profile.""" + assert profile_mgr.add_token( + TEST_PROFILE_NAME, TEST_TOKEN) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + assert profile_mgr.get_api_token() == TEST_TOKEN - sut = ProfileMgr() + # TC: Fail to add token if data file is read-only. + data_file_path = profile_mgr.get_profiles_folder() + TEST_PROFILE_NAME + \ + "/" + DATA_FILE + backup_permissions = stat.S_IMODE(os.lstat(data_file_path).st_mode) + os.chmod(data_file_path, backup_permissions & NO_WRITING) + assert profile_mgr.add_token( + TEST_PROFILE_NAME, TEST_TOKEN) is Ret.CODE.RET_ERROR_FILE_OPEN_FAILED + # Restore the previous permissions. + os.chmod(data_file_path, backup_permissions) - # TC: Delete a non-existing profile. - sut.delete("test_profile") - assert sut.load("test_profile") is not Ret.CODE.RET_OK - # TC: Delete a profile and check that it was deleted successfully. +def test_delete_profile(profile_mgr: ProfileMgr): + """Tests the deletion of a new profile.""" # Add a new profile and check if it was created successfully. - assert sut.add("test_profile", ProfileType.JIRA, "testServer", - None, "testUser", "testPassword", None) is Ret.CODE.RET_OK - - assert sut.load("test_profile") is Ret.CODE.RET_OK + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.SUPERSET, TEST_SERVER, + None, TEST_USER, TEST_PASSWORD, None) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK -def test_getters(): - """Tests the getters of the profile manager.""" + # TC: Delete a profile and check that it was deleted successfully. + try: + profile_mgr.delete(TEST_PROFILE_NAME) + # pylint: disable=W0718 + except Exception as exc: + pytest.fail(f"Unexpected exception: {exc}") - sut = ProfileMgr() - # Delete the profile if it already exists. - sut.delete("test_profile") +def test_getters(profile_mgr: ProfileMgr): + """Tests the getters of the profile manager.""" # TC: get_profiles - # Add a new profile and check if it was created successfully. - assert sut.add("test_profile", ProfileType.JIRA, "testServer", - "testToken", "testUser", "testPassword", os.path.dirname( - os.path.realpath(__file__)) - + "/test_data/testCertificate.cert") is Ret.CODE.RET_OK - assert sut.load("test_profile") is Ret.CODE.RET_OK + # Add a new profile including certificate and check if it was created successfully. + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.POLARION, TEST_SERVER, + TEST_TOKEN, TEST_USER, TEST_PASSWORD, TEST_CERT_PATH) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK - profiles = sut.get_profiles() - assert "test_profile" in profiles + profiles = profile_mgr.get_profiles() + assert TEST_PROFILE_NAME in profiles # TC: get_name - assert sut.get_name() == "test_profile" + assert profile_mgr.get_name() == TEST_PROFILE_NAME # TC: get_type - assert sut.get_type() == ProfileType.JIRA + assert profile_mgr.get_type() == ProfileType.POLARION # TC: get_server_url - assert sut.get_server_url() == "testServer" + assert profile_mgr.get_server_url() == TEST_SERVER # TC: get_api_token - assert sut.get_api_token() == "testToken" + assert profile_mgr.get_api_token() == TEST_TOKEN # TC: get_user (expected to be None since the profile contains a token). - assert sut.get_user() is None + assert profile_mgr.get_user() is None # TC: get_password (expected to be None since the profile contains a token). - assert sut.get_password() is None + assert profile_mgr.get_password() is None # TC: get_cert_path - assert ".cert" in sut.get_cert_path() + cert_path = profile_mgr.get_cert_path() + assert cert_path and ".cert" in cert_path + + +def test_invalid_type(profile_mgr: ProfileMgr): + """Tests that loading a profile with an unknown type fails.""" + + # Create a profile and make its type invalid on disk. + + assert profile_mgr.add(TEST_PROFILE_NAME, ProfileType.SUPERSET, TEST_SERVER, + None, TEST_USER, TEST_PASSWORD, None) is Ret.CODE.RET_OK + assert profile_mgr.load(TEST_PROFILE_NAME) is Ret.CODE.RET_OK + + data_file_path = profile_mgr.get_profiles_folder() + TEST_PROFILE_NAME + \ + "/" + DATA_FILE + + profile_dict = None + with open(data_file_path, 'r+', encoding="UTF-8") as data_file: + profile_dict = json.load(data_file) + profile_dict['type'] = 'invalid' + + data_file.seek(0) + data_file.write(json.dumps(profile_dict, indent=4)) + data_file.truncate() + + # TC: Fail to load invalid profile. + assert profile_mgr.load( + TEST_PROFILE_NAME) is Ret.CODE.RET_ERROR_INVALID_PROFILE_TYPE ################################################################################