From a08db3f37174022dc0f810376972b90a37315fdb Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 11 Jun 2024 13:49:09 -0400 Subject: [PATCH 1/7] fix: make fetch-libs + underscores work Fixes #1681 --- charmcraft/application/commands/store.py | 6 ++++-- charmcraft/application/main.py | 1 + charmcraft/models/project.py | 15 +++++++-------- charmcraft/store/client.py | 5 ++++- tests/unit/models/test_project.py | 6 +----- .../models/valid_charms_yaml/full-platforms.yaml | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py index 0e2374bba..a0df916d7 100644 --- a/charmcraft/application/commands/store.py +++ b/charmcraft/application/commands/store.py @@ -1700,10 +1700,11 @@ def run(self, parsed_args: argparse.Namespace) -> None: lib_name = f"{lib_md.charm_name}.{lib_md.lib_name}" local_lib = local_libs.get(lib_name) if local_lib and local_lib.content_hash == lib_md.content_hash: - emit.debug( + emit.progress( f"Skipping {lib_name} because the same file already exists on " f"disk (hash: {lib_md.content_hash}). " - "Delete the file and re-run 'charmcraft fetch-libs' to force re-download." + "Delete the file and re-run 'charmcraft fetch-libs' to force re-download.", + permanent=True, ) continue lib_name = utils.get_lib_module_name(lib_md.charm_name, lib_md.lib_name, lib_md.api) @@ -1722,6 +1723,7 @@ def run(self, parsed_args: argparse.Namespace) -> None: lib_path = utils.get_lib_path(lib_md.charm_name, lib_md.lib_name, lib_md.api) lib_path.parent.mkdir(exist_ok=True, parents=True) lib_path.write_text(lib.content) + emit.debug(f"Downloaded {lib_name}.") emit.message(f"Downloaded {downloaded_libs} charm libraries.") diff --git a/charmcraft/application/main.py b/charmcraft/application/main.py index bec78bb37..1768cfcbf 100644 --- a/charmcraft/application/main.py +++ b/charmcraft/application/main.py @@ -52,6 +52,7 @@ def __init__( super().__init__(app=app, services=services) self._global_args: dict[str, Any] = {} self._dispatcher: craft_cli.Dispatcher | None = None + self._cli_loggers |= {"charmcraft"} @property def command_groups(self) -> list[craft_cli.CommandGroup]: diff --git a/charmcraft/models/project.py b/charmcraft/models/project.py index cc32425e8..03daf73cf 100644 --- a/charmcraft/models/project.py +++ b/charmcraft/models/project.py @@ -125,8 +125,8 @@ class CharmLib(models.CraftBaseModel): """A Charm library dependency for this charm.""" lib: str = pydantic.Field( - title="Library Path (e.g. my_charm.my_library)", - regex=r"[a-z0-9_]+\.[a-z0-9_]+", + title="Library Path (e.g. my-charm.my_library)", + regex=r"[a-z][a-z0-9_-]+\.[a-z][a-z0-9_]+", ) version: str = pydantic.Field( title="Version filter for the charm. Either an API version or a specific [api].[patch].", @@ -141,16 +141,15 @@ def _validate_name(cls, value: str) -> str: raise ValueError( f"Library name invalid. Expected '[charm_name].[lib_name]', got {value!r}" ) - if not re.fullmatch("[a-z0-9_]+", charm_name): - if "-" in charm_name: - raise ValueError( - f"Invalid charm name in lib {value!r}. Try replacing hyphens ('-') with underscores ('_')." - ) + # Accept python-importable charm names, but convert them to store-accepted names. + if "_" in charm_name: + charm_name = charm_name.replace("_", "-") + if not re.fullmatch("[a-z0-9_-]+", charm_name): raise ValueError( f"Invalid charm name for lib {value!r}. Value {charm_name!r} is invalid." ) if not re.fullmatch("[a-z0-9_]+", lib_name): - raise ValueError(f"Library name {lib_name!r} is invalid.") + raise ValueError(f"Library name {lib_name!r} is invalid. Library names must be valid Python module names.") return str(value) @pydantic.validator("version", pre=True) diff --git a/charmcraft/store/client.py b/charmcraft/store/client.py index 54a0915df..a2fe1060b 100644 --- a/charmcraft/store/client.py +++ b/charmcraft/store/client.py @@ -199,9 +199,12 @@ def fetch_libraries_metadata( http://api.charmhub.io/docs/libraries.html#fetch_libraries """ + emit.trace(f"Fetching library metadata from charmhub: {libs}", ) response = self.request_urlpath_json("POST", "/v1/charm/libraries/bulk", json=libs) if "libraries" not in response: raise CraftError( "Server returned invalid response while querying libraries", details=str(response) ) - return [Library.from_dict(lib) for lib in response["libraries"]] + converted_response = [Library.from_dict(lib) for lib in response["libraries"]] + emit.trace(f"Store response: {converted_response}") + return converted_response diff --git a/tests/unit/models/test_project.py b/tests/unit/models/test_project.py index f4c2cc457..42414d5ce 100644 --- a/tests/unit/models/test_project.py +++ b/tests/unit/models/test_project.py @@ -205,15 +205,11 @@ def test_create_valid_charm_lib(lib_name, lib_version): ("name", "error_match"), [ ("boop", r"Library name invalid. Expected '\[charm_name\].\[lib_name\]', got 'boop'"), - ( - "raw-charm-name.valid_lib", - r"Invalid charm name in lib 'raw-charm-name.valid_lib'. Try replacing hyphens \('-'\) with underscores \('_'\).", - ), ( "Invalid charm name.valid_lib", "Invalid charm name for lib 'Invalid charm name.valid_lib'. Value 'Invalid charm name' is invalid", ), - ("my_charm.invalid library name", "Library name 'invalid library name' is invalid."), + ("my_charm.invalid-library-name", "Library name 'invalid-library-name' is invalid. Library names must be valid Python module names."), ], ) def test_invalid_charm_lib_name(name: str, error_match: str): diff --git a/tests/unit/models/valid_charms_yaml/full-platforms.yaml b/tests/unit/models/valid_charms_yaml/full-platforms.yaml index 855e44561..566815f19 100644 --- a/tests/unit/models/valid_charms_yaml/full-platforms.yaml +++ b/tests/unit/models/valid_charms_yaml/full-platforms.yaml @@ -27,7 +27,7 @@ parts: charm-libs: - lib: my_charm.my_lib version: '1' - - lib: other_charm.lib + - lib: other-charm.lib version: '0.123' base: ubuntu@24.04 build-base: ubuntu@devel From 4ac543008a5615032188aadba19c0dc8c693823b Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 11 Jun 2024 14:24:55 -0400 Subject: [PATCH 2/7] fix: allow fetch-libs to work without logging in Fixes #1671 Requires #1701 --- charmcraft/models/project.py | 4 +- charmcraft/services/store.py | 15 ++++- charmcraft/store/client.py | 76 ++++++++++++------------ tests/integration/services/conftest.py | 6 ++ tests/integration/services/test_store.py | 44 ++++++++++++++ tests/unit/models/test_project.py | 5 +- tests/unit/store/test_client.py | 19 ++++-- 7 files changed, 122 insertions(+), 47 deletions(-) create mode 100644 tests/integration/services/test_store.py diff --git a/charmcraft/models/project.py b/charmcraft/models/project.py index 03daf73cf..7a71e4bee 100644 --- a/charmcraft/models/project.py +++ b/charmcraft/models/project.py @@ -149,7 +149,9 @@ def _validate_name(cls, value: str) -> str: f"Invalid charm name for lib {value!r}. Value {charm_name!r} is invalid." ) if not re.fullmatch("[a-z0-9_]+", lib_name): - raise ValueError(f"Library name {lib_name!r} is invalid. Library names must be valid Python module names.") + raise ValueError( + f"Library name {lib_name!r} is invalid. Library names must be valid Python module names." + ) return str(value) @pydantic.validator("version", pre=True) diff --git a/charmcraft/services/store.py b/charmcraft/services/store.py index 785a9da0b..546a79966 100644 --- a/charmcraft/services/store.py +++ b/charmcraft/services/store.py @@ -22,6 +22,7 @@ import craft_application import craft_store from craft_store import models +from overrides import override from charmcraft import const, env, errors, store from charmcraft.models import CharmLib @@ -164,6 +165,16 @@ class StoreService(BaseStoreService): ClientClass = store.Client client: store.Client # pyright: ignore[reportIncompatibleVariableOverride] + anonymous_client: store.AnonymousClient + + @override + def setup(self) -> None: + """Set up the store service.""" + super().setup() + self.anonymous_client = store.AnonymousClient( + api_base_url=self._base_url, + storage_base_url=self._storage_url, + ) def set_resource_revisions_architectures( self, name: str, resource_name: str, updates: dict[int, list[str]] @@ -209,7 +220,7 @@ def get_libraries_metadata(self, libraries: Sequence[CharmLib]) -> Sequence[Libr store_lib["patch"] = patch_version store_libs.append(store_lib) - return self.client.fetch_libraries_metadata(store_libs) + return self.anonymous_client.fetch_libraries_metadata(store_libs) def get_libraries_metadata_by_name( self, libraries: Sequence[CharmLib] @@ -224,6 +235,6 @@ def get_library( self, charm_name: str, *, library_id: str, api: int | None = None, patch: int | None = None ) -> Library: """Get a library by charm name and ID from charmhub.""" - return self.client.get_library( + return self.anonymous_client.get_library( charm_name=charm_name, library_id=library_id, api=api, patch=patch ) diff --git a/charmcraft/store/client.py b/charmcraft/store/client.py index a2fe1060b..3bcbce428 100644 --- a/charmcraft/store/client.py +++ b/charmcraft/store/client.py @@ -70,6 +70,45 @@ def request_urlpath_json(self, method: str, urlpath: str, *args, **kwargs) -> di f"Could not retrieve json response ({response.status_code} from request" ) from json_error + def get_library( + self, *, charm_name: str, library_id: str, api: int | None = None, patch: int | None = None + ) -> Library: + """Fetch a library attached to a charm. + + http://api.charmhub.io/docs/libraries.html#fetch_library + """ + params = {} + if api is not None: + params["api"] = api + if patch is not None: + params["patch"] = patch + return Library.from_dict( + self.request_urlpath_json( + "GET", + f"/v1/charm/libraries/{charm_name}/{library_id}", + params=params, + ) + ) + + def fetch_libraries_metadata( + self, libs: Sequence[LibraryMetadataRequest] + ) -> Sequence[Library]: + """Fetch the metadata for one or more charm libraries. + + http://api.charmhub.io/docs/libraries.html#fetch_libraries + """ + emit.trace( + f"Fetching library metadata from charmhub: {libs}", + ) + response = self.request_urlpath_json("POST", "/v1/charm/libraries/bulk", json=libs) + if "libraries" not in response: + raise CraftError( + "Server returned invalid response while querying libraries", details=str(response) + ) + converted_response = [Library.from_dict(lib) for lib in response["libraries"]] + emit.trace(f"Store response: {converted_response}") + return converted_response + class Client(craft_store.StoreClient): """Lightweight layer above StoreClient.""" @@ -171,40 +210,3 @@ def _storage_push(self, monitor) -> requests.Response: headers={"Content-Type": monitor.content_type, "Accept": "application/json"}, data=monitor, ) - - def get_library( - self, *, charm_name: str, library_id: str, api: int | None = None, patch: int | None = None - ) -> Library: - """Fetch a library attached to a charm. - - http://api.charmhub.io/docs/libraries.html#fetch_library - """ - params = {} - if api is not None: - params["api"] = api - if patch is not None: - params["patch"] = patch - return Library.from_dict( - self.request_urlpath_json( - "GET", - f"/v1/charm/libraries/{charm_name}/{library_id}", - params=params, - ) - ) - - def fetch_libraries_metadata( - self, libs: Sequence[LibraryMetadataRequest] - ) -> Sequence[Library]: - """Fetch the metadata for one or more charm libraries. - - http://api.charmhub.io/docs/libraries.html#fetch_libraries - """ - emit.trace(f"Fetching library metadata from charmhub: {libs}", ) - response = self.request_urlpath_json("POST", "/v1/charm/libraries/bulk", json=libs) - if "libraries" not in response: - raise CraftError( - "Server returned invalid response while querying libraries", details=str(response) - ) - converted_response = [Library.from_dict(lib) for lib in response["libraries"]] - emit.trace(f"Store response: {converted_response}") - return converted_response diff --git a/tests/integration/services/conftest.py b/tests/integration/services/conftest.py index a21a248dd..0d004f134 100644 --- a/tests/integration/services/conftest.py +++ b/tests/integration/services/conftest.py @@ -14,6 +14,8 @@ # # For further info, check https://github.com/canonical/charmcraft """Configuration for services integration tests.""" +import sys + import pytest from charmcraft import services @@ -25,6 +27,10 @@ def service_factory(fs, fake_path, simple_charm) -> services.CharmcraftServiceFa fake_project_dir = fake_path / "project" fake_project_dir.mkdir() + # Allow access to the real venv library path. + # This is necessary because certifi lazy-loads the certificate file. + fs.add_real_directory(sys.path[-1]) + factory = services.CharmcraftServiceFactory(app=APP_METADATA) app = Charmcraft(app=APP_METADATA, services=factory) diff --git a/tests/integration/services/test_store.py b/tests/integration/services/test_store.py new file mode 100644 index 000000000..05ed870ce --- /dev/null +++ b/tests/integration/services/test_store.py @@ -0,0 +1,44 @@ +# Copyright 2024 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For further info, check https://github.com/canonical/charmcraft +"""Integration tests for the store service.""" + + +import pytest + +from charmcraft import models, services + + +@pytest.fixture() +def store_service(service_factory): + return service_factory.store + + +@pytest.mark.parametrize( + "libraries", + [ + [models.CharmLib(lib="observability-libs.cert_handler", version="1")], + [models.CharmLib(lib="observability-libs.cert_handler", version="1.8")], + ], +) +def test_get_libraries(store_service: services.StoreService, libraries): + libraries_response = store_service.get_libraries_metadata(libraries) + assert len(libraries_response) == len(libraries) + for lib in libraries_response: + full_lib = store_service.get_library( + lib.charm_name, library_id=lib.lib_id, api=lib.api, patch=lib.patch + ) + assert full_lib.content is not None + assert full_lib.content_hash == lib.content_hash diff --git a/tests/unit/models/test_project.py b/tests/unit/models/test_project.py index 42414d5ce..2cdc88a13 100644 --- a/tests/unit/models/test_project.py +++ b/tests/unit/models/test_project.py @@ -209,7 +209,10 @@ def test_create_valid_charm_lib(lib_name, lib_version): "Invalid charm name.valid_lib", "Invalid charm name for lib 'Invalid charm name.valid_lib'. Value 'Invalid charm name' is invalid", ), - ("my_charm.invalid-library-name", "Library name 'invalid-library-name' is invalid. Library names must be valid Python module names."), + ( + "my_charm.invalid-library-name", + "Library name 'invalid-library-name' is invalid. Library names must be valid Python module names.", + ), ], ) def test_invalid_charm_lib_name(name: str, error_match: str): diff --git a/tests/unit/store/test_client.py b/tests/unit/store/test_client.py index 146a02ed0..ccc9df1a4 100644 --- a/tests/unit/store/test_client.py +++ b/tests/unit/store/test_client.py @@ -27,6 +27,11 @@ def client() -> store.Client: return store.Client(api_base_url="http://charmhub.local") +@pytest.fixture() +def anonymous_client() -> store.AnonymousClient: + return store.AnonymousClient("http://charmhub.local", "http://storage.charmhub.local") + + @pytest.mark.parametrize( ("charm", "lib_id", "api", "patch", "expected_call"), [ @@ -48,7 +53,9 @@ def client() -> store.Client: ), ], ) -def test_get_library_success(monkeypatch, client, charm, lib_id, api, patch, expected_call): +def test_get_library_success( + monkeypatch, anonymous_client, charm, lib_id, api, patch, expected_call +): mock_get_urlpath_json = mock.Mock( return_value={ "charm-name": charm, @@ -59,9 +66,9 @@ def test_get_library_success(monkeypatch, client, charm, lib_id, api, patch, exp "hash": "hashy!", } ) - monkeypatch.setattr(client, "request_urlpath_json", mock_get_urlpath_json) + monkeypatch.setattr(anonymous_client, "request_urlpath_json", mock_get_urlpath_json) - client.get_library(charm_name=charm, library_id=lib_id, api=api, patch=patch) + anonymous_client.get_library(charm_name=charm, library_id=lib_id, api=api, patch=patch) mock_get_urlpath_json.assert_has_calls([expected_call]) @@ -98,11 +105,11 @@ def test_get_library_success(monkeypatch, client, charm, lib_id, api, patch, exp ), ], ) -def test_fetch_libraries_metadata(monkeypatch, client, libs, json_response, expected): +def test_fetch_libraries_metadata(monkeypatch, anonymous_client, libs, json_response, expected): mock_get_urlpath_json = mock.Mock(return_value=json_response) - monkeypatch.setattr(client, "request_urlpath_json", mock_get_urlpath_json) + monkeypatch.setattr(anonymous_client, "request_urlpath_json", mock_get_urlpath_json) - assert client.fetch_libraries_metadata(libs) == expected + assert anonymous_client.fetch_libraries_metadata(libs) == expected mock_get_urlpath_json.assert_has_calls( [mock.call("POST", "/v1/charm/libraries/bulk", json=libs)] From 203436c104dccb580e5d21ff773b0fa07d922f76 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 12 Jun 2024 10:53:18 -0400 Subject: [PATCH 3/7] style: autoformat --- charmcraft/utils/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charmcraft/utils/file.py b/charmcraft/utils/file.py index d4e20bba7..cb4d5be38 100644 --- a/charmcraft/utils/file.py +++ b/charmcraft/utils/file.py @@ -18,8 +18,8 @@ import os import pathlib import zipfile - from _stat import S_IRGRP, S_IROTH, S_IRUSR, S_IXGRP, S_IXOTH, S_IXUSR + from craft_cli import CraftError # handy masks for execution and reading for everybody From 9c6b843fa6c304c9771a9d81fa828995675be26a Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 12 Jun 2024 11:02:29 -0400 Subject: [PATCH 4/7] fix(test): add all python paths that don't error --- tests/integration/services/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/integration/services/conftest.py b/tests/integration/services/conftest.py index 0d004f134..8fc396d01 100644 --- a/tests/integration/services/conftest.py +++ b/tests/integration/services/conftest.py @@ -14,8 +14,10 @@ # # For further info, check https://github.com/canonical/charmcraft """Configuration for services integration tests.""" +import contextlib import sys +import pyfakefs.fake_filesystem import pytest from charmcraft import services @@ -23,13 +25,15 @@ @pytest.fixture() -def service_factory(fs, fake_path, simple_charm) -> services.CharmcraftServiceFactory: +def service_factory(fs: pyfakefs.fake_filesystem.FakeFilesystem, fake_path, simple_charm) -> services.CharmcraftServiceFactory: fake_project_dir = fake_path / "project" fake_project_dir.mkdir() # Allow access to the real venv library path. # This is necessary because certifi lazy-loads the certificate file. - fs.add_real_directory(sys.path[-1]) + for python_path in sys.path: + with contextlib.suppress(OSError): + fs.add_real_directory(python_path) factory = services.CharmcraftServiceFactory(app=APP_METADATA) From 488d8379ba08cbfb636c0777fa1f5094862d2bf9 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 12 Jun 2024 11:34:42 -0400 Subject: [PATCH 5/7] style: autoformat --- tests/integration/services/conftest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integration/services/conftest.py b/tests/integration/services/conftest.py index 8fc396d01..9b8c49eb7 100644 --- a/tests/integration/services/conftest.py +++ b/tests/integration/services/conftest.py @@ -25,7 +25,9 @@ @pytest.fixture() -def service_factory(fs: pyfakefs.fake_filesystem.FakeFilesystem, fake_path, simple_charm) -> services.CharmcraftServiceFactory: +def service_factory( + fs: pyfakefs.fake_filesystem.FakeFilesystem, fake_path, simple_charm +) -> services.CharmcraftServiceFactory: fake_project_dir = fake_path / "project" fake_project_dir.mkdir() From dc8e9d438810e5d688663914a24dae90044a6932 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 12 Jun 2024 11:41:33 -0400 Subject: [PATCH 6/7] fix(test): fix pyfakefs real_directory errors --- tests/integration/services/conftest.py | 2 ++ tests/integration/services/test_package.py | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/integration/services/conftest.py b/tests/integration/services/conftest.py index 9b8c49eb7..693d797f6 100644 --- a/tests/integration/services/conftest.py +++ b/tests/integration/services/conftest.py @@ -34,6 +34,8 @@ def service_factory( # Allow access to the real venv library path. # This is necessary because certifi lazy-loads the certificate file. for python_path in sys.path: + if not python_path: + continue with contextlib.suppress(OSError): fs.add_real_directory(python_path) diff --git a/tests/integration/services/test_package.py b/tests/integration/services/test_package.py index 40a7e5ae5..9e261e852 100644 --- a/tests/integration/services/test_package.py +++ b/tests/integration/services/test_package.py @@ -14,6 +14,7 @@ # # For further info, check https://github.com/canonical/charmcraft """Tests for package service.""" +import contextlib import datetime import pathlib @@ -50,7 +51,8 @@ def package_service(fake_path, service_factory, default_build_plan): @freezegun.freeze_time(datetime.datetime(2020, 3, 14, 0, 0, 0, tzinfo=datetime.timezone.utc)) def test_write_metadata(monkeypatch, fs, package_service, project_path): monkeypatch.setattr(charmcraft, "__version__", "3.0-test-version") - fs.add_real_directory(project_path) + with contextlib.suppress(FileExistsError): + fs.add_real_directory(project_path) test_prime_dir = pathlib.Path("/prime") fs.create_dir(test_prime_dir) expected_prime_dir = project_path / "prime" @@ -79,7 +81,8 @@ def test_overwrite_metadata(monkeypatch, fs, package_service, project_path): Regression test for https://github.com/canonical/charmcraft/issues/1654 """ monkeypatch.setattr(charmcraft, "__version__", "3.0-test-version") - fs.add_real_directory(project_path) + with contextlib.suppress(FileExistsError): + fs.add_real_directory(project_path) test_prime_dir = pathlib.Path("/prime") fs.create_dir(test_prime_dir) expected_prime_dir = project_path / "prime" @@ -104,7 +107,8 @@ def test_no_overwrite_reactive_metadata(monkeypatch, fs, package_service): """ monkeypatch.setattr(charmcraft, "__version__", "3.0-test-version") project_path = pathlib.Path(__file__).parent / "sample_projects" / "basic-reactive" - fs.add_real_directory(project_path) + with contextlib.suppress(FileExistsError): + fs.add_real_directory(project_path) test_prime_dir = pathlib.Path("/prime") fs.create_dir(test_prime_dir) test_stage_dir = pathlib.Path("/stage") From e1ec79188fbfbc6ac6c4ca9cc0f606ec6a0e14dd Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 12 Jun 2024 12:14:09 -0400 Subject: [PATCH 7/7] tests: fix tests --- tests/conftest.py | 13 ++++++++++++- tests/unit/commands/test_store.py | 14 +++++++------- tests/unit/services/test_store.py | 7 ++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 75cad67e4..a875ada97 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -64,9 +64,19 @@ def mock_store_client(): return client +@pytest.fixture() +def mock_store_anonymous_client() -> mock.Mock: + return mock.Mock(spec_set=store.AnonymousClient) + + @pytest.fixture() def service_factory( - fs, fake_project_dir, fake_prime_dir, simple_charm, mock_store_client + fs, + fake_project_dir, + fake_prime_dir, + simple_charm, + mock_store_client, + mock_store_anonymous_client, ) -> services.CharmcraftServiceFactory: factory = services.CharmcraftServiceFactory(app=APP_METADATA) @@ -78,6 +88,7 @@ def service_factory( factory.project = simple_charm factory.store.client = mock_store_client + factory.store.anonymous_client = mock_store_anonymous_client return factory diff --git a/tests/unit/commands/test_store.py b/tests/unit/commands/test_store.py index fe69d988a..774b3b376 100644 --- a/tests/unit/commands/test_store.py +++ b/tests/unit/commands/test_store.py @@ -164,7 +164,7 @@ def test_fetch_libs_no_charm_libs( Could not find the following libraries on charmhub: - lib: mysql.mysql version: '1' - - lib: some_charm.lib + - lib: some-charm.lib version: '1.2' """ ), @@ -173,7 +173,7 @@ def test_fetch_libs_no_charm_libs( ) def test_fetch_libs_missing_from_store(service_factory, libs, expected): service_factory.project.charm_libs = libs - service_factory.store.client.fetch_libraries_metadata.return_value = [] + service_factory.store.anonymous_client.fetch_libraries_metadata.return_value = [] fetch_libs = FetchLibs({"app": APP_METADATA, "services": service_factory}) with pytest.raises(errors.CraftError) as exc_info: @@ -213,8 +213,8 @@ def test_fetch_libs_missing_from_store(service_factory, libs, expected): ) def test_fetch_libs_no_content(new_path, service_factory, libs, store_libs, dl_lib, expected): service_factory.project.charm_libs = libs - service_factory.store.client.fetch_libraries_metadata.return_value = store_libs - service_factory.store.client.get_library.return_value = dl_lib + service_factory.store.anonymous_client.fetch_libraries_metadata.return_value = store_libs + service_factory.store.anonymous_client.get_library.return_value = dl_lib fetch_libs = FetchLibs({"app": APP_METADATA, "services": service_factory}) with pytest.raises(errors.CraftError, match=expected) as exc_info: @@ -254,10 +254,10 @@ def test_fetch_libs_no_content(new_path, service_factory, libs, store_libs, dl_l ) def test_fetch_libs_success( new_path, emitter, service_factory, libs, store_libs, dl_lib, expected -): +) -> None: service_factory.project.charm_libs = libs - service_factory.store.client.fetch_libraries_metadata.return_value = store_libs - service_factory.store.client.get_library.return_value = dl_lib + service_factory.store.anonymous_client.fetch_libraries_metadata.return_value = store_libs + service_factory.store.anonymous_client.get_library.return_value = dl_lib fetch_libs = FetchLibs({"app": APP_METADATA, "services": service_factory}) fetch_libs.run(argparse.Namespace()) diff --git a/tests/unit/services/test_store.py b/tests/unit/services/test_store.py index ec4730d07..540b6629d 100644 --- a/tests/unit/services/test_store.py +++ b/tests/unit/services/test_store.py @@ -35,6 +35,7 @@ def store(service_factory) -> services.StoreService: store = services.StoreService(app=application.APP_METADATA, services=service_factory) store.client = mock.Mock(spec_set=client.Client) + store.anonymous_client = mock.Mock(spec_set=client.AnonymousClient) return store @@ -232,11 +233,11 @@ def test_get_credentials(monkeypatch, store): ([], []), ( [CharmLib(lib="my_charm.my_lib", version="1")], - [{"charm-name": "my_charm", "library-name": "my_lib", "api": 1}], + [{"charm-name": "my-charm", "library-name": "my_lib", "api": 1}], ), ( [CharmLib(lib="my_charm.my_lib", version="1.0")], - [{"charm-name": "my_charm", "library-name": "my_lib", "api": 1, "patch": 0}], + [{"charm-name": "my-charm", "library-name": "my_lib", "api": 1, "patch": 0}], ), ], ) @@ -244,4 +245,4 @@ def test_fetch_libraries_metadata(monkeypatch, store, libs, expected_call): store.get_libraries_metadata(libs) - store.client.fetch_libraries_metadata.assert_called_once_with(expected_call) + store.anonymous_client.fetch_libraries_metadata.assert_called_once_with(expected_call)