Repository: cobbler/cobbler

Issue:
Property Setters don't deduplicate in dict or list case
### Describe the bug

When using a setter like `item.kernel_options`, the setter is assuming that you pass it a raw value. However if the data is retrieved before via `item.kernel_options` the data returned is resolved. This leads to data duplication inside Cobbler.

### Steps to reproduce

1. `my_value = item.kernel_options`
2. Do things with my_value
3. `item.kernel_options = my_value`

### Expected behavior

The data that is attached to the parent(s) is not duplicated in the children.

### Cobbler version

<!--- Paste output from `cobbler version` -->
````paste below
````

### Operating system

<!--- On which operating system do you use Cobbler? -->

### Cobbler log

<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->
````paste below
````

### Screenshots

<!--- If applicable, add screenshots to help explain your problem. -->

### Additional information

This can be fixed with logic that is already present in `CobblerAPI.set_item_resolved_value`.



Discussion:

Resolving patch:
diff --git a/cobbler/items/__init__.py b/cobbler/items/__init__.py
--- a/cobbler/items/__init__.py
+++ b/cobbler/items/__init__.py
@@ -2,4 +2,8 @@
 This package contains all data storage classes. The classes are responsible for ensuring that types of the properties
 are correct but not for logical checks. The classes should be as stupid as possible. Further they are responsible for
 returning the logic for serializing and deserializing themselves.
+
+Cobbler has a concept of inheritance where an attribute/a property may have the value ``<<inherit>>``. This then takes
+over the value of the parent item with the exception of dictionaries. Values that are of type ``dict`` are always
+implicitly inherited, to remove a key-value pair from the dictionary in the inheritance chain prefix the key with ``!``.
 """
diff --git a/cobbler/items/item.py b/cobbler/items/item.py
--- a/cobbler/items/item.py
+++ b/cobbler/items/item.py
@@ -105,7 +105,7 @@
 import re
 import uuid
 from abc import abstractmethod
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, TypeVar, Union
 
 import yaml
 
@@ -126,6 +126,7 @@
 
 
 RE_OBJECT_NAME = re.compile(r"[a-zA-Z0-9_\-.:]*$")
+T = TypeVar("T")
 
 
 class Item:
@@ -380,10 +381,34 @@ def __setattr__(self, name: str, value: Any):
             self.clean_cache(name)
         super().__setattr__(name, value)
 
+    def __common_resolve(self, property_name: str):
+        settings_name = property_name
+        if property_name.startswith("proxy_url_"):
+            property_name = "proxy"
+        if property_name == "owners":
+            settings_name = "default_ownership"
+        attribute = "_" + property_name
+
+        return getattr(self, attribute), settings_name
+
+    def __resolve_get_parent_or_settings(self, property_name: str, settings_name: str):
+        settings = self.api.settings()
+        conceptual_parent = self.get_conceptual_parent()
+
+        if hasattr(self.parent, property_name):
+            return getattr(self.parent, property_name)
+        elif hasattr(conceptual_parent, property_name):
+            return getattr(conceptual_parent, property_name)
+        elif hasattr(settings, settings_name):
+            return getattr(settings, settings_name)
+        elif hasattr(settings, f"default_{settings_name}"):
+            return getattr(settings, f"default_{settings_name}")
+        return None
+
     def _resolve(self, property_name: str) -> Any:
         """
         Resolve the ``property_name`` value in the object tree. This function traverses the tree from the object to its
-        topmost parent and returns the first value that is not inherited. If the the tree does not contain a value the
+        topmost parent and returns the first value that is not inherited. If the tree does not contain a value the
         settings are consulted.
 
         :param property_name: The property name to resolve.
