diff --git a/test/mock_data.py b/test/mock_data.py index 4d65b3dc..6d605e93 100644 --- a/test/mock_data.py +++ b/test/mock_data.py @@ -443,9 +443,7 @@ CATALOGUE_ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY = { **CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, "name": "Catalogue Item With Mandatory Properties Only", - "properties": [ - PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE, - ], + "properties": [PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE], } CATALOGUE_ITEM_GET_DATA_WITH_MANDATORY_PROPERTIES_ONLY = { @@ -464,6 +462,8 @@ BASE_CATALOGUE_ITEM_DATA_WITH_PROPERTIES = CATALOGUE_ITEM_DATA_WITH_ALL_PROPERTIES +# Required values only + ITEM_DATA_REQUIRED_VALUES_ONLY = { "is_defective": False, "usage_status": "In Use", @@ -476,6 +476,33 @@ "usage_status_id": str(ObjectId()), } +# All values, no properties +ITEM_DATA_ALL_VALUES_NO_PROPERTIES = { + **ITEM_DATA_REQUIRED_VALUES_ONLY, + "purchase_order_number": "1234-123", + "warranty_end_date": "2015-11-15T23:59:59Z", + "asset_number": "1234-123456", + "serial_number": "1234-123456-123", + "delivered_date": "2012-12-05T12:00:00Z", + "notes": "Test notes", +} + +ITEM_IN_DATA_ALL_VALUES_NO_PROPERTIES = { + **ITEM_DATA_ALL_VALUES_NO_PROPERTIES, + "catalogue_item_id": str(ObjectId()), + "system_id": str(ObjectId()), + "usage_status_id": str(ObjectId()), + "usage_status": "In Use", +} + + +# Only mandatory properties + +ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY = { + **ITEM_DATA_REQUIRED_VALUES_ONLY, + "properties": [PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE], +} + # All properties ITEM_DATA_WITH_ALL_PROPERTIES = { @@ -487,16 +514,6 @@ ], } -# pylint:disable=fixme -# TODO: Replace in later item's PR when have a suitable name for one -ITEM_IN_DATA_A = { - "catalogue_item_id": str(ObjectId()), - "system_id": str(ObjectId()), - "is_defective": False, - "usage_status_id": str(ObjectId()), - "usage_status": "In Use", -} - # --------------------------------- MANUFACTURERS --------------------------------- # Required values only diff --git a/test/unit/repositories/test_item.py b/test/unit/repositories/test_item.py index 618336e3..addcc151 100644 --- a/test/unit/repositories/test_item.py +++ b/test/unit/repositories/test_item.py @@ -3,7 +3,7 @@ """ from test.mock_data import ( - ITEM_IN_DATA_A, + ITEM_IN_DATA_ALL_VALUES_NO_PROPERTIES, ITEM_IN_DATA_REQUIRED_VALUES_ONLY, PROPERTY_DATA_STRING_MANDATORY_TEXT, SYSTEM_IN_DATA_NO_PARENT_A, @@ -317,21 +317,21 @@ class TestList(ListDSL): def test_list(self): """Test listing all items.""" - self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_A]) + self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_ALL_VALUES_NO_PROPERTIES]) self.call_list(system_id=None, catalogue_item_id=None) self.check_list_success() def test_list_with_system_id_filter(self): """Test listing all items with a given `system_id`.""" - self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_A]) + self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_ALL_VALUES_NO_PROPERTIES]) self.call_list(system_id=str(ObjectId()), catalogue_item_id=None) self.check_list_success() def test_list_with_catalogue_category_id_filter(self): """Test listing all items with a given `catalogue_category_id`.""" - self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_A]) + self.mock_list([ITEM_IN_DATA_REQUIRED_VALUES_ONLY, ITEM_IN_DATA_ALL_VALUES_NO_PROPERTIES]) self.call_list(system_id=None, catalogue_item_id=str(ObjectId())) self.check_list_success() diff --git a/test/unit/services/test_catalogue_item.py b/test/unit/services/test_catalogue_item.py index 0ff07c4d..f26c94fe 100644 --- a/test/unit/services/test_catalogue_item.py +++ b/test/unit/services/test_catalogue_item.py @@ -637,7 +637,7 @@ def mock_update( ManufacturerOut( **{ **ManufacturerIn(**new_manufacturer_in_data).model_dump(), - "_id": new_manufacturer_in_data, + "_id": catalogue_item_update_data["manufacturer_id"], }, ) if new_manufacturer_in_data @@ -816,8 +816,8 @@ def test_update_all_fields_except_ids_or_properties_with_no_children(self): self.mock_update( catalogue_item_id, - catalogue_item_update_data=CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, - stored_catalogue_item_data=CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES, + catalogue_item_update_data=CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES, + stored_catalogue_item_data=CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, ) self.call_update(catalogue_item_id) self.check_update_success() @@ -830,8 +830,8 @@ def test_update_all_fields_except_ids_or_properties_with_children(self): self.mock_update( catalogue_item_id, - catalogue_item_update_data=CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, - stored_catalogue_item_data=CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES, + catalogue_item_update_data=CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES, + stored_catalogue_item_data=CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, has_child_elements=True, ) self.call_update(catalogue_item_id) diff --git a/test/unit/services/test_item.py b/test/unit/services/test_item.py index f54efd41..e9b32b03 100644 --- a/test/unit/services/test_item.py +++ b/test/unit/services/test_item.py @@ -11,8 +11,11 @@ CATALOGUE_CATEGORY_IN_DATA_LEAF_NO_PARENT_NO_PROPERTIES, CATALOGUE_ITEM_DATA_REQUIRED_VALUES_ONLY, CATALOGUE_ITEM_DATA_WITH_ALL_PROPERTIES, + ITEM_DATA_ALL_VALUES_NO_PROPERTIES, ITEM_DATA_REQUIRED_VALUES_ONLY, ITEM_DATA_WITH_ALL_PROPERTIES, + ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY, + SYSTEM_IN_DATA_NO_PARENT_A, USAGE_STATUS_IN_DATA_IN_USE, ) from test.unit.services.conftest import MODEL_MIXINS_FIXED_DATETIME_NOW, ServiceTestHelpers @@ -36,7 +39,7 @@ ) from inventory_management_system_api.models.catalogue_item import CatalogueItemIn, CatalogueItemOut, PropertyIn from inventory_management_system_api.models.item import ItemIn, ItemOut -from inventory_management_system_api.models.system import SystemOut +from inventory_management_system_api.models.system import SystemIn, SystemOut from inventory_management_system_api.models.usage_status import UsageStatusIn, UsageStatusOut from inventory_management_system_api.schemas.catalogue_item import PropertyPostSchema from inventory_management_system_api.schemas.item import ItemPatchSchema, ItemPostSchema @@ -301,6 +304,8 @@ def check_create_success(self) -> None: # This is the get for the usage status self.mock_usage_status_repository.get.assert_called_once_with(self._item_post.usage_status_id) + # TODO: Should _catalogue_category_out be _stored_catalogue_category_out? + self.wrapped_utils.process_properties.assert_called_once_with( self._catalogue_category_out.properties, self._expected_merged_properties ) @@ -487,7 +492,420 @@ def test_list(self): self.check_list_success() -# TODO: Update tests +# pylint:disable=too-many-instance-attributes +class UpdateDSL(ItemServiceDSL): + """Base class for `update` tests.""" + + # TODO: Check if all of these are actually used + _stored_item: Optional[ItemOut] + _stored_catalogue_item_in: Optional[CatalogueItemIn] + _stored_catalogue_item_out: Optional[CatalogueItemOut] + _stored_catalogue_category_out: Optional[CatalogueCategoryOut] + _item_patch: ItemPatchSchema + _expected_item_in: ItemIn + _expected_item_out: MagicMock + _updated_item_id: str + _updated_item: MagicMock + _update_exception: pytest.ExceptionInfo + + _updating_system: bool + _updating_usage_status: bool + _updating_properties: bool + _expected_merged_properties: List[PropertyPostSchema] + + # pylint:disable=too-many-arguments + def mock_update( + self, + item_id: str, + item_update_data: dict, + stored_item_data: Optional[dict], + stored_catalogue_item_data: Optional[dict] = None, + stored_catalogue_category_in_data: Optional[dict] = None, + new_system_in_data: Optional[dict] = None, + new_usage_status_in_data: Optional[dict] = None, + ) -> None: + """ + Mocks repository methods appropriately to test the `update` service method. + + :param item_id: ID of the item that will be obtained. + :param item_update_data: Dictionary containing the basic patch data as would be required for a `ItemPatchSchema` + but without any mandatory IDs or property IDs. + :param stored_item_data: Either `None` or a dictionary containing the catalogue basic catalogue item data for + the existing stored catalogue item as would be required for a `ItemPostSchema` but + without any mandatory IDs or property IDs. + :param stored_catalogue_item_data: Either `None` or a dictionary containing the catalogue basic catalogue item + data for the existing stored catalogue item as would be required for a + `CatalogueItemPostSchema` but without any mandatory IDs or property IDs. + :param stored_catalogue_category_in_data: Either `None` or a dictionary containing the catalogue category data + for the existing stored catalogue category as would be required for a + `CatalogueCategoryIn` database model. + :param new_system_in_data: Either `None` or a dictionary containing the system data for the new stored system as + would be required for a `SystemIn` database model. + :param new_usage_status_in_data: Either `None` or a dictionary containing the usage status data for the new + stored usage status as would be required for a `UsageStatus` database model. + """ + + # Add property ids to the stored catalogue item and item if needed + + catalogue_category_id = str(ObjectId()) + # TODO: Could simplify - copied from catalogue items - including the stored part below + stored_catalogue_category_in = ( + CatalogueCategoryIn(**stored_catalogue_category_in_data) if stored_catalogue_category_in_data else None + ) + + expected_stored_catalogue_item_properties_in = [] + if ( + stored_catalogue_category_in + and "properties" in stored_catalogue_item_data + and stored_catalogue_item_data["properties"] + ): + expected_stored_catalogue_item_properties_in, _ = self.construct_properties_in_and_post_with_ids( + stored_catalogue_category_in.properties, stored_catalogue_item_data["properties"] + ) + + # Stored catalogue item + self._stored_catalogue_item_out = ( + CatalogueItemOut( + **CatalogueItemIn( + **{ + **stored_catalogue_item_data, + "catalogue_category_id": catalogue_category_id, + "manufacturer_id": str(ObjectId()), + "properties": expected_stored_catalogue_item_properties_in, + }, + ).model_dump(), + id=str(ObjectId()), + ) + if stored_catalogue_item_data + else None + ) + + expected_stored_item_properties_in = [] + if stored_item_data and "properties" in stored_item_data and stored_item_data["properties"]: + expected_stored_item_properties_in, _ = self.construct_properties_in_and_post_with_ids( + stored_catalogue_category_in.properties, stored_item_data["properties"] + ) + + # Generate mandatory IDs to be inserted where needed + stored_ids_to_insert = { + "catalogue_item_id": catalogue_category_id, + "system_id": str(ObjectId()), + "usage_status_id": str(ObjectId()), + } + + # Stored item + self._stored_item = ( + ItemOut( + **ItemIn( + **{ + **stored_item_data, + **stored_ids_to_insert, + "properties": expected_stored_item_properties_in, + }, + ).model_dump(), + id=item_id, + ) + if stored_item_data + else None + ) + ServiceTestHelpers.mock_get(self.mock_item_repository, self._stored_item) + + # Stored system + self._updating_system = "system_id" in item_update_data + + if self._updating_system: + ServiceTestHelpers.mock_get( + self.mock_system_repository, + ( + SystemOut( + **{ + **SystemIn(**new_system_in_data).model_dump(), + "_id": item_update_data["system_id"], + }, + ) + if new_system_in_data + else None + ), + ) + + # Stored usage status + self._updating_usage_status = "usage_status_id" in item_update_data + + if self._updating_usage_status: + ServiceTestHelpers.mock_get( + self.mock_usage_status_repository, + ( + UsageStatusOut( + **{ + **UsageStatusIn(**new_usage_status_in_data).model_dump(), + "_id": item_update_data["usage_status_id"], + }, + ) + if new_usage_status_in_data + else None + ), + ) + + # Item + + self._updating_properties = "properties" in item_update_data + + # When properties are given need to add any property `id`s and ensure the expected data inserts them as well + property_post_schemas = [] + expected_properties_in = [] + if self._updating_properties: + # Catalogue item + ServiceTestHelpers.mock_get(self.mock_catalogue_item_repository, self._stored_catalogue_item_out) + + # Catalogue category + self._stored_catalogue_category_out = ( + CatalogueCategoryOut( + **stored_catalogue_category_in.model_dump(by_alias=True), + id=self._stored_catalogue_item_out.catalogue_category_id, + ) + if stored_catalogue_category_in_data + else None + ) + ServiceTestHelpers.mock_get(self.mock_catalogue_category_repository, self._stored_catalogue_category_out) + + if self._updating_properties and item_update_data["properties"]: + _, property_post_schemas = self.construct_properties_in_and_post_with_ids( + stored_catalogue_category_in.properties, item_update_data["properties"] + ) + + # Any missing properties should be inherited from the catalogue item + # TODO: Can use this in the one above in create instead of the if? + supplied_properties = property_post_schemas + + supplied_properties_dict = { + supplied_property.id: supplied_property for supplied_property in supplied_properties + } + self._expected_merged_properties = [] + + if self._stored_catalogue_item_out and self._stored_catalogue_category_out: + for prop in self._stored_catalogue_item_out.properties: + supplied_property = supplied_properties_dict.get(prop.id) + self._expected_merged_properties.append( + supplied_property if supplied_property else PropertyPostSchema(**prop.model_dump()) + ) + + expected_properties_in = utils.process_properties( + self._stored_catalogue_category_out.properties, self._expected_merged_properties + ) + + item_update_data["properties"] = property_post_schemas + + # Updated item + self._expected_item_out = MagicMock() + ServiceTestHelpers.mock_update(self.mock_item_repository, self._expected_item_out) + + # Patch schema + self._item_patch = ItemPatchSchema(**item_update_data) + + # Construct the expected input for the repository + merged_item_data = { + **(stored_item_data or {}), + **stored_ids_to_insert, + **item_update_data, + } + self._expected_item_in = ItemIn(**{**merged_item_data, "properties": expected_properties_in}) + + def call_update(self, item_id: str) -> None: + """ + Calls the `ItemService` `update` method with the appropriate data from a prior call to `mock_update`. + + :param item_id: ID of the item to be updated. + """ + + self._updated_item_id = item_id + self._updated_item = self.item_service.update(item_id, self._item_patch) + + def call_update_expecting_error(self, item_id: str, error_type: type[BaseException]) -> None: + """ + Calls the `ItemService` `update` method with the appropriate data from a prior call to + `mock_update` while expecting an error to be raised. + + :param item_id: ID of the item to be updated. + :param error_type: Expected exception to be raised. + """ + + with pytest.raises(error_type) as exc: + self.item_service.update(item_id, self._item_patch) + self._update_exception = exc + + def check_update_success(self) -> None: + """Checks that a prior call to `call_update` worked as expected.""" + + # TODO: Implement + + self.mock_item_repository.get.assert_called_once_with(self._updated_item_id) + + if self._updating_system: + self.mock_system_repository.get.assert_called_once_with(self._item_patch.system_id) + + if self._updating_usage_status: + self.mock_usage_status_repository.get.assert_called_once_with(self._item_patch.usage_status_id) + + if self._updating_properties: + self.mock_catalogue_item_repository.get.assert_called_once_with(self._stored_item.catalogue_item_id) + + self.mock_catalogue_category_repository.get.assert_called_once_with( + self._stored_catalogue_item_out.catalogue_category_id + ) + + self.wrapped_utils.process_properties.assert_called_once_with( + self._stored_catalogue_category_out.properties, self._expected_merged_properties + ) + else: + self.mock_catalogue_category_repository.get.assert_not_called() + self.wrapped_utils.process_properties.assert_not_called() + + def check_update_failed_with_exception(self, message: str) -> None: + """ + Checks that a prior call to `call_update_expecting_error` worked as expected, raising an exception + with the correct message. + + :param message: Expected message of the raised exception. + """ + + self.mock_item_repository.update.assert_not_called() + + assert str(self._update_exception.value) == message + + +class TestUpdate(UpdateDSL): + """Tests for updating a catalogue item.""" + + def test_update_all_fields_except_ids_or_properties(self): + """Test updating all fields of an item except its any of its `_id` fields or properties.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data=ITEM_DATA_ALL_VALUES_NO_PROPERTIES, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + ) + self.call_update(item_id) + self.check_update_success() + + def test_update_properties_with_all(self): + """Test updating an item's `properties` while populating all available properties.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data=ITEM_DATA_ALL_VALUES_NO_PROPERTIES, + # Strictly speaking we wouldn't allow this in the first place - the stored data is missing a mandatory + # property but it is irrelevant for this test and saves creating a new version + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + stored_catalogue_item_data=BASE_CATALOGUE_ITEM_DATA_WITH_PROPERTIES, + stored_catalogue_category_in_data=BASE_CATALOGUE_CATEGORY_IN_DATA_WITH_PROPERTIES_MM, + ) + self.call_update(item_id) + self.check_update_success() + + def test_update_properties_with_mandatory_only(self): + """Test updating an item's `properties` while only populating the mandatory properties.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data=ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY, + # Strictly speaking we wouldn't allow this in the first place - the stored data is missing a mandatory + # property but it is irrelevant for this test and saves creating a new version + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + stored_catalogue_item_data=BASE_CATALOGUE_ITEM_DATA_WITH_PROPERTIES, + stored_catalogue_category_in_data=BASE_CATALOGUE_CATEGORY_IN_DATA_WITH_PROPERTIES_MM, + ) + self.call_update(item_id) + self.check_update_success() + + def test_update_catalogue_item_id(self): + """Test updating an item's `catalogue_item_id`.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data={"catalogue_item_id": str(ObjectId())}, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + ) + self.call_update_expecting_error(item_id, InvalidActionError) + self.check_update_failed_with_exception("Cannot change the catalogue item the item belongs to") + + def test_update_system_id(self): + """Test updating an item's `system_id`.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data={"system_id": str(ObjectId())}, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + new_system_in_data=SYSTEM_IN_DATA_NO_PARENT_A, + ) + self.call_update(item_id) + self.check_update_success() + + def test_update_with_non_existent_system_id(self): + """Test updating an item's `system_id` to a non-existent system.""" + + item_id = str(ObjectId()) + system_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data={"system_id": system_id}, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + new_system_in_data=None, + ) + self.call_update_expecting_error(item_id, MissingRecordError) + self.check_update_failed_with_exception(f"No system found with ID: {system_id}") + + def test_update_usage_status_id(self): + """Test updating an item's `usage_status_id`.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data={"usage_status_id": str(ObjectId())}, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + new_usage_status_in_data=USAGE_STATUS_IN_DATA_IN_USE, + ) + self.call_update(item_id) + self.check_update_success() + + def test_update_with_non_existent_usage_status_id(self): + """Test updating an item's `usage_status_id` to a non-existent usage status.""" + + item_id = str(ObjectId()) + usage_status_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data={"usage_status_id": usage_status_id}, + stored_item_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + new_usage_status_in_data=None, + ) + self.call_update_expecting_error(item_id, MissingRecordError) + self.check_update_failed_with_exception(f"No usage status found with ID: {usage_status_id}") + + def test_update_with_non_existent_id(self): + """Test updating an item with a non-existent ID.""" + + item_id = str(ObjectId()) + + self.mock_update( + item_id, + item_update_data=ITEM_DATA_REQUIRED_VALUES_ONLY, + stored_item_data=None, + ) + self.call_update_expecting_error(item_id, MissingRecordError) + self.check_update_failed_with_exception(f"No item found with ID: {item_id}") class DeleteDSL(ItemServiceDSL): @@ -519,854 +937,3 @@ def test_delete(self): self.call_delete(str(ObjectId())) self.check_delete_success() - - -# # pylint: disable=duplicate-code -# FULL_CATALOGUE_CATEGORY_A_INFO = { -# "name": "Category A", -# "code": "category-a", -# "is_leaf": True, -# "parent_id": None, -# "properties": [ -# {"name": "Property A", "type": "number", "unit": "mm", "mandatory": False}, -# {"name": "Property B", "type": "boolean", "unit": None, "mandatory": True}, -# {"name": "Property C", "type": "string", "unit": "cm", "mandatory": True}, -# {"name": "Property D", "type": "string", "unit": None, "mandatory": False}, -# ], -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } - -# FULL_CATALOGUE_ITEM_A_INFO = { -# "name": "Catalogue Item A", -# "description": "This is Catalogue Item A", -# "cost_gbp": 129.99, -# "cost_to_rework_gbp": None, -# "days_to_replace": 2.0, -# "days_to_rework": None, -# "drawing_link": "https://drawing-link.com/", -# "drawing_number": None, -# "item_model_number": "abc123", -# "is_obsolete": False, -# "obsolete_reason": None, -# "obsolete_replacement_catalogue_item_id": None, -# "notes": None, -# "properties": [ -# {"name": "Property A", "value": 20, "unit": "mm"}, -# {"name": "Property B", "value": False, "unit": None}, -# {"name": "Property C", "value": "20x15x10", "unit": "cm"}, -# ], -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } - -# ITEM_INFO = { -# "is_defective": False, -# "warranty_end_date": datetime(2015, 11, 15, 23, 59, 59, 0, tzinfo=timezone.utc), -# "serial_number": "xyz123", -# "delivered_date": datetime(2012, 12, 5, 12, 0, 0, 0, tzinfo=timezone.utc), -# "notes": "Test notes", -# "properties": [{"name": "Property A", "value": 21}], -# } - -# FULL_ITEM_INFO = { -# **ITEM_INFO, -# "purchase_order_number": None, -# "asset_number": None, -# "properties": [ -# {"name": "Property A", "value": 21, "unit": "mm"}, -# {"name": "Property B", "value": False, "unit": None}, -# {"name": "Property C", "value": "20x15x10", "unit": "cm"}, -# {"name": "Property D", "value": None, "unit": None}, -# ], -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } - -# FULL_SYSTEM_INFO = { -# "name": "Test name a", -# "code": "test-name-a", -# "location": "Test location", -# "owner": "Test owner", -# "importance": "low", -# "description": "Test description", -# "parent_id": None, -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } -# USAGE_STATUS_A = { -# "value": "New", -# "code": "new", -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } -# USAGE_STATUS_B = { -# "value": "Used", -# "code": "used", -# "created_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# "modified_time": MODEL_MIXINS_FIXED_DATETIME_NOW, -# } - - -# # pylint: enable=duplicate-code - - -# def test_update( -# test_helpers, -# item_repository_mock, -# usage_status_repository_mock, -# model_mixins_datetime_now_mock, # pylint: disable=unused-argument -# item_service, -# ): -# """ -# Test updating an item, - -# Verify that the `update` method properly handles the item to be updated. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="Used", -# **{ -# **FULL_ITEM_INFO, -# "created_time": FULL_ITEM_INFO["created_time"] - timedelta(days=5), -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "is_defective": True, -# "usage_status": item.usage_status, -# "usage_status_id": item.usage_status_id, -# "modified_time": item.created_time, -# "properties": item_properties, -# } -# ), -# ) -# # Mock `update` to return the updated item -# test_helpers.mock_update(item_repository_mock, item) -# # Mock `get` to return the usage status -# test_helpers.mock_get( -# usage_status_repository_mock, -# UsageStatusOut(id=item.usage_status_id, **USAGE_STATUS_B), -# ) -# updated_item = item_service.update( -# item.id, -# ItemPatchSchema(is_defective=item.is_defective, usage_status_id=item.usage_status_id), -# ) - -# item_repository_mock.update.assert_called_once_with( -# item.id, -# ItemIn( -# catalogue_item_id=item.catalogue_item_id, -# system_id=item.system_id, -# usage_status_id=item.usage_status_id, -# usage_status=item.usage_status, -# **{ -# **FULL_ITEM_INFO, -# "created_time": item.created_time, -# "properties": item_properties, -# }, -# ), -# ) -# assert updated_item == item - - -# def test_update_with_non_existent_id(test_helpers, item_repository_mock, item_service): -# """ -# Test updating an item with a non-existent ID. - -# Verify that the `update` method properly handles the item to be updated with a non-existent ID. -# """ -# # Mock `get` to return an item -# test_helpers.mock_get(item_repository_mock, None) - -# item_id = str(ObjectId()) -# with pytest.raises(MissingRecordError) as exc: -# item_service.update(item_id, ItemPatchSchema(properties=[])) -# item_repository_mock.update.assert_not_called() -# assert str(exc.value) == f"No item found with ID: {item_id}" - - -# def test_update_change_catalogue_item_id(test_helpers, item_repository_mock, item_service): -# """ -# Test updating an item with a catalogue item ID. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "catalogue_item_id": str(ObjectId()), -# "properties": item_properties, -# } -# ), -# ) - -# catalogue_item_id = str(ObjectId()) -# with pytest.raises(InvalidActionError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(catalogue_item_id=catalogue_item_id), -# ) -# item_repository_mock.update.assert_not_called() -# assert str(exc.value) == "Cannot change the catalogue item the item belongs to" - - -# def test_update_change_system_id( -# test_helpers, -# item_repository_mock, -# system_repository_mock, -# usage_status_repository_mock, -# model_mixins_datetime_now_mock, # pylint: disable=unused-argument -# item_service, -# # pylint: disable=too-many-arguments -# ): -# """ -# Test updating system id to an existing id -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "created_time": FULL_ITEM_INFO["created_time"] - timedelta(days=5), -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "system_id": str(ObjectId()), -# "modified_time": item.created_time, -# "properties": item_properties, -# } -# ), -# ) - -# # Mock `get` to return a system -# test_helpers.mock_get( -# system_repository_mock, -# SystemOut( -# id=item.system_id, -# **FULL_SYSTEM_INFO, -# ), -# ) - -# # Mock `get` to return the usage status -# test_helpers.mock_get( -# usage_status_repository_mock, -# UsageStatusOut(id=item.usage_status_id, **USAGE_STATUS_A), -# ) - -# # Mock `update` to return the updated item -# test_helpers.mock_update(item_repository_mock, item) - -# updated_item = item_service.update( -# item.id, -# ItemPatchSchema(system_id=item.system_id), -# ) - -# item_repository_mock.update.assert_called_once_with( -# item.id, -# ItemIn( -# catalogue_item_id=item.catalogue_item_id, -# system_id=item.system_id, -# usage_status=item.usage_status, -# usage_status_id=item.usage_status_id, -# **{ -# **FULL_ITEM_INFO, -# "created_time": item.created_time, -# "properties": item_properties, -# }, -# ), -# ) -# assert updated_item == item - - -# def test_update_with_non_existent_system_id(test_helpers, system_repository_mock, item_repository_mock, item_service): -# """ -# Test updating an item with a non-existent system ID. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "system_id": str(ObjectId()), -# "properties": item_properties, -# } -# ), -# ) - -# # Mock `get` to not return a catalogue item -# test_helpers.mock_get(system_repository_mock, None) - -# system_id = str(ObjectId()) -# with pytest.raises(MissingRecordError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(system_id=system_id), -# ) -# item_repository_mock.update.assert_not_called() -# assert str(exc.value) == f"No system found with ID: {system_id}" - - -# def test_update_with_non_existent_usage_status( -# test_helpers, usage_status_repository_mock, item_repository_mock, item_service -# ): -# """ -# Test updating an item with a non-existent usage status ID. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "usage_status_id": str(ObjectId()), -# "properties": item_properties, -# } -# ), -# ) - -# # Mock `get` to not return a catalogue item -# test_helpers.mock_get(usage_status_repository_mock, None) - -# usage_status_id = str(ObjectId()) -# with pytest.raises(MissingRecordError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(usage_status_id=usage_status_id), -# ) -# item_repository_mock.update.assert_not_called() -# assert str(exc.value) == f"No usage status found with ID: {usage_status_id}" - - -# def test_update_with_invalid_usage_status( -# test_helpers, usage_status_repository_mock, item_repository_mock, item_service -# ): -# """ -# Test updating an item with a invalid usage status ID. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "usage_status_id": str(ObjectId()), -# "properties": item_properties, -# } -# ), -# ) - -# # Mock `get` to not return a catalogue item -# test_helpers.mock_get(usage_status_repository_mock, None) - -# usage_status_id = "invalid" -# with pytest.raises(MissingRecordError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(usage_status_id=usage_status_id), -# ) -# item_repository_mock.update.assert_not_called() -# assert str(exc.value) == f"No usage status found with ID: {usage_status_id}" - - -# def test_update_change_property_value( -# test_helpers, -# catalogue_item_repository_mock, -# catalogue_category_repository_mock, -# item_repository_mock, -# model_mixins_datetime_now_mock, # pylint: disable=unused-argument -# item_service, -# ): # pylint: disable=too-many-arguments -# """ -# Test updating a value of a property -# """ -# item_properties = add_ids_to_properties( -# None, -# [ -# {"name": "Property A", "value": 1, "unit": "mm"}, -# *FULL_ITEM_INFO["properties"][-3:], -# ], -# ) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "created_time": FULL_ITEM_INFO["created_time"] - timedelta(days=5), -# "properties": item_properties, -# }, -# ) - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "properties": item_properties, -# "modified_time": item.created_time, -# } -# ), -# ) - -# catalogue_category_id = str(ObjectId()) -# manufacturer_id = str(ObjectId()) - -# # Mock `get` to return a catalogue item -# test_helpers.mock_get( -# catalogue_item_repository_mock, -# CatalogueItemOut( -# id=item.catalogue_item_id, -# catalogue_category_id=catalogue_category_id, -# manufacturer_id=manufacturer_id, -# **{ -# **FULL_CATALOGUE_ITEM_A_INFO, -# "properties": add_ids_to_properties(item_properties, FULL_CATALOGUE_ITEM_A_INFO["properties"]), -# }, -# ), -# ) -# # Mock `get` to return a catalogue category -# # pylint: disable=duplicate-code -# test_helpers.mock_get( -# catalogue_category_repository_mock, -# CatalogueCategoryOut( -# id=catalogue_category_id, -# **{ -# **FULL_CATALOGUE_CATEGORY_A_INFO, -# "properties": add_ids_to_properties( -# item_properties, -# FULL_CATALOGUE_CATEGORY_A_INFO["properties"], -# ), -# }, -# ), -# ) -# # pylint: enable=duplicate-code - -# # Mock `update` to return the updated item -# test_helpers.mock_update(item_repository_mock, item) - -# updated_item = item_service.update( -# item.id, -# ItemPatchSchema(properties=[{"id": prop.id, "value": prop.value} for prop in item.properties]), -# ) - -# item_repository_mock.update.assert_called_once_with( -# item.id, -# ItemIn( -# catalogue_item_id=item.catalogue_item_id, -# system_id=item.system_id, -# usage_status_id=item.usage_status_id, -# usage_status=item.usage_status, -# **{ -# **FULL_ITEM_INFO, -# "created_time": item.created_time, -# "properties": item_properties, -# }, -# ), -# ) -# assert updated_item == item - - -# def test_update_with_missing_existing_properties( -# test_helpers, -# catalogue_item_repository_mock, -# catalogue_category_repository_mock, -# item_repository_mock, -# model_mixins_datetime_now_mock, # pylint: disable=unused-argument -# item_service, -# ): # pylint: disable=too-many-arguments -# """ -# Test updating properties with missing properties -# """ -# item_properties = add_ids_to_properties( -# None, -# [ -# FULL_CATALOGUE_ITEM_A_INFO["properties"][0], -# *FULL_ITEM_INFO["properties"][-3:], -# ], -# ) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "created_time": FULL_ITEM_INFO["created_time"] - timedelta(days=5), -# "properties": item_properties, -# }, -# ) - -# # Mock `get` to return an item -# test_helpers.mock_get( -# item_repository_mock, -# ItemOut( -# **{ -# **item.model_dump(), -# "properties": add_ids_to_properties(None, FULL_ITEM_INFO["properties"]), -# "modified_time": item.created_time, -# } -# ), -# ) - -# catalogue_category_id = str(ObjectId()) -# manufacturer_id = str(ObjectId()) - -# # Mock `get` to return a catalogue item -# test_helpers.mock_get( -# catalogue_item_repository_mock, -# CatalogueItemOut( -# id=item.catalogue_item_id, -# catalogue_category_id=catalogue_category_id, -# manufacturer_id=manufacturer_id, -# **{ -# **FULL_CATALOGUE_ITEM_A_INFO, -# "properties": add_ids_to_properties(item_properties, FULL_CATALOGUE_ITEM_A_INFO["properties"]), -# }, -# ), -# ) -# # Mock `get` to return a catalogue category -# # pylint: disable=duplicate-code -# test_helpers.mock_get( -# catalogue_category_repository_mock, -# CatalogueCategoryOut( -# id=catalogue_category_id, -# **{ -# **FULL_CATALOGUE_CATEGORY_A_INFO, -# "properties": add_ids_to_properties( -# item_properties, -# FULL_CATALOGUE_CATEGORY_A_INFO["properties"], -# ), -# }, -# ), -# ) -# # pylint: enable=duplicate-code - -# # Mock `update` to return the updated item -# test_helpers.mock_update(item_repository_mock, item) - -# updated_item = item_service.update( -# item.id, -# ItemPatchSchema(properties=[{"id": prop.id, "value": prop.value} for prop in item.properties[-2:]]), -# ) - -# item_repository_mock.update.assert_called_once_with( -# item.id, -# ItemIn( -# catalogue_item_id=item.catalogue_item_id, -# system_id=item.system_id, -# usage_status_id=item.usage_status_id, -# usage_status=item.usage_status, -# **{ -# **FULL_ITEM_INFO, -# "created_time": item.created_time, -# "properties": item_properties, -# }, -# ), -# ) -# assert updated_item == item - - -# def test_update_change_value_for_string_property_invalid_type( -# test_helpers, -# catalogue_category_repository_mock, -# catalogue_item_repository_mock, -# item_repository_mock, -# item_service, -# ): -# """ -# Test changing the value of a string property to an invalid type. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get(item_repository_mock, item) - -# catalogue_category_id = str(ObjectId()) -# manufacturer_id = str(ObjectId()) - -# # Mock `get` to return a catalogue item -# test_helpers.mock_get( -# catalogue_item_repository_mock, -# CatalogueItemOut( -# id=item.catalogue_item_id, -# catalogue_category_id=catalogue_category_id, -# manufacturer_id=manufacturer_id, -# **{ -# **FULL_CATALOGUE_ITEM_A_INFO, -# "properties": add_ids_to_properties(item_properties, FULL_CATALOGUE_ITEM_A_INFO["properties"]), -# }, -# ), -# ) -# # Mock `get` to return a catalogue category -# test_helpers.mock_get( -# catalogue_category_repository_mock, -# CatalogueCategoryOut( -# id=catalogue_category_id, -# **{ -# **FULL_CATALOGUE_CATEGORY_A_INFO, -# "properties": add_ids_to_properties( -# item_properties, -# FULL_CATALOGUE_CATEGORY_A_INFO["properties"], -# ), -# }, -# ), -# ) - -# properties = [{"id": prop.id, "value": prop.value} for prop in item.properties] -# properties[2]["value"] = True -# with pytest.raises(InvalidPropertyTypeError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(properties=properties), -# ) -# item_repository_mock.update.assert_not_called() -# assert ( -# str(exc.value) == f"Invalid value type for property with ID '{item.properties[2].id}'. Expected type: string." -# ) - - -# def test_update_change_value_for_number_property_invalid_type( -# test_helpers, -# catalogue_category_repository_mock, -# catalogue_item_repository_mock, -# item_repository_mock, -# item_service, -# ): -# """ -# Test changing the value of a number property to an invalid type. -# """ -# # pylint: disable=duplicate-code -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) -# # pylint: enable=duplicate-code - -# # Mock `get` to return an item -# test_helpers.mock_get(item_repository_mock, item) - -# catalogue_category_id = str(ObjectId()) -# manufacturer_id = str(ObjectId()) - -# # Mock `get` to return a catalogue item -# test_helpers.mock_get( -# catalogue_item_repository_mock, -# CatalogueItemOut( -# id=item.catalogue_item_id, -# catalogue_category_id=catalogue_category_id, -# manufacturer_id=manufacturer_id, -# **{ -# **FULL_CATALOGUE_ITEM_A_INFO, -# "properties": add_ids_to_properties(item_properties, FULL_CATALOGUE_ITEM_A_INFO["properties"]), -# }, -# ), -# ) -# # Mock `get` to return a catalogue category -# test_helpers.mock_get( -# catalogue_category_repository_mock, -# CatalogueCategoryOut( -# id=catalogue_category_id, -# **{ -# **FULL_CATALOGUE_CATEGORY_A_INFO, -# "properties": add_ids_to_properties( -# item_properties, -# FULL_CATALOGUE_CATEGORY_A_INFO["properties"], -# ), -# }, -# ), -# ) - -# properties = [{"id": prop.id, "value": prop.value} for prop in item.properties] -# properties[0]["value"] = "20" -# with pytest.raises(InvalidPropertyTypeError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(properties=properties), -# ) -# item_repository_mock.update.assert_not_called() -# assert ( -# str(exc.value) == f"Invalid value type for property with ID '{item.properties[0].id}'. Expected type: number." -# ) - - -# def test_update_change_value_for_boolean_property_invalid_type( -# test_helpers, -# catalogue_category_repository_mock, -# catalogue_item_repository_mock, -# item_repository_mock, -# item_service, -# ): -# """ -# Test changing the value of a boolean property to an invalid type. -# """ -# item_properties = add_ids_to_properties(None, FULL_ITEM_INFO["properties"]) -# item = ItemOut( -# id=str(ObjectId()), -# catalogue_item_id=str(ObjectId()), -# system_id=str(ObjectId()), -# usage_status_id=str(ObjectId()), -# usage_status="New", -# **{ -# **FULL_ITEM_INFO, -# "properties": item_properties, -# }, -# ) - -# # Mock `get` to return an item -# test_helpers.mock_get(item_repository_mock, item) - -# catalogue_category_id = str(ObjectId()) -# manufacturer_id = str(ObjectId()) - -# # Mock `get` to return a catalogue item -# test_helpers.mock_get( -# catalogue_item_repository_mock, -# CatalogueItemOut( -# id=item.catalogue_item_id, -# catalogue_category_id=catalogue_category_id, -# manufacturer_id=manufacturer_id, -# **{ -# **FULL_CATALOGUE_ITEM_A_INFO, -# "properties": add_ids_to_properties(item_properties, FULL_CATALOGUE_ITEM_A_INFO["properties"]), -# }, -# ), -# ) -# # Mock `get` to return a catalogue category -# test_helpers.mock_get( -# catalogue_category_repository_mock, -# CatalogueCategoryOut( -# id=catalogue_category_id, -# **{ -# **FULL_CATALOGUE_CATEGORY_A_INFO, -# "properties": add_ids_to_properties( -# item_properties, -# FULL_CATALOGUE_CATEGORY_A_INFO["properties"], -# ), -# }, -# ), -# ) - -# properties = [{"id": prop.id, "value": prop.value} for prop in item.properties] -# properties[1]["value"] = "False" -# with pytest.raises(InvalidPropertyTypeError) as exc: -# item_service.update( -# item.id, -# ItemPatchSchema(properties=properties), -# ) -# item_repository_mock.update.assert_not_called() -# assert ( -# str(exc.value) == f"Invalid value type for property with ID '{item.properties[1].id}'. Expected type: boolean." -# )