From cecfc4de9846e725082bf69cbe78052dc09c57ac Mon Sep 17 00:00:00 2001
From: Dzmitry Pryskoka <mr.priskoka@yandex.com>
Date: Sun, 21 Jan 2024 22:45:38 +0300
Subject: [PATCH] Add tests

---
 requirements.txt                              |   1 +
 src/kataloger/catalog_updater.py              |   8 +-
 src/kataloger/catalog_updater_builder.py      |  19 +-
 src/kataloger/data/artifact_metadata.py       |  14 -
 src/kataloger/helpers/xml_parse_helpers.py    |   4 +-
 tests/data/artifact/test_library.py           |  13 +
 tests/data/artifact/test_plugin.py            |  13 +
 tests/data/test_repository.py                 |  50 +++
 tests/entity_factory.py                       |  60 +++
 tests/helpers/test_toml_parse_helpers.py      | 333 +++++++++++++++
 tests/helpers/test_xml_parse_helpers.py       | 221 ++++++++++
 tests/test_catalog_updater.py                 | 385 ++++++++++++++++++
 tests/test_catalog_updater_builder.py         | 115 ++++++
 .../universal/test_universal_version.py       |   8 +-
 .../test_universal_version_factory.py         |  19 +-
 15 files changed, 1223 insertions(+), 40 deletions(-)
 create mode 100644 tests/data/artifact/test_library.py
 create mode 100644 tests/data/artifact/test_plugin.py
 create mode 100644 tests/data/test_repository.py
 create mode 100644 tests/entity_factory.py
 create mode 100644 tests/helpers/test_toml_parse_helpers.py
 create mode 100644 tests/helpers/test_xml_parse_helpers.py
 create mode 100644 tests/test_catalog_updater.py
 create mode 100644 tests/test_catalog_updater_builder.py

diff --git a/requirements.txt b/requirements.txt
index d9bc4d9..a3cca10 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,7 @@ attrs==23.2.0
 charset-normalizer==3.3.2
 frozenlist==1.4.1
 pytest==7.4.4
+pytest-asyncio==0.23.3
 idna==3.6
 multidict==6.0.4
 xmltodict==0.13.0
diff --git a/src/kataloger/catalog_updater.py b/src/kataloger/catalog_updater.py
index cd83592..b109187 100644
--- a/src/kataloger/catalog_updater.py
+++ b/src/kataloger/catalog_updater.py
@@ -23,7 +23,7 @@ def __init__(
         update_resolvers: list[UpdateResolver],
         verbose: bool = False,
     ):
-        if not library_repositories or not plugin_repositories:
+        if not (library_repositories or plugin_repositories):
             raise KatalogerConfigurationException("No repositories provided!")
 
         if not update_resolvers:
@@ -50,7 +50,11 @@ async def get_artifact_updates(self, artifacts: list[Artifact]) -> list[Artifact
         library_updates, plugin_updates = await self.get_updates(libraries, plugins)
         return library_updates + plugin_updates
 
-    async def get_updates(self, libraries: list[Library], plugins: list[Plugin]) -> tuple[list[ArtifactUpdate], list[ArtifactUpdate]]:
+    async def get_updates(
+        self,
+        libraries: list[Library],
+        plugins: list[Plugin],
+    ) -> tuple[list[ArtifactUpdate], list[ArtifactUpdate]]:
         library_updates = await self.get_library_updates(libraries)
         plugin_updates = await self.get_plugin_updates(plugins)
         return library_updates, plugin_updates
diff --git a/src/kataloger/catalog_updater_builder.py b/src/kataloger/catalog_updater_builder.py
index e48af08..7788b03 100644
--- a/src/kataloger/catalog_updater_builder.py
+++ b/src/kataloger/catalog_updater_builder.py
@@ -10,11 +10,12 @@
 
 class CatalogUpdaterBuilder:
 
-    repositories_path: Optional[Path] = None
-    library_repositories: list[Repository] = []
-    plugin_repositories: list[Repository] = []
-    update_resolvers: list[UpdateResolver] = []
-    verbose: bool = False
+    def __init__(self):
+        self.repositories_path: Optional[Path] = None
+        self.library_repositories: list[Repository] = []
+        self.plugin_repositories: list[Repository] = []
+        self.update_resolvers: list[UpdateResolver] = []
+        self.verbose: bool = False
 
     def set_repositories_path(self, path: Path) -> Self:
         if not (path.exists() and path.is_file()):
@@ -50,8 +51,8 @@ def build(self) -> CatalogUpdater:
             self.plugin_repositories.extend(plugin_repos)
 
         return CatalogUpdater(
-            self.library_repositories,
-            self.plugin_repositories,
-            self.update_resolvers,
-            self.verbose,
+            library_repositories=self.library_repositories,
+            plugin_repositories=self.plugin_repositories,
+            update_resolvers=self.update_resolvers,
+            verbose=self.verbose,
         )
diff --git a/src/kataloger/data/artifact_metadata.py b/src/kataloger/data/artifact_metadata.py
index 4c4c856..b620424 100644
--- a/src/kataloger/data/artifact_metadata.py
+++ b/src/kataloger/data/artifact_metadata.py
@@ -1,9 +1,7 @@
 from dataclasses import dataclass
-from functools import total_ordering
 
 
 @dataclass(frozen=True)
-@total_ordering
 class ArtifactMetadata:
     latest_version: str
     release_version: str
@@ -12,15 +10,3 @@ class ArtifactMetadata:
 
     def __repr__(self):
         return f"{len(self.versions)}/{self.latest_version}"
-
-    def __eq__(self, other) -> bool:
-        if not isinstance(other, ArtifactMetadata):
-            return False
-
-        return self.last_updated == other.last_updated
-
-    def __lt__(self, other) -> bool:
-        if not isinstance(other, ArtifactMetadata):
-            return False
-
-        return self.last_updated == other.last_updated
diff --git a/src/kataloger/helpers/xml_parse_helpers.py b/src/kataloger/helpers/xml_parse_helpers.py
index 9d3529e..a4380b6 100644
--- a/src/kataloger/helpers/xml_parse_helpers.py
+++ b/src/kataloger/helpers/xml_parse_helpers.py
@@ -8,7 +8,7 @@
 
 def try_parse_maven_group_metadata(response: str) -> Optional[ArtifactMetadata]:
     try:
-        metadata = xmltodict.parse(response)
+        metadata = xmltodict.parse(response.strip())
     except ExpatError:
         return None
 
@@ -24,5 +24,5 @@ def try_parse_maven_group_metadata(response: str) -> Optional[ArtifactMetadata]:
             versions=versions,
             last_updated=int(version_info.get("lastUpdated", 0)),
         )