@@ -391,32 +416,17 @@ def _resolve(self, property_name: str) -> Any:
                                 ``property_name``.
         :return: The resolved value.
         """
-        settings_name = property_name
-        if property_name.startswith("proxy_url_"):
-            property_name = "proxy"
-        if property_name == "owners":
-            settings_name = "default_ownership"
-        attribute = "_" + property_name
-
-        if not hasattr(self, attribute):
-            raise AttributeError(
-                f'{type(self)} "{self.name}" does not have property "{property_name}"'
-            )
-
-        attribute_value = getattr(self, attribute)
-        settings = self.api.settings()
+        attribute_value, settings_name = self.__common_resolve(property_name)
 
         if attribute_value == enums.VALUE_INHERITED:
-            logical_parent = self.logical_parent
-            if logical_parent is not None and hasattr(logical_parent, property_name):
-                return getattr(logical_parent, property_name)
-            if hasattr(settings, settings_name):
-                return getattr(settings, settings_name)
-            if hasattr(settings, f"default_{settings_name}"):
-                return getattr(settings, f"default_{settings_name}")
-            AttributeError(
+            possible_return = self.__resolve_get_parent_or_settings(
+                property_name, settings_name
+            )
+            if possible_return is not None:
+                return possible_return
+            raise AttributeError(
                 f'{type(self)} "{self.name}" inherits property "{property_name}", but neither its parent nor'
-                f"settings have it"
+                f" settings have it"
             )
 
         return attribute_value
@@ -427,31 +437,17 @@ def _resolve_enum(
         """
         See :meth:`~cobbler.items.item.Item._resolve`
         """
-        settings_name = property_name
-        attribute = "_" + property_name
-
-        if not hasattr(self, attribute):
-            raise AttributeError(
-                f'{type(self)} "{self.name}" does not have property "{property_name}"'
+        attribute_value, settings_name = self.__common_resolve(property_name)
+        unwrapped_value = getattr(attribute_value, "value", "")
+        if unwrapped_value == enums.VALUE_INHERITED:
+            possible_return = self.__resolve_get_parent_or_settings(
+                unwrapped_value, settings_name
             )
-
-        attribute_value = getattr(self, attribute)
-        settings = self.api.settings()
-
-        if (
-            isinstance(attribute_value, enums.ConvertableEnum)
-            and attribute_value.value == enums.VALUE_INHERITED
-        ):
-            logical_parent = self.logical_parent
-            if logical_parent is not None and hasattr(logical_parent, property_name):
-                return getattr(logical_parent, property_name)
-            if hasattr(settings, settings_name):
-                return enum_type.to_enum(getattr(settings, settings_name))
-            if hasattr(settings, f"default_{settings_name}"):
-                return enum_type.to_enum(getattr(settings, f"default_{settings_name}"))
-            AttributeError(
+            if possible_return is not None:
+                return enum_type(possible_return)
+            raise AttributeError(
                 f'{type(self)} "{self.name}" inherits property "{property_name}", but neither its parent nor'
-                "settings have it"
+                f" settings have it"
             )
 
         return attribute_value
@@ -467,19 +463,14 @@ def _resolve_dict(self, property_name: str) -> Dict[str, Any]:
         """
         attribute = "_" + property_name
 
-        if not hasattr(self, attribute):
-            raise AttributeError(
-                f'{type(self)} "{self.name}" does not have property "{property_name}"'
-            )
-
         attribute_value = getattr(self, attribute)
         settings = self.api.settings()
 
         merged_dict: Dict[str, Any] = {}
 
-        logical_parent = self.logical_parent
-        if logical_parent is not None and hasattr(logical_parent, property_name):
-            merged_dict.update(getattr(logical_parent, property_name))
+        conceptual_parent = self.get_conceptual_parent()
+        if hasattr(conceptual_parent, property_name):
+            merged_dict.update(getattr(conceptual_parent, property_name))
         elif hasattr(settings, property_name):
             merged_dict.update(getattr(settings, property_name))
 
@@ -489,6 +480,39 @@ def _resolve_dict(self, property_name: str) -> Dict[str, Any]:
         utils.dict_annihilate(merged_dict)
         return merged_dict
 
