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()