-    except KeyError:
+    except (KeyError, TypeError):
         return None
diff --git a/tests/data/artifact/test_library.py b/tests/data/artifact/test_library.py
new file mode 100644
index 0000000..2ab941c
--- /dev/null
+++ b/tests/data/artifact/test_library.py
@@ -0,0 +1,13 @@
+from kataloger.data.artifact.library import Library
+
+
+class TestLibrary:
+    def test_library_to_path_should_return_path_part_from_library_coordinates(self):
+        library: Library = Library(
+            name="library",
+            coordinates="com.library.group:library-artifact-id",
+            version="1.0.0",
+        )
+        expected_path_part: str = "com/library/group/library-artifact-id"
+
+        assert library.to_path() == expected_path_part
diff --git a/tests/data/artifact/test_plugin.py b/tests/data/artifact/test_plugin.py
new file mode 100644
index 0000000..91cac47
--- /dev/null
+++ b/tests/data/artifact/test_plugin.py
@@ -0,0 +1,13 @@
+from kataloger.data.artifact.plugin import Plugin
+
+
+class TestPlugin:
+    def test_plugin_to_path_should_return_path_part_from_plugin_coordinates(self):
+        plugin: Plugin = Plugin(
+            name="plugin",
+            coordinates="com.plugin.artifact-id",
+            version="1.0.0",
+        )
+        expected_path_part: str = "com/plugin/artifact-id/com.plugin.artifact-id.gradle.plugin"
+
+        assert plugin.to_path() == expected_path_part
diff --git a/tests/data/test_repository.py b/tests/data/test_repository.py
new file mode 100644
index 0000000..1a84158
--- /dev/null
+++ b/tests/data/test_repository.py
@@ -0,0 +1,50 @@
+from typing import Optional
+
+from yarl import URL
+
+from kataloger.data.repository import Repository
+
+
+class TestRepository:
+    def test_repository_should_require_authorization_when_user_and_password_is_not_none(self):
+        self._test_require_authorization(
+            user="repoUser",
+            password="repoPassword",
+            expected_requires_authorization=True,
+        )
+
+    def test_repository_should_not_require_authorization_when_user_is_not_none_but_password_is_none(self):
+        self._test_require_authorization(
+            user="repoUser",
+            password=None,
+            expected_requires_authorization=False,
+        )
+
+    def test_repository_should_not_require_authorization_when_password_is_not_none_but_user_is_none(self):
+        self._test_require_authorization(
+            user=None,
+            password="repoPassword",
+            expected_requires_authorization=False,
+        )
+
+    def test_repository_should_not_require_authorization_when_user_and_password_is_none(self):
+        self._test_require_authorization(
+            user=None,
+            password=None,
+            expected_requires_authorization=False,
+        )
+
+    @staticmethod
+    def _test_require_authorization(
+        user: Optional[str],
+        password: Optional[str],
+        expected_requires_authorization: bool,
+    ):
+        repository: Repository = Repository(
+            name="repository",
+            address=URL("https://reposito,ry/"),
+            user=user,
+            password=password,
+        )
+
+        assert repository.requires_authorization() == expected_requires_authorization
diff --git a/tests/entity_factory.py b/tests/entity_factory.py
new file mode 100644
index 0000000..7603816
--- /dev/null
+++ b/tests/entity_factory.py
@@ -0,0 +1,60 @@
+from yarl import URL
+
+from kataloger.data.artifact.library import Library
+from kataloger.data.artifact.plugin import Plugin
+from kataloger.data.artifact_update import ArtifactUpdate
+from kataloger.data.repository import Repository
+
+
+class EntityFactory:
+    @staticmethod
+    def create_repository(
+        name: str = "default_repository",
+        address: URL = "https://reposito.ry/",
+        user: str | None = None,
+        password: str | None = None,
+    ) -> Repository:
+        return Repository(
+            name=name,
+            address=address,
+            user=user,
+            password=password,
+        )
+
+    @staticmethod
+    def create_library(
+        name: str = "default_library",
+        coordinates: str = "com.library.group:library",
+        version: str = "1.0.0",
+    ) -> Library:
+        return Library(
+            name=name,
+            coordinates=coordinates,
+            version=version,
+        )
+
+    @staticmethod
+    def create_plugin(
+        name: str = "default_plugin",
+        coordinates: str = "com.library.group:library",
+        version: str = "1.0.0",
+    ) -> Plugin:
+        return Plugin(
+            name=name,
+            coordinates=coordinates,
+            version=version,
+        )
+
+    @staticmethod
+    def create_artifact_update(
+        name: str = "artifact_name",
+        update_repository_name: str = "update_repository_name",
+        current_version: str = "0.1.0",
+        available_version: str = "1.0.0",
+    ) -> ArtifactUpdate:
+        return ArtifactUpdate(
+            name=name,
+            update_repository_name=update_repository_name,
+            current_version=current_version,
+            available_version=available_version,
+        )
diff --git a/tests/helpers/test_toml_parse_helpers.py b/tests/helpers/test_toml_parse_helpers.py
new file mode 100644
index 0000000..d9498b8
--- /dev/null
+++ b/tests/helpers/test_toml_parse_helpers.py
@@ -0,0 +1,333 @@
+from unittest.mock import Mock, patch, mock_open
+
+import pytest
+from yarl import URL
+
+from kataloger.data.artifact.library import Library
+from kataloger.data.artifact.plugin import Plugin
+from kataloger.data.repository import Repository
+from kataloger.execptions.kataloger_parse_exception import KatalogerParseException
+from kataloger.helpers import toml_parse_helpers
+from kataloger.helpers.toml_parse_helpers import parse_plugins, parse_libraries, parse_repositories, \
+    load_toml_to_dict, load_repositories, load_catalog
+
+
+class TestTomlParseHelpers:
+    default_artifact_name = "artifact_name"
+    default_plugin_id = "com.plug.in"
+    default_library_module = "com.library:library-core"
+    default_version = "1.0.0"
+    default_repository_name = "repository_name"
+    default_repository_address = "https://reposito.ry/"
+
+    def test_should_read_toml_file_to_dictionary_when_toml_format_is_correct(self):
+        toml: bytes = b"""\
+        [versions]
+        hilt = "2.50"
+        
+        [libraries]
+        kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version = "1.9.22" }
+        """
+        expected_data: dict = {
+            "versions": {
+                "hilt": "2.50"
+            },
+            "libraries": {
+                "kotlin-stdlib": {
+                    "module": "org.jetbrains.kotlin:kotlin-stdlib",
+                    "version": "1.9.22",
+                }
+            },
+        }
+        with patch("builtins.open", mock_open(read_data=toml)):
+            actual_data: dict = load_toml_to_dict(path=Mock())
+
+        assert actual_data == expected_data
+
+    def test_should_raise_exception_when_toml_format_is_incorrect(self):
+        toml: bytes = b"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"""
+        with patch("builtins.open", mock_open(read_data=toml)):
+            with pytest.raises(KatalogerParseException):
+                load_toml_to_dict(path=Mock())
+
+    def test_should_return_empty_plugins_list_when_catalog_has_no_plugins(self):
+        catalog: dict = {"libraries": {}}
+        expected_plugins: list[Plugin] = []
+        actual_plugins: list[Plugin] = parse_plugins(catalog, versions={}, verbose=False)
+
+        assert actual_plugins == expected_plugins
+
+    def test_should_parse_plugin_when_it_has_id_and_version(self):
+        catalog: dict = {
+            "plugins": {
+                self.default_artifact_name: {
+                    "id": self.default_plugin_id,
+                    "version": self.default_version,
+                }
+            },
+        }
+        expected_plugin: Plugin = Plugin(
+            name=self.default_artifact_name,
+            coordinates=self.default_plugin_id,
+            version=self.default_version,
+        )
+        actual_plugins: list[Plugin] = parse_plugins(catalog, versions={}, verbose=False)
+
+        assert actual_plugins == [expected_plugin]
+
+    def test_should_parse_plugin_when_it_has_id_and_reference_to_version(self):
+        version_reference: str = f"{self.default_artifact_name}.version"
+        catalog: dict = {
+            "plugins": {
+                self.default_artifact_name: {
+                    "id": self.default_plugin_id,
+                    "version": {"ref": version_reference},
+                }
+            },
+        }
+        versions: dict[str, str] = {version_reference: self.default_version}
+        expected_plugin: Plugin = Plugin(
+            name=self.default_artifact_name,
+            coordinates=self.default_plugin_id,
+            version=self.default_version,
+        )
+        actual_plugins: list[Plugin] = parse_plugins(catalog, versions, verbose=False)
+
+        assert actual_plugins == [expected_plugin]
+
+    def test_should_raise_exception_when_there_is_no_plugin_version_by_reference(self):
+        catalog: dict = {
+            "plugins": {
+                self.default_artifact_name: {
+                    "id": self.default_plugin_id,
+                    "version": {"ref": "version_reference"},
+                }
+            },
+        }
+
+        with pytest.raises(KatalogerParseException):
+            parse_plugins(catalog, versions={}, verbose=False)
+
+    def test_should_raise_exception_when_plugin_structure_is_incorrect(self):
+        catalog: dict = {
+            "plugins": {
+                self.default_artifact_name: {
+                    "module": self.default_plugin_id,
+                    "ref": "version",
+                }
+            },
+        }
+
+        with pytest.raises(KatalogerParseException):
+            parse_plugins(catalog, versions={}, verbose=False)
+
+    def test_should_return_empty_libraries_list_when_catalog_has_no_libraries(self):
+        catalog: dict = {"plugins": {}}
+        expected_libraries: list[Library] = []
+        actual_libraries: list[Library] = parse_libraries(catalog, versions={}, verbose=False)
+
+        assert actual_libraries == expected_libraries
+
+    def test_should_parse_library_when_it_has_module_and_version(self):
+        catalog: dict = {
+            "libraries": {
+                self.default_artifact_name: {
+                    "module": self.default_library_module,
+                    "version": self.default_version,
+                }
+            },
+        }
+        expected_library: Library = Library(
+            name=self.default_artifact_name,
+            coordinates=self.default_library_module,
+            version=self.default_version,
+        )
+        actual_libraries: list[Library] = parse_libraries(catalog, versions={}, verbose=False)
+
+        assert actual_libraries == [expected_library]
+
+    def test_should_parse_library_when_it_has_module_and_reference_to_version(self):
+        version_reference: str = f"{self.default_artifact_name}.version"
+        catalog: dict = {
+            "libraries": {
+                self.default_artifact_name: {
+                    "module": self.default_library_module,
+                    "version": {"ref": version_reference},
+                }
+            },
+        }
+        versions: dict[str, str] = {version_reference: self.default_version}
+        expected_library: Library = Library(
+            name=self.default_artifact_name,
+            coordinates=self.default_library_module,
+            version=self.default_version,
+        )
+        actual_libraries: list[Library] = parse_libraries(catalog, versions, verbose=False)
+
+        assert actual_libraries == [expected_library]
+
+    def test_should_raise_exception_when_there_is_no_library_version_by_reference(self):
+        catalog: dict = {
+            "libraries": {
+                self.default_artifact_name: {
+                    "module": self.default_library_module,
+                    "version": {"ref": "version_reference"},
+                }
+            },
+        }
+
+        with pytest.raises(KatalogerParseException):
+            parse_libraries(catalog, versions={}, verbose=False)
+
+    def test_should_raise_exception_when_library_structure_is_incorrect(self):
+        catalog: dict = {
+            "libraries": {
+                self.default_artifact_name: {
+                    "id": self.default_library_module,
+                    "ref": "version",
+                }
+            },
+        }
+
+        with pytest.raises(KatalogerParseException):
+            parse_libraries(catalog, versions={}, verbose=False)
+
+    def test_should_return_empty_repositories_list_when_there_is_no_repositories(self):
+        expected_repositories: list[Repository] = []
+        actual_repositories: list[Repository] = parse_repositories(repositories_data=[])
+
+        assert actual_repositories == expected_repositories
+
+    def test_should_parse_repository_when_it_has_name_and_address(self):
+        data: list[tuple[str, str]] = [
+            (self.default_repository_name, self.default_repository_address),
+        ]
+        expected_repository: Repository = Repository(
+            name=self.default_repository_name,
+            address=URL(self.default_repository_address),
+            user=None,
+            password=None,
+        )
+        actual_repositories: list[Repository] = parse_repositories(data)
+
+        assert actual_repositories == [expected_repository]
+
+    def test_should_parse_repository_when_it_has_name_address_user_and_password(self):
+        repository_user: str = "username"
+        repository_password: str = "password"
+        data: list[tuple] = [
+            (
+                self.default_repository_name, {
+                    "address": self.default_repository_address,
+                    "user": repository_user,
+                    "password": repository_password,
+                },
+            )
+        ]
+        expected_repository: Repository = Repository(
+            name=self.default_repository_name,
+            address=URL(self.default_repository_address),
+            user=repository_user,
+            password=repository_password,
+        )
+        actual_repositories: list[Repository] = parse_repositories(data)
+
+        assert actual_repositories == [expected_repository]
+
+    def test_should_raise_exception_when_there_is_no_repository_user_but_password_present(self):
+        data: list[tuple] = [
+            (
+                self.default_repository_name, {
+                    "address": self.default_repository_address,
+                    "user": "username",
+                },
+            )
+        ]
+
+        with pytest.raises(KatalogerParseException):
+            parse_repositories(data)
+
+    def test_should_raise_exception_when_there_is_no_repository_password_but_user_present(self):
+        data: list[tuple] = [
+            (
+                self.default_repository_name, {
+                    "address": self.default_repository_address,
+                    "password": "password",
+                },
+            )
+        ]
+
+        with pytest.raises(KatalogerParseException):
+            parse_repositories(data)
+
+    def test_should_raise_exception_when_repository_structure_is_incorrect(self):
+        data: list[tuple] = [("repository_name", "repository_address", "repository_port")]
+
+        with pytest.raises(KatalogerParseException):
+            parse_repositories(data)
+
+    def test_should_load_catalog_from_path(self):
+        version_reference: str = f"{self.default_library_module}.version"
+        library_version: str = "1.1.1"
+        catalog: dict = {
+            "versions": {version_reference: library_version},
+            "libraries": {
+                self.default_artifact_name: {
+                    "module": self.default_library_module,
+                    "version": {"ref": version_reference},
+                }
+            },
+            "plugins": {
+                self.default_artifact_name: {
+                    "id": self.default_plugin_id,
+                    "version": self.default_version,
+                }
+            },
+        }
+        expected_library: Library = Library(
+            name=self.default_artifact_name,
+            coordinates=self.default_library_module,
+            version=library_version,
+        )
+        expected_plugin: Plugin = Plugin(
+            name=self.default_artifact_name,
+            coordinates=self.default_plugin_id,
+            version=self.default_version,
+        )
+
+        toml_parse_helpers.load_toml_to_dict = Mock(return_value=catalog)
+        actual_libraries, actual_plugins = load_catalog(catalog_path=Mock(), verbose=False)
+
+        assert actual_libraries == [expected_library]
+        assert actual_plugins == [expected_plugin]
+
+    def test_should_load_repositories_from_path(self):
+        repository_user: str = "username"
+        repository_password: str = "password"
+        repository_data: dict = {
+            "libraries": {
+                self.default_repository_name: self.default_repository_address,
+            },
+            "plugins": {
+                self.default_repository_name: {
+                    "address": self.default_repository_address,
+                    "user": repository_user,
+                    "password": repository_password,
+                },
+            }
+        }
+        expected_library_repository: Repository = Repository(
+            name=self.default_repository_name,
+            address=URL(self.default_repository_address),
+        )
+        expected_plugin_repository: Repository = Repository(
+            name=self.default_repository_name,
+            address=URL(self.default_repository_address),
+            user=repository_user,
+            password=repository_password,
+        )
+        toml_parse_helpers.load_toml_to_dict = Mock(return_value=repository_data)
+        actual_library_repositories, actual_plugin_repositories = load_repositories(Mock())
+
+        assert actual_library_repositories == [expected_library_repository]
+        assert actual_plugin_repositories == [expected_plugin_repository]
diff --git a/tests/helpers/test_xml_parse_helpers.py b/tests/helpers/test_xml_parse_helpers.py
new file mode 100644
index 0000000..66c09c1
--- /dev/null
+++ b/tests/helpers/test_xml_parse_helpers.py
@@ -0,0 +1,221 @@
+from typing import Optional
+
+from kataloger.data.artifact_metadata import ArtifactMetadata
+from kataloger.helpers.xml_parse_helpers import try_parse_maven_group_metadata
+
+
+class TestXmlParseHelpers:
+
+    default_latest_version: str = "1.1.0"
+    default_release_version: str = "1.0.0"
+    default_last_updated: int = 2024
+
+    def test_should_parse_artifact_metadata(self):
+        response: str = self._create_xml_response(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_parse_metadata_with_absent_latest_version_using_last_version_as_latest_version(self):
+        response: str = self._create_xml_response(
+            latest_version=None,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_parse_metadata_with_absent_release_version_using_last_version_as_release_version(self):
+        response: str = self._create_xml_response(
+            latest_version=self.default_latest_version,
+            release_version=None,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_latest_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_parse_metadata_with_only_one_version(self):
+        response: str = self._create_xml_response(
+            latest_version=self.default_latest_version,
+            release_version=self.default_latest_version,
+            versions=[self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_latest_version,
+            versions=[self.default_latest_version],
+            last_updated=self.default_last_updated,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_parse_metadata_with_absent_last_updated_element_using_zero_as_last_updated(self):
+        response: str = self._create_xml_response(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=None,
+        )
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[self.default_release_version, self.default_latest_version],
+            last_updated=0,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_parse_metadata_with_absent_schema_tag(self):
+        response: str = f"""\
+            <metadata>
+            <groupId>com.library</groupId>
+            <artifactId>artifact-id</artifactId>
+            <versioning>
+                <versions>
+                    <version>{self.default_latest_version}</version>
+                </versions>
+            </versioning>
+            </metadata>
+        """
+        expected_metadata: ArtifactMetadata = ArtifactMetadata(
+            latest_version=self.default_latest_version,
+            release_version=self.default_latest_version,
+            versions=[self.default_latest_version],
+            last_updated=0,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata == expected_metadata
+
+    def test_should_return_none_when_metadata_has_empty_versions_tag(self):
+        response: str = self._create_xml_response(
+            latest_version=self.default_latest_version,
+            release_version=self.default_release_version,
+            versions=[],
+            last_updated=self.default_last_updated,
+        )
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata is None
+
+    def test_should_return_none_when_metadata_has_empty_versioning_tag(self):
+        response: str = f"""\
+            <?xml version="1.0" encoding="UTF-8"?>
+            <metadata>
+            <groupId>com.library</groupId>
+            <artifactId>artifact-id</artifactId>
+            <versioning></versioning>
+            </metadata>
+        """
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata is None
+
+    def test_should_return_none_when_metadata_has_no_versioning_tag(self):
+        response: str = f"""\
+            <?xml version="1.0" encoding="UTF-8"?>
+            <metadata>
+            <groupId>com.library</groupId>
+            <artifactId>artifact-id</artifactId>
+            </metadata>
+        """
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata is None
+
+    def test_should_return_none_when_response_has_not_metadata_xml(self):
+        response: str = f"""\
+            <?xml version="1.0" encoding="UTF-8"?>
+            <note>
+            <script/>
+            <to>Tove</to>
+            <from>Jani</from>
+            <heading>Reminder</heading>
+            <body>Don't forget me this weekend!</body>
+            </note>
+        """
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata is None
+
+    def test_should_return_none_when_response_is_not_xml(self):
+        response: str = """
+            fun main() {
+                val name = "stranger"
+                println("Hi, $name!")
+                print("Current count:")
+                for (i in 0..10) {
+                    print(" $i")
+                }
+            }
+        """
+        actual_metadata: Optional[ArtifactMetadata] = try_parse_maven_group_metadata(response)
+
+        assert actual_metadata is None
+
+    @staticmethod
+    def _create_xml_response(
+        latest_version: Optional[str],
+        release_version: Optional[str],
+        versions: list[str],
+        last_updated: Optional[int],
+    ) -> str:
+        if latest_version:
+            latest_version_element = f"<latest>{latest_version}</latest>"
+        else:
+            latest_version_element = ""
+
+        if release_version:
+            release_version_element = f"<release>{release_version}</release>"
+        else:
+            release_version_element = ""
+
+        if last_updated:
+            last_updated_tag = f"<lastUpdated>{last_updated}</lastUpdated>"
+        else:
+            last_updated_tag = ""
+
+        versions_array = "".join(f"<version>{version}</version>" for version in versions)
+        return f"""
+            <?xml version="1.0" encoding="UTF-8"?>
+            <metadata>
+            <groupId>com.library</groupId>
+            <artifactId>artifact-id</artifactId>
+            <versioning>
+                {latest_version_element}
+                {release_version_element}
+                <versions>{versions_array}</versions>
+                {last_updated_tag}
+            </versioning>
+            </metadata>
+        """
diff --git a/tests/test_catalog_updater.py b/tests/test_catalog_updater.py
new file mode 100644
index 0000000..19e700e
--- /dev/null
+++ b/tests/test_catalog_updater.py
@@ -0,0 +1,385 @@
+from unittest.mock import Mock, patch, AsyncMock, call
+
+import pytest
+
+from entity_factory import EntityFactory
+from kataloger.catalog_updater import CatalogUpdater
+from kataloger.data.artifact.library import Library
+from kataloger.data.artifact.plugin import Plugin
+from kataloger.data.artifact_update import ArtifactUpdate
+from kataloger.data.repository import Repository
+from kataloger.execptions.kataloger_configuration_exception import KatalogerConfigurationException
+from kataloger.update_resolver.base.update_resolution import UpdateResolution
+from kataloger.update_resolver.base.update_resolver import UpdateResolver
+
+
+class TestCatalogUpdater:
+
+    def test_should_raise_configuration_exception_when_no_library_and_plugin_repositories_provided(self):
+        with pytest.raises(KatalogerConfigurationException, match="No repositories provided!"):
+            self._create_catalog_updater(
+                library_repositories=[],
+                plugin_repositories=[],
+                update_resolvers=[Mock()],
+            )
+
+    def test_should_not_raise_configuration_exception_when_no_library_repositories_provided(self):
+        repository: Repository = EntityFactory.create_repository()
+        self._create_catalog_updater(
+            library_repositories=[],
+            plugin_repositories=[repository],
+            update_resolvers=[Mock()],
+        )
+
+    def test_should_not_raise_configuration_exception_when_no_plugin_repositories_provided(self):
+        repository: Repository = EntityFactory.create_repository()
+        self._create_catalog_updater(
+            library_repositories=[repository],
+            plugin_repositories=[],
+            update_resolvers=[Mock()],
+        )
+
+    def test_should_raise_configuration_exception_when_no_update_resolvers_provided(self):
+        repository: Repository = EntityFactory.create_repository()
+        with pytest.raises(KatalogerConfigurationException, match="No update resolvers provided!"):
+            self._create_catalog_updater(
+                library_repositories=[repository],
+                plugin_repositories=[repository],
+                update_resolvers=[],
+            )
+
+    def test_try_find_update_should_return_none_when_resolver_update_resolution_is_no_updates(self):
+        library: Library = EntityFactory.create_library()
+        repository: Repository = EntityFactory.create_repository()
+        resolver_mock: Mock = self._create_resolver_mock(resolution=UpdateResolution.NO_UPDATES)
+
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        actual_update: ArtifactUpdate = catalog_updater.try_find_update(artifact=library, repositories_metadata=[])
+
+        assert actual_update is None
+        resolver_mock.resolve.assert_called_once()
+
+    def test_try_find_update_should_return_update_when_resolver_update_resolution_is_update_found(self):
+        library: Library = EntityFactory.create_library()
+        repository: Repository = EntityFactory.create_repository()
+        expected_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=repository.name,
+        )
+        resolver_mock: Mock = self._create_resolver_mock(UpdateResolution.UPDATE_FOUND, expected_update)
+
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        actual_update: ArtifactUpdate = catalog_updater.try_find_update(artifact=library, repositories_metadata=[])
+
+        assert actual_update == expected_update
+        resolver_mock.resolve.assert_called_once()
+
+    def test_try_find_update_should_return_update_when_first_resolver_cant_resolve_update_but_second_does(self):
+        library: Library = EntityFactory.create_library()
+        repository: Repository = EntityFactory.create_repository()
+        metadata_mock: list[Mock] = [Mock()]
+        expected_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=repository.name,
+        )
+        first_resolver: Mock = self._create_resolver_mock(UpdateResolution.CANT_RESOLVE)
+        second_resolver: Mock = self._create_resolver_mock(UpdateResolution.UPDATE_FOUND, expected_update)
+
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            update_resolvers=[first_resolver, second_resolver],
+        )
+        actual_update: ArtifactUpdate = catalog_updater.try_find_update(
+            artifact=library,
+            repositories_metadata=metadata_mock,
+        )
+
+        assert actual_update == expected_update
+        first_resolver.resolve.assert_called_once_with(library, metadata_mock)
+        second_resolver.resolve.assert_called_once_with(library, metadata_mock)
+
+    @pytest.mark.asyncio
+    async def test_get_library_updates_should_return_no_updates_when_there_is_no_library_repositories(self):
+        library: Library = EntityFactory.create_library()
+        repository: Repository = EntityFactory.create_repository()
+        resolver_mock: Mock = self._create_resolver_mock(UpdateResolution.NO_UPDATES)
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            plugin_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        actual_updates: list[ArtifactUpdate] = await catalog_updater.get_library_updates(libraries=[library])
+
+        assert actual_updates == []
+        resolver_mock.resolve.assert_not_called()
+
+    @pytest.mark.asyncio
+    async def test_get_library_updates_should_get_metadata_for_libraries_and_return_not_none_updates(self):
+        library: Library = EntityFactory.create_library()
+        repository: Repository = EntityFactory.create_repository()
+        expected_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=repository.name,
+        )
+        libraries = [library, library]
+        resolver_mock: Mock = Mock()
+        resolver_mock.resolve.side_effect = [
+            (UpdateResolution.NO_UPDATES, None),
+            (UpdateResolution.UPDATE_FOUND, expected_update),
+        ]
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        repositories_metadata = {Mock(): Mock(), Mock(): Mock()}
+        with patch(
+            target="kataloger.catalog_updater.get_all_artifact_metadata",
+            new=AsyncMock(return_value=repositories_metadata),
+        ) as load_metadata_mock:
+            actual_updates: list[ArtifactUpdate] = await catalog_updater.get_library_updates(libraries)
+
+        assert actual_updates == [expected_update]
+        assert resolver_mock.resolve.call_count == 2
+        load_metadata_mock.assert_called_once_with(
+            artifacts=libraries,
+            repositories=[repository],
+            verbose=False,
+        )
+
+    @pytest.mark.asyncio
+    async def test_get_plugin_updates_should_return_no_updates_when_there_is_no_plugin_repositories(self):
+        plugin: Plugin = EntityFactory.create_plugin()
+        repository: Repository = EntityFactory.create_repository()
+        resolver_mock: Mock = self._create_resolver_mock(UpdateResolution.NO_UPDATES)
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        actual_updates: list[ArtifactUpdate] = await catalog_updater.get_plugin_updates(plugins=[plugin])
+
+        assert actual_updates == []
+        resolver_mock.resolve.assert_not_called()
+
+    @pytest.mark.asyncio
+    async def test_get_plugin_updates_should_get_metadata_for_plugins_and_return_not_none_updates(self):
+        plugin: Plugin = EntityFactory.create_plugin()
+        repository: Repository = EntityFactory.create_repository()
+        expected_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=plugin.name,
+            update_repository_name=repository.name,
+        )
+        plugins = [plugin, plugin]
+        resolver_mock: Mock = Mock()
+        resolver_mock.resolve.side_effect = [
+            (UpdateResolution.NO_UPDATES, None),
+            (UpdateResolution.UPDATE_FOUND, expected_update),
+        ]
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            plugin_repositories=[repository],
+            update_resolvers=[resolver_mock],
+        )
+        repositories_metadata = {Mock(): Mock(), Mock(): Mock()}
+        with patch(
+            target="kataloger.catalog_updater.get_all_artifact_metadata",
+            new=AsyncMock(return_value=repositories_metadata),
+        ) as load_metadata_mock:
+            actual_updates: list[ArtifactUpdate] = await catalog_updater.get_plugin_updates(plugins)
+
+        assert actual_updates == [expected_update]
+        assert resolver_mock.resolve.call_count == 2
+        load_metadata_mock.assert_called_once_with(
+            artifacts=plugins,
+            repositories=[repository],
+            verbose=False,
+        )
+
+    @pytest.mark.asyncio
+    async def test_get_updates_should_return_updates_for_libraries_and_plugins(self):
+        library: Library = EntityFactory.create_library()
+        plugin: Plugin = EntityFactory.create_plugin()
+        library_repository: Repository = EntityFactory.create_repository(name="library_repository")
+        plugin_repository: Repository = EntityFactory.create_repository(name="plugin_repository")
+        library_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=library_repository.name,
+        )
+        plugin_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=plugin.name,
+            update_repository_name=plugin_repository.name,
+        )
+        resolver_mock: Mock = Mock()
+        resolver_mock.resolve.side_effect = [
+            (UpdateResolution.UPDATE_FOUND, library_update),
+            (UpdateResolution.UPDATE_FOUND, plugin_update),
+        ]
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[library_repository],
+            plugin_repositories=[plugin_repository],
+            update_resolvers=[resolver_mock],
+        )
+
+        with patch(
+            target="kataloger.catalog_updater.get_all_artifact_metadata",
+            new=AsyncMock(return_value={Mock(): Mock()}),
+        ) as load_metadata_mock:
+            library_updates, plugin_updates = await catalog_updater.get_updates(libraries=[library], plugins=[plugin])
+
+        assert library_updates == [library_update]
+        assert plugin_updates == [plugin_update]
+        assert resolver_mock.resolve.call_count == 2
+        expected_load_metadata_calls = [
+            call(
+                artifacts=[library],
+                repositories=[library_repository],
+                verbose=False,
+            ),
+            call(
+                artifacts=[plugin],
+                repositories=[plugin_repository],
+                verbose=False,
+            )
+        ]
+        assert load_metadata_mock.call_args_list == expected_load_metadata_calls
+
+    @pytest.mark.asyncio
+    async def test_get_artifact_updates_should_return_artifact_updates_from_correct_repositories(self):
+        library: Library = EntityFactory.create_library()
+        plugin: Plugin = EntityFactory.create_plugin()
+        library_repository: Repository = EntityFactory.create_repository(name="library_repository")
+        plugin_repository: Repository = EntityFactory.create_repository(name="plugin_repository")
+        library_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=library_repository.name,
+        )
+        plugin_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=plugin.name,
+            update_repository_name=plugin_repository.name,
+        )
+        resolver_mock: Mock = Mock()
+        resolver_mock.resolve.side_effect = [
+            (UpdateResolution.UPDATE_FOUND, library_update),
+            (UpdateResolution.UPDATE_FOUND, plugin_update),
+        ]
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[library_repository],
+            plugin_repositories=[plugin_repository],
+            update_resolvers=[resolver_mock],
+        )
+
+        with patch(
+            target="kataloger.catalog_updater.get_all_artifact_metadata",
+            new=AsyncMock(return_value={Mock(): Mock()}),
+        ) as load_metadata_mock:
+            artifact_updates: list[ArtifactUpdate] = await catalog_updater.get_artifact_updates(
+                artifacts=[library, plugin],
+            )
+
+        assert artifact_updates == [library_update, plugin_update]
+        assert resolver_mock.resolve.call_count == 2
+        expected_load_metadata_calls = [
+            call(
+                artifacts=[library],
+                repositories=[library_repository],
+                verbose=False,
+            ),
+            call(
+                artifacts=[plugin],
+                repositories=[plugin_repository],
+                verbose=False,
+            )
+        ]
+        assert load_metadata_mock.call_args_list == expected_load_metadata_calls
+
+    @pytest.mark.asyncio
+    async def test_should_return_empty_list_when_there_are_no_libraries_and_plugins_in_loaded_catalog(self):
+        expected_updates: list[ArtifactUpdate] = []
+        repository: Repository = EntityFactory.create_repository()
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[repository],
+            plugin_repositories=[repository],
+            update_resolvers=[Mock()],
+        )
+        with patch("kataloger.catalog_updater.load_catalog", Mock(return_value=([], []))):
+            actual_updates: list[ArtifactUpdate] = await catalog_updater.get_catalog_updates(catalog_path=Mock())
+
+        assert actual_updates == expected_updates
+
+    @pytest.mark.asyncio
+    async def test_should_return_artifact_updates_when_there_are_libraries_and_plugins_in_loaded_catalog(self):
+        library: Library = EntityFactory.create_library()
+        plugin: Plugin = EntityFactory.create_plugin()
+        library_repository: Repository = EntityFactory.create_repository(name="library_repository")
+        plugin_repository: Repository = EntityFactory.create_repository(name="plugin_repository")
+        library_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=library.name,
+            update_repository_name=library_repository.name,
+        )
+        plugin_update: ArtifactUpdate = EntityFactory.create_artifact_update(
+            name=plugin.name,
+            update_repository_name=plugin_repository.name,
+        )
+        resolver_mock: Mock = Mock()
+        resolver_mock.resolve.side_effect = [
+            (UpdateResolution.UPDATE_FOUND, library_update),
+            (UpdateResolution.UPDATE_FOUND, plugin_update),
+        ]
+        catalog_updater: CatalogUpdater = self._create_catalog_updater(
+            library_repositories=[library_repository],
+            plugin_repositories=[plugin_repository],
+            update_resolvers=[resolver_mock],
+        )
+
+        with patch("kataloger.catalog_updater.load_catalog", Mock(return_value=([library], [plugin]))):
+            with patch(
+                target="kataloger.catalog_updater.get_all_artifact_metadata",
+                new=AsyncMock(return_value={Mock(): Mock()}),
+            ) as load_metadata_mock:
+                artifact_updates: list[ArtifactUpdate] = await catalog_updater.get_catalog_updates(catalog_path=Mock())
+
+        assert artifact_updates == [library_update, plugin_update]
+        assert resolver_mock.resolve.call_count == 2
+        expected_load_metadata_calls = [
+            call(
+                artifacts=[library],
+                repositories=[library_repository],
+                verbose=False,
+            ),
+            call(
+                artifacts=[plugin],
+                repositories=[plugin_repository],
+                verbose=False,
+            )
+        ]
+        assert load_metadata_mock.call_args_list == expected_load_metadata_calls
+
+    @staticmethod
+    def _create_resolver_mock(resolution: UpdateResolution, update: ArtifactUpdate | None = None) -> Mock:
+        resolver_mock = Mock()
+        resolver_mock.resolve.return_value = (resolution, update)
+        return resolver_mock
+
+    @staticmethod
+    def _create_catalog_updater(
+        library_repositories: list[Repository] | None = None,
+        plugin_repositories: list[Repository] | None = None,
+        update_resolvers: list[UpdateResolver] | None = None,
+        verbose: bool = False,
+    ) -> CatalogUpdater:
+        if not library_repositories:
+            library_repositories = []
+        if not plugin_repositories:
+            plugin_repositories = []
+        if not update_resolvers:
+            update_resolvers = []
+
+        return CatalogUpdater(
+            library_repositories=library_repositories,
+            plugin_repositories=plugin_repositories,
+            update_resolvers=update_resolvers,
+            verbose=verbose,
+        )
diff --git a/tests/test_catalog_updater_builder.py b/tests/test_catalog_updater_builder.py
new file mode 100644
index 0000000..196527a
--- /dev/null
+++ b/tests/test_catalog_updater_builder.py
@@ -0,0 +1,115 @@
+from unittest.mock import Mock, patch
+
+import pytest
+
+from entity_factory import EntityFactory
+from kataloger import catalog_updater_builder
+from kataloger.catalog_updater import CatalogUpdater
+from kataloger.catalog_updater_builder import CatalogUpdaterBuilder
+from kataloger.data.repository import Repository
+from kataloger.execptions.kataloger_configuration_exception import KatalogerConfigurationException
+
+
+class TestCatalogUpdaterBuilder:
+    def test_set_repositories_should_not_fail_when_path_exists_and_its_a_file(self):
+        path_mock: Mock = self._create_path_mock(exists=True, is_file=True)
+
+        CatalogUpdaterBuilder().set_repositories_path(path_mock)
+
+    def test_set_repositories_should_fail_when_path_exists_but_its_not_a_file(self):
+        path_mock: Mock = self._create_path_mock(exists=True, is_file=False)
+
+        with pytest.raises(KatalogerConfigurationException):
+            CatalogUpdaterBuilder().set_repositories_path(path_mock)
+
+    def test_set_repositories_should_fail_when_path_not_exists(self):
+        path_mock: Mock = self._create_path_mock(exists=False, is_file=True)
+
+        with pytest.raises(KatalogerConfigurationException):
+            CatalogUpdaterBuilder().set_repositories_path(path_mock)
+
+    def test_should_load_repositories_from_provided_path_and_create_catalog_updater_with_them(self):
+        library_repository: Repository = EntityFactory.create_repository()
+        plugin_repository: Repository = EntityFactory.create_repository()
+        load_repositories_mock: Mock = Mock(return_value=([library_repository], [plugin_repository]))
+
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            with patch.object(catalog_updater_builder, "load_repositories", load_repositories_mock):
+                CatalogUpdaterBuilder().set_repositories_path(Mock()).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=[library_repository],
+            plugin_repositories=[plugin_repository],
+            update_resolvers=[],
+            verbose=False,
+        )
+
+    def test_should_create_catalog_updater_with_added_library_repositories(self):
+        library_repositories: list[Repository] = [EntityFactory.create_repository()]
+
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            CatalogUpdaterBuilder().set_library_repositories(library_repositories).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=library_repositories,
+            plugin_repositories=[],
+            update_resolvers=[],
+            verbose=False,
+        )
+
+    def test_should_create_catalog_updater_with_added_plugin_repositories(self):
+        plugin_repositories: list[Repository] = [EntityFactory.create_repository()]
+
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            CatalogUpdaterBuilder().set_plugin_repositories(plugin_repositories).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=[],
+            plugin_repositories=plugin_repositories,
+            update_resolvers=[],
+            verbose=False,
+        )
+
+    def test_should_create_catalog_updater_with_added_update_resolvers(self):
+        update_resolvers = [Mock()]
+
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            CatalogUpdaterBuilder().set_resolvers(update_resolvers).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=[],
+            plugin_repositories=[],
+            update_resolvers=update_resolvers,
+            verbose=False,
+        )
+
+    def test_should_create_catalog_updater_with_added_update_resolver(self):
+        update_resolver = Mock()
+
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            CatalogUpdaterBuilder().add_resolver(update_resolver).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=[],
+            plugin_repositories=[],
+            update_resolvers=[update_resolver],
+            verbose=False,
+        )
+
+    def test_should_create_catalog_updater_in_verbose_mode_when_it_enabled(self):
+        with patch.object(CatalogUpdater, "__init__", Mock(return_value=None)) as init_mock:
+            CatalogUpdaterBuilder().set_verbose(verbose=True).build()
+
+        init_mock.assert_called_once_with(
+            library_repositories=[],
+            plugin_repositories=[],
+            update_resolvers=[],
+            verbose=True,
+        )
+
+    @staticmethod
+    def _create_path_mock(exists: bool, is_file: bool) -> Mock:
+        path_mock: Mock = Mock()
+        path_mock.exists.return_value = exists
+        path_mock.is_file.return_value = is_file
+        return path_mock
diff --git a/tests/update_resolver/universal/test_universal_version.py b/tests/update_resolver/universal/test_universal_version.py
index 3fcf367..ee039ea 100644
--- a/tests/update_resolver/universal/test_universal_version.py
+++ b/tests/update_resolver/universal/test_universal_version.py
@@ -58,7 +58,7 @@ def test_pre_release_number_should_be_extracted_as_int_from_version_if_pre_relea
             "1.2.3-beta.4": 4,
         }
         for (version, expected_pre_release_number) in versions_to_numbers.items():