+    def _deduplicate_dict(
+        self, property_name: str, value: Dict[str, T]
+    ) -> Dict[str, T]:
+        """
+        Filter out the key:value pair may come from parent and global settings.
+        Note: we do not know exactly which resolver does key:value belongs to, what we did is just deduplicate them.
+
+        :param property_name: The property name to deduplicated.
+        :param value: The value that should be deduplicated.
+        :returns: The deduplicated dictionary
+        """
+        _, settings_name = self.__common_resolve(property_name)
+        settings = self.api.settings()
+        conceptual_parent = self.get_conceptual_parent()
+
+        if hasattr(self.parent, property_name):
+            parent_value = getattr(self.parent, property_name)
+        elif hasattr(conceptual_parent, property_name):
+            parent_value = getattr(conceptual_parent, property_name)
+        elif hasattr(settings, settings_name):
+            parent_value = getattr(settings, settings_name)
+        elif hasattr(settings, f"default_{settings_name}"):
+            parent_value = getattr(settings, f"default_{settings_name}")
+        else:
+            parent_value = {}
+
+        # Because we use getattr pyright cannot correctly check this.
+        for key in parent_value:  # type: ignore
+            if key in value and parent_value[key] == value[key]:  # type: ignore
+                value.pop(key)  # type: ignore
+
+        return value
+
     @property
     def uid(self) -> str:
         """
@@ -635,11 +659,14 @@ def kernel_options(self, options: Dict[str, Any]):
         :raises ValueError: In case the values set could not be parsed successfully.
         """
         try:
-            self._kernel_options = input_converters.input_string_or_dict(
-                options, allow_multiples=True
-            )
+            value = input_converters.input_string_or_dict(options, allow_multiples=True)
+            if value == enums.VALUE_INHERITED:
+                self._kernel_options = enums.VALUE_INHERITED
+                return
+            # pyright doesn't understand that the only valid str return value is this constant.
+            self._kernel_options = self._deduplicate_dict("kernel_options", value)  # type: ignore
         except TypeError as error:
-            raise TypeError("invalid kernel options") from error
+            raise TypeError("invalid kernel value") from error
 
     @InheritableDictProperty
     def kernel_options_post(self) -> Dict[str, Any]:
@@ -690,7 +717,11 @@ def autoinstall_meta(self, options: Dict[Any, Any]):
         :raises ValueError: If splitting the value does not succeed.
         """
         value = input_converters.input_string_or_dict(options, allow_multiples=True)
-        self._autoinstall_meta = value
+        if value == enums.VALUE_INHERITED:
+            self._autoinstall_meta = enums.VALUE_INHERITED
+            return
+        # pyright doesn't understand that the only valid str return value is this constant.
+        self._autoinstall_meta = self._deduplicate_dict("autoinstall_meta", value)  # type: ignore
 
     @InheritableProperty
     def mgmt_classes(self) -> List[Any]:
diff --git a/cobbler/items/profile.py b/cobbler/items/profile.py
--- a/cobbler/items/profile.py
+++ b/cobbler/items/profile.py
@@ -561,6 +561,8 @@ def filename(self) -> str:
         """
         The filename which is fetched by the client from TFTP.
 
+        If the filename is set to ``<<inherit>>`` and there is no parent profile then it will be set to an empty string.
+
         :getter: Either the default/inherited one, or the one specific to this profile.
         :setter: The new filename which is fetched on boot. May raise a ``TypeError`` when the wrong type was given.
         """


Test patch:
diff --git a/tests/items/inheritance_test.py b/tests/items/inheritance_test.py
new file mode 100644
--- /dev/null
+++ b/tests/items/inheritance_test.py
@@ -0,0 +1,91 @@
+"""
+Test that verifies that the inheritance concept in Cobbler works for all data types.
+"""
+
+from typing import Callable
+
+from cobbler.api import CobblerAPI
+from cobbler.items.distro import Distro
+from cobbler.items.profile import Profile
+
+# pylint: disable=protected-access
+
+
+def test_resolved_dict_deduplication(
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+):
+    """
+    Test that verifies if properties with dictionaries correctly deduplicate.
+    """
+    # Arrange
+    test_distro = create_distro()
+    test_profile = create_profile(test_distro.name)
+    test_distro.kernel_options = {"a": True, "b": 5}
+    expected_result_raw = {"b": 6, "c": "test"}
+    expected_result_resolved = {"a": True, "b": 6, "c": "test"}
+    expected_result_distro = {"a": True, "b": 5}
+
+    # Act
+    test_profile.kernel_options = {"a": True, "b": 6, "c": "test"}
+
+    # Assert
+    assert test_profile._kernel_options == expected_result_raw  # type: ignore
+    assert test_profile.kernel_options == expected_result_resolved
+    assert test_distro.kernel_options == expected_result_distro
+
+
+def test_resolved_list_deduplication(
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+):
+    """
+    Test that verifies if properties with lists correctly resolve.
+    """
+    # Arrange
+    test_distro = create_distro()
+    test_profile = create_profile(test_distro.name)
+    test_distro.owners = ["owner1"]
+    expected_result_raw = ["owner2"]
+    expected_result_resolved = ["owner2"]
+    expected_result_distro = ["owner1"]
+
+    # Act
+    test_profile.owners = ["owner2"]
+
+    # Assert
+    assert test_profile._owners == expected_result_raw  # type: ignore
+    assert test_profile.owners == expected_result_resolved
+    assert test_distro.owners == expected_result_distro
+
+
+def test_to_dict_filter_resolved(
+    cobbler_api: CobblerAPI, create_distro: Callable[[], Distro]
+):
+    """
+    Test that verifies if properties with dictionaries correctly resolve.
+    """
+    # Arrange
+    test_distro = create_distro()
+    test_distro.kernel_options = {"test": True}
+    test_distro.autoinstall_meta = {"tree": "http://test/url"}
+    cobbler_api.add_distro(test_distro)
+
+    titem = Profile(cobbler_api)
+    titem.name = "to_dict_filter_resolved_profile"
+    titem.distro = test_distro.name
+    new_kernel_options = titem.kernel_options
+    new_autoinstall_meta = titem.autoinstall_meta
+    new_kernel_options["test"] = False
+    new_autoinstall_meta["tree"] = "http://newtest/url"
+    titem.kernel_options = new_kernel_options
+    titem.autoinstall_meta = new_autoinstall_meta
+    cobbler_api.add_profile(titem)
+
+    # Act
+    result = titem.to_dict(resolved=True)
+
+    # Assert
+    assert isinstance(result, dict)
+    assert result.get("kernel_options") == {"test": False}
+    assert result.get("autoinstall_meta") == {"tree": "http://newtest/url"}
diff --git a/tests/items/system_test.py b/tests/items/system_test.py
--- a/tests/items/system_test.py
+++ b/tests/items/system_test.py
@@ -1,14 +1,26 @@
+"""
+Test module that asserts that Cobbler System functionallity is working as expected.
+"""
+
+from typing import Any, Callable, List, Optional
+
 import pytest
 
 from cobbler import enums
+from cobbler.api import CobblerAPI
 from cobbler.cexceptions import CX
+from cobbler.items.distro import Distro
+from cobbler.items.image import Image
 from cobbler.items.profile import Profile
 from cobbler.items.system import NetworkInterface, System
 
 from tests.conftest import does_not_raise
 
 
-def test_object_creation(cobbler_api):
+def test_object_creation(cobbler_api: CobblerAPI):
+    """
+    Test that verifies that the object constructor can be successfully used.
+    """
     # Arrange
 
     # Act
@@ -18,7 +30,10 @@ def test_object_creation(cobbler_api):
     assert isinstance(system, System)
 
 
-def test_make_clone(cobbler_api):
+def test_make_clone(cobbler_api: CobblerAPI):
+    """
+    Test that verifies that cloning an object logically is working as expected.
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -29,7 +44,10 @@ def test_make_clone(cobbler_api):
     assert result != system
 
 
-def test_to_dict(cobbler_api):
+def test_to_dict(cobbler_api: CobblerAPI):
+    """
+    Test that verfies that converting the System to a dict works as expected.
+    """
     # Arrange
     titem = System(cobbler_api)
 
@@ -41,7 +59,12 @@ def test_to_dict(cobbler_api):
     assert result.get("autoinstall") == enums.VALUE_INHERITED
 
 