-            assert expected_pre_release_number == UniversalVersion(version).pre_release_number
+            assert UniversalVersion(version).pre_release_number == expected_pre_release_number
 
     def test_pre_release_number_should_be_extracted_as_0_from_version_if_pre_release_part_not_contains_number(self):
         versions = [
@@ -67,7 +67,7 @@ def test_pre_release_number_should_be_extracted_as_0_from_version_if_pre_release
         ]
         expected_pre_release_number = 0
         for version in versions:
-            assert expected_pre_release_number == UniversalVersion(version).pre_release_number
+            assert UniversalVersion(version).pre_release_number == expected_pre_release_number
 
     def test_pre_release_index_should_be_calculated_correctly_and_case_independent(self):
         versions_to_index = {
@@ -80,7 +80,7 @@ def test_pre_release_index_should_be_calculated_correctly_and_case_independent(s
             "1.2.3-RC01": 3,
         }
         for (version, expected_index) in versions_to_index.items():
-            assert expected_index == UniversalVersion(version)._pre_release_index()
+            assert UniversalVersion(version)._pre_release_index() == expected_index
 
     def test_versions_should_considered_as_equal_when_version_string_representations_are_equals(self):
         first_version = UniversalVersion("1.2.3-pre-release-name-005")
@@ -152,4 +152,4 @@ def test_when_numeric_parts_are_equal_and_pre_release_names_are_not_equals_shoul
     def _version_comparison_test(data: list[tuple[str, str, bool]]):
         for (first_version, second_version, expected_result) in data:
             actual_result = UniversalVersion(first_version) < UniversalVersion(second_version)
-            assert expected_result == actual_result
+            assert actual_result == expected_result
diff --git a/tests/update_resolver/universal/test_universal_version_factory.py b/tests/update_resolver/universal/test_universal_version_factory.py
index 8360ae6..a68dbea 100644
--- a/tests/update_resolver/universal/test_universal_version_factory.py
+++ b/tests/update_resolver/universal/test_universal_version_factory.py
@@ -1,25 +1,26 @@
+from kataloger.update_resolver.universal.universal_version import UniversalVersion
 from kataloger.update_resolver.universal.universal_version_factory import UniversalVersionFactory
 
 
 class TestUniversalVersionFactory:
     def test_create_should_return_universal_version_for_provided_version_string(self):
-        version_string = "1.2.3-alpha01"
-        version = self.create_factory().create(version_string)
+        version_string: str = "1.2.3-alpha01"
+        version: UniversalVersion = self._create_factory().create(version_string)
 
         assert version_string == version.raw
 
     def test_try_create_should_return_false_when_version_string_does_not_match_universal_version_regexp(self):
-        version_string = "RELEASE131"
-        can_create_uv = self.create_factory().can_create(version_string)
+        version_string: str = "RELEASE131"
+        can_create_version: bool = self._create_factory().can_create(version_string)
 
-        assert not can_create_uv
+        assert not can_create_version
 
     def test_try_create_should_return_true_when_version_string_does_match_universal_version_regexp(self):
-        version_string = "1.2.3-alpha01"
-        can_create_uv = self.create_factory().can_create(version_string)
+        version_string: str = "1.2.3-alpha01"
+        can_create_version: bool = self._create_factory().can_create(version_string)
 
-        assert can_create_uv
+        assert can_create_version
 
     @staticmethod
-    def create_factory() -> UniversalVersionFactory:
+    def _create_factory() -> UniversalVersionFactory:
         return UniversalVersionFactory()