-def test_to_dict_resolved_profile(cobbler_api, create_distro):
+def test_to_dict_resolved_profile(
+    cobbler_api: CobblerAPI, create_distro: Callable[[], Distro]
+):
+    """
+    Test that verfies that a system which is based on a profile can be converted to a dictionary successfully.
+    """
     # Arrange
     test_distro = create_distro()
     test_distro.kernel_options = {"test": True}
@@ -67,7 +90,12 @@ def test_to_dict_resolved_profile(cobbler_api, create_distro):
     assert enums.VALUE_INHERITED not in str(result)
 
 
-def test_to_dict_resolved_image(cobbler_api, create_image):
+def test_to_dict_resolved_image(
+    cobbler_api: CobblerAPI, create_image: Callable[[], Image]
+):
+    """
+    Test that verfies that a system which is based on an image can be converted to a dictionary successfully.
+    """
     # Arrange
     test_image = create_image()
     test_image.kernel_options = {"test": True}
@@ -91,7 +119,10 @@ def test_to_dict_resolved_image(cobbler_api, create_image):
 # Properties Tests
 
 
-def test_ipv6_autoconfiguration(cobbler_api):
+def test_ipv6_autoconfiguration(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "ipv6_autoconfiguration".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -102,7 +133,10 @@ def test_ipv6_autoconfiguration(cobbler_api):
     assert not system.ipv6_autoconfiguration
 
 
-def test_repos_enabled(cobbler_api):
+def test_repos_enabled(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "repos_enabled".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -113,7 +147,10 @@ def test_repos_enabled(cobbler_api):
     assert not system.repos_enabled
 
 
-def test_autoinstall(cobbler_api):
+def test_autoinstall(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "autoinstall".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -135,19 +172,26 @@ def test_autoinstall(cobbler_api):
     ],
 )
 def test_boot_loaders(
-    request,
-    cobbler_api,
-    create_distro,
-    create_profile,
-    input_value,
-    expected_exception,
-    expected_output,
+    request: "pytest.FixtureRequest",
+    cobbler_api: CobblerAPI,
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+    input_value: Any,
+    expected_exception: Any,
+    expected_output: List[str],
 ):
+    """
+    Test that verfies the functionality of the property "boot_loaders".
+    """
     # Arrange
     tmp_distro = create_distro()
     tmp_profile = create_profile(tmp_distro.name)
     system = System(cobbler_api)
-    system.name = request.node.originalname
+    system.name = (
+        request.node.originalname  # type: ignore
+        if request.node.originalname  # type: ignore
+        else request.node.name
+    )
     system.profile = tmp_profile.name
 
     # Act
@@ -176,7 +220,10 @@ def test_boot_loaders(
         (True, does_not_raise()),
     ],
 )
-def test_enable_ipxe(cobbler_api, value, expected):
+def test_enable_ipxe(cobbler_api: CobblerAPI, value: Any, expected: Any):
+    """
+    Test that verfies the functionality of the property "enable_ipxe".
+    """
     # Arrange
     distro = System(cobbler_api)
 
@@ -189,7 +236,10 @@ def test_enable_ipxe(cobbler_api, value, expected):
         assert distro.enable_ipxe or not distro.enable_ipxe
 
 
-def test_gateway(cobbler_api):
+def test_gateway(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "gateway".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -200,7 +250,10 @@ def test_gateway(cobbler_api):
     assert system.gateway == ""
 
 
-def test_hostname(cobbler_api):
+def test_hostname(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "hostname".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -211,7 +264,10 @@ def test_hostname(cobbler_api):
     assert system.hostname == ""
 
 
-def test_image(cobbler_api):
+def test_image(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "image".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -222,7 +278,10 @@ def test_image(cobbler_api):
     assert system.image == ""
 
 
-def test_ipv6_default_device(cobbler_api):
+def test_ipv6_default_device(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "ipv6_default_device".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -233,7 +292,10 @@ def test_ipv6_default_device(cobbler_api):
     assert system.ipv6_default_device == ""
 
 
-def test_name_servers(cobbler_api):
+def test_name_servers(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "name_servers".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -244,7 +306,10 @@ def test_name_servers(cobbler_api):
     assert system.name_servers == []
 
 
-def test_name_servers_search(cobbler_api):
+def test_name_servers_search(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "name_servers_search".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -269,7 +334,10 @@ def test_name_servers_search(cobbler_api):
         (True, does_not_raise()),
     ],
 )
-def test_netboot_enabled(cobbler_api, value, expected):
+def test_netboot_enabled(cobbler_api: CobblerAPI, value: Any, expected: Any):
+    """
+    Test that verfies the functionality of the property "netboot_enabled".
+    """
     # Arrange
     distro = System(cobbler_api)
 
@@ -291,8 +359,14 @@ def test_netboot_enabled(cobbler_api, value, expected):
     ],
 )
 def test_next_server_v4(
-    cobbler_api, input_next_server, expected_exception, expected_result
+    cobbler_api: CobblerAPI,
+    input_next_server: Any,
+    expected_exception: Any,
+    expected_result: str,
 ):
+    """
+    Test that verfies the functionality of the property "next_server_v4".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -313,8 +387,14 @@ def test_next_server_v4(
     ],
 )
 def test_next_server_v6(
-    cobbler_api, input_next_server, expected_exception, expected_result
+    cobbler_api: CobblerAPI,
+    input_next_server: Any,
+    expected_exception: Any,
+    expected_result: str,
 ):
+    """
+    Test that verfies the functionality of the property "next_server_v6".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -326,18 +406,30 @@ def test_next_server_v6(
         assert system.next_server_v6 == expected_result
 
 
-def test_filename(cobbler_api):
+def test_filename(
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+    create_system: Callable[[str, str, str], System],
+):
+    """
+    Test that verfies the functionality of the property "filename".
+    """
     # Arrange
-    system = System(cobbler_api)
+    test_distro = create_distro()
+    test_profile = create_profile(test_distro.name)
+    test_system: System = create_system(profile_name=test_profile.name)  # type: ignore
 
     # Act
-    system.filename = "<<inherit>>"
+    test_system.filename = "<<inherit>>"
 
     # Assert
-    assert system.filename == "<<inherit>>"
+    assert test_system.filename == ""
 
 
-def test_power_address(cobbler_api):
+def test_power_address(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_address".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -348,7 +440,10 @@ def test_power_address(cobbler_api):
     assert system.power_address == ""
 
 
-def test_power_id(cobbler_api):
+def test_power_id(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_id".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -359,7 +454,10 @@ def test_power_id(cobbler_api):
     assert system.power_id == ""
 
 
-def test_power_pass(cobbler_api):
+def test_power_pass(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_pass".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -370,7 +468,10 @@ def test_power_pass(cobbler_api):
     assert system.power_pass == ""
 
 
-def test_power_type(cobbler_api):
+def test_power_type(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_type".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -381,7 +482,10 @@ def test_power_type(cobbler_api):
     assert system.power_type == "docker"
 
 
-def test_power_user(cobbler_api):
+def test_power_user(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_user".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -392,7 +496,10 @@ def test_power_user(cobbler_api):
     assert system.power_user == ""
 
 
-def test_power_options(cobbler_api):
+def test_power_options(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_options".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -403,7 +510,10 @@ def test_power_options(cobbler_api):
     assert system.power_options == ""
 
 
-def test_power_identity_file(cobbler_api):
+def test_power_identity_file(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "power_identity_file".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -414,7 +524,10 @@ def test_power_identity_file(cobbler_api):
     assert system.power_identity_file == ""
 
 
-def test_profile(cobbler_api):
+def test_profile(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "profile".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -433,7 +546,15 @@ def test_profile(cobbler_api):
         (False, pytest.raises(TypeError), ""),
     ],
 )
-def test_proxy(cobbler_api, input_proxy, expected_exception, expected_result):
+def test_proxy(
+    cobbler_api: CobblerAPI,
+    input_proxy: Any,
+    expected_exception: Any,
+    expected_result: str,
+):
+    """
+    Test that verfies the functionality of the property "proxy".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -454,8 +575,14 @@ def test_proxy(cobbler_api, input_proxy, expected_exception, expected_result):
     ],
 )
 def test_redhat_management_key(
-    cobbler_api, input_redhat_management_key, expected_exception, expected_result
+    cobbler_api: CobblerAPI,
+    input_redhat_management_key: Any,
+    expected_exception: Any,
+    expected_result: str,
 ):
+    """
+    Test that verfies the functionality of the property "redhat_management_key".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -476,7 +603,15 @@ def test_redhat_management_key(
         (False, pytest.raises(TypeError), None),
     ],
 )
-def test_server(cobbler_api, input_server, expected_exception, expected_result):
+def test_server(
+    cobbler_api: CobblerAPI,
+    input_server: Any,
+    expected_exception: Any,
+    expected_result: Optional[str],
+):
+    """
+    Test that verfies the functionality of the property "server".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -488,7 +623,10 @@ def test_server(cobbler_api, input_server, expected_exception, expected_result):
         assert system.server == expected_result
 
 
-def test_status(cobbler_api):
+def test_status(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "status".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -510,7 +648,12 @@ def test_status(cobbler_api):
         ("<<inherit>>", does_not_raise(), True),
     ],
 )
-def test_virt_auto_boot(cobbler_api, value, expected_exception, expected_result):
+def test_virt_auto_boot(
+    cobbler_api: CobblerAPI, value: Any, expected_exception: Any, expected_result: bool
+):
+    """
+    Test that verfies the functionality of the property "virt_auto_boot".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -532,7 +675,12 @@ def test_virt_auto_boot(cobbler_api, value, expected_exception, expected_result)
         (5, does_not_raise(), 5),
     ],
 )
-def test_virt_cpus(cobbler_api, value, expected_exception, expected_result):
+def test_virt_cpus(
+    cobbler_api: CobblerAPI, value: Any, expected_exception: Any, expected_result: int
+):
+    """
+    Test that verfies the functionality of the property "virt_cpus".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -554,7 +702,15 @@ def test_virt_cpus(cobbler_api, value, expected_exception, expected_result):
         ("", pytest.raises(ValueError), None),
     ],
 )
-def test_virt_disk_driver(cobbler_api, value, expected_exception, expected_result):
+def test_virt_disk_driver(
+    cobbler_api: CobblerAPI,
+    value: Any,
+    expected_exception: Any,
+    expected_result: Optional[enums.VirtDiskDrivers],
+):
+    """
+    Test that verfies the functionality of the property "virt_disk_driver".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -575,8 +731,14 @@ def test_virt_disk_driver(cobbler_api, value, expected_exception, expected_resul
     ],
 )
 def test_virt_file_size(
-    cobbler_api, input_virt_file_size, expected_exception, expected_result
+    cobbler_api: CobblerAPI,
+    input_virt_file_size: Any,
+    expected_exception: Any,
+    expected_result: float,
 ):
+    """
+    Test that verfies the functionality of the property "virt_file_size".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -597,13 +759,16 @@ def test_virt_file_size(
     ],
 )
 def test_virt_path(
-    cobbler_api,
-    create_distro,
-    create_profile,
-    input_path,
-    expected_exception,
-    expected_result,
+    cobbler_api: CobblerAPI,
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+    input_path: Any,
+    expected_exception: Any,
+    expected_result: Optional[str],
 ):
+    """
+    Test that verfies the functionality of the property "virt_path".
+    """
     # Arrange
     tmp_distro = create_distro()
     tmp_profile = create_profile(tmp_distro.name)
@@ -618,7 +783,10 @@ def test_virt_path(
         assert system.virt_path == expected_result
 
 
-def test_virt_pxe_boot(cobbler_api):
+def test_virt_pxe_boot(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "virt_pxe_boot".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -639,13 +807,16 @@ def test_virt_pxe_boot(cobbler_api):
     ],
 )
 def test_virt_ram(
-    cobbler_api,
-    create_distro,
-    create_profile,
-    value,
-    expected_exception,
-    expected_result,
+    cobbler_api: CobblerAPI,
+    create_distro: Callable[[], Distro],
+    create_profile: Callable[[str], Profile],
+    value: Any,
+    expected_exception: Any,
+    expected_result: int,
 ):
+    """
+    Test that verfies the functionality of the property "virt_ram".
+    """
     # Arrange
     distro = create_distro()
     profile = create_profile(distro.name)
@@ -670,7 +841,15 @@ def test_virt_ram(
         (False, pytest.raises(TypeError), None),
     ],
 )
-def test_virt_type(cobbler_api, value, expected_exception, expected_result):
+def test_virt_type(
+    cobbler_api: CobblerAPI,
+    value: Any,
+    expected_exception: Any,
+    expected_result: Optional[enums.VirtType],
+):
+    """
+    Test that verfies the functionality of the property "virt_type".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -682,7 +861,10 @@ def test_virt_type(cobbler_api, value, expected_exception, expected_result):
         assert system.virt_type == expected_result
 
 
-def test_serial_device(cobbler_api):
+def test_serial_device(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "serial_device".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -701,7 +883,10 @@ def test_serial_device(cobbler_api):
         # FIXME: (False, pytest.raises(TypeError)) --> This does not raise a TypeError but instead a value Error.
     ],
 )
-def test_serial_baud_rate(cobbler_api, value, expected_exception):
+def test_serial_baud_rate(cobbler_api: CobblerAPI, value: Any, expected_exception: Any):
+    """
+    Test that verfies the functionality of the property "serial_baud_rate".
+    """
     # Arrange
     system = System(cobbler_api)
 
@@ -716,7 +901,10 @@ def test_serial_baud_rate(cobbler_api, value, expected_exception):
             assert system.serial_baud_rate == value
 
 
-def test_from_dict_with_network_interface(cobbler_api):
+def test_from_dict_with_network_interface(cobbler_api: CobblerAPI):
+    """
+    Test that verifies that the ``to_dict`` method works with network interfaces.
+    """
     # Arrange
     system = System(cobbler_api)
     system.interfaces = {"default": NetworkInterface(cobbler_api)}
@@ -757,8 +945,14 @@ def test_from_dict_with_network_interface(cobbler_api):
     ],
 )
 def test_network_interface_type(
-    cobbler_api, value, expected_exception, expected_result
+    cobbler_api: CobblerAPI,
+    value: Any,
+    expected_exception: Any,
+    expected_result: Optional[enums.NetworkInterfaceType],
 ):
+    """
+    Test that verifies that the ``NetworkInterface.interface_type`` works as expected.
+    """
     # Arrange
     interface = NetworkInterface(cobbler_api)
 
@@ -781,8 +975,15 @@ def test_network_interface_type(
     ],
 )
 def test_is_management_supported(
-    cobbler_api, input_mac, input_ipv4, input_ipv6, expected_result
+    cobbler_api: CobblerAPI,
+    input_mac: str,
+    input_ipv4: str,
+    input_ipv6: str,
+    expected_result: bool,
 ):
+    """
+    Test that verifies that the ``is_management_supported()`` works as expected.
+    """
     # Arrange
     system = System(cobbler_api)
     system.interfaces = {"default": NetworkInterface(cobbler_api)}
@@ -797,7 +998,10 @@ def test_is_management_supported(
     assert result is expected_result
 
 
-def test_display_name(cobbler_api):
+def test_display_name(cobbler_api: CobblerAPI):
+    """
+    Test that verfies the functionality of the property "display_name".
+    """
     # Arrange
     system = System(cobbler_api)
 
diff --git a/tests/xmlrpcapi/profile_test.py b/tests/xmlrpcapi/profile_test.py
--- a/tests/xmlrpcapi/profile_test.py
+++ b/tests/xmlrpcapi/profile_test.py
@@ -155,11 +155,13 @@ def test_create_profile_negative(
 
     # Act & Assert
     try:
-        remote.modify_profile(profile, field_name, field_value, token)
+        result = remote.modify_profile(profile, field_name, field_value, token)
     except (CX, TypeError, ValueError, OSError):
         assert True
     else:
-        pytest.fail("Bad field did not raise an exception!")
+        if result:
+            pytest.fail("Bad field did not raise an exception!")
+        assert True
 
 
 @pytest.mark.usefixtures(