diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index 39b74004..00000000 --- a/test/conftest.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Module for providing pytest testing configuration. -""" - -from typing import Optional - -from bson import ObjectId - - -def add_ids_to_properties(properties_with_ids: Optional[list], properties_without_ids: list): - """ - A tests method for adding the IDs from the properties in `properties_with_ids` as the IDs to the properties in - `properties_without_ids` based on matching names. Unique IDs are generated for each property if no - `properties_with_ids` are provided. Additionally, unique IDs are generated for each unit if the unit value - is not None. - - :param properties_with_ids: The list of properties with IDs. These are typically the catalogue category properties. - :param properties_without_ids: The list of properties without IDs. These can be catalogue category, catalogue item - or item properties. - :return: The list of properties with the added IDs. - """ - properties = [] - for property_without_id in properties_without_ids: - prop_id = None - unit_id = None - - if properties_with_ids: - # Match up property and unit IDs - for property_with_id in properties_with_ids: - if property_with_id["name"] == property_without_id["name"]: - prop_id = property_with_id["id"] - - if property_with_id.get("unit") == property_without_id.get("unit"): - unit_id = property_with_id["unit_id"] - else: - # Generate a new property id and lookup the unit id from the units list - prop_id = str(ObjectId()) - - if property_without_id.get("unit") is not None: - if property_without_id.get("unit_id") is None: - unit_id = str(ObjectId()) - else: - unit_id = property_without_id["unit_id"] - - properties.append({**property_without_id, "id": prop_id, "unit_id": unit_id}) - - return properties diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py index 0f162998..338d1aaf 100644 --- a/test/e2e/conftest.py +++ b/test/e2e/conftest.py @@ -90,22 +90,36 @@ class E2ETestHelpers: """ @staticmethod - def check_created_and_modified_times_updated_correctly(post_response: Response, patch_response: Response): - """Checks that an updated entity has a created_time that is the same as its original, but an updated_time + def check_created_and_modified_times_updated_correctly(post_response: Response, new_response: Response): + """Checks that an updated entity has a `created_time` that is the same as its original, but an `updated_time` that is newer :param post_response: Original response for the entity post request - :param patch_response: Updated response for the entity patch request + :param new_response: Updated response for the entity patch/get request """ original_data = post_response.json() - updated_data = patch_response.json() + updated_data = new_response.json() assert original_data["created_time"] == updated_data["created_time"] assert datetime.fromisoformat(updated_data["modified_time"]) > datetime.fromisoformat( original_data["modified_time"] ) + @staticmethod + def check_created_and_modified_times_not_updated(post_response: Response, new_response: Response): + """Checks that an entity still has the same `created_time` and `updated_time` as its original + + :param post_response: Original response for the entity post request + :param new_response: Updated response for the entity patch/get request + """ + + original_data = post_response.json() + updated_data = new_response.json() + + assert original_data["created_time"] == updated_data["created_time"] + assert original_data["modified_time"] == updated_data["modified_time"] + @staticmethod def replace_unit_values_with_ids_in_properties(data: dict, unit_value_id_dict: dict[str, str]) -> dict: """Inserts unit IDs into some data that may have a 'properties' list within it while removing the unit value. diff --git a/test/e2e/test_catalogue_category.py b/test/e2e/test_catalogue_category.py index a57177cd..1fea665e 100644 --- a/test/e2e/test_catalogue_category.py +++ b/test/e2e/test_catalogue_category.py @@ -57,7 +57,7 @@ def setup(self, test_client): self.test_client = test_client self.unit_value_id_dict = {} - def add_unit_value_and_id(self, unit_value: str, unit_id: str) -> None: + def set_unit_value_and_id(self, unit_value: str, unit_id: str) -> None: """ Stores a unit value and ID inside the `unit_value_id_dict` for tests that need to have a non-existent or invalid unit ID. @@ -75,7 +75,7 @@ def post_unit(self, unit_post_data: dict) -> None: """ post_response = self.test_client.post("/v1/units", json=unit_post_data) - self.add_unit_value_and_id(unit_post_data["value"], post_response.json()["id"]) + self.set_unit_value_and_id(unit_post_data["value"], post_response.json()["id"]) def post_catalogue_category(self, catalogue_category_data: dict) -> Optional[str]: """ @@ -263,16 +263,16 @@ def test_create_leaf_with_properties(self): self.check_post_catalogue_category_success(CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM) def test_create_leaf_with_properties_with_non_existent_unit_id(self): - """Test creating a leaf catalogue category with a property with a non-existent unit ID provided.""" + """Test creating a leaf catalogue category with a property with a non-existent unit ID.""" - self.add_unit_value_and_id("mm", str(ObjectId())) + self.set_unit_value_and_id("mm", str(ObjectId())) self.post_catalogue_category(CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM) self.check_post_catalogue_category_failed_with_detail(422, "The specified unit does not exist") def test_create_leaf_with_properties_with_invalid_unit_id(self): - """Test creating a leaf catalogue category with a property with an invalid unit ID provided.""" + """Test creating a leaf catalogue category with a property with an invalid unit ID.""" - self.add_unit_value_and_id("mm", "invalid-id") + self.set_unit_value_and_id("mm", "invalid-id") self.post_catalogue_category(CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM) self.check_post_catalogue_category_failed_with_detail(422, "The specified unit does not exist") @@ -327,7 +327,7 @@ def test_create_leaf_property_with_invalid_allowed_values_type(self): ) def test_create_leaf_property_with_empty_allowed_values_list(self): - """Test creating a leaf catalogue category with a property with an allowed values list that is empty.""" + """Test creating a leaf catalogue category with a property with an empty allowed values list.""" self.post_leaf_catalogue_category_with_allowed_values("string", {"type": "list", "values": []}) self.check_post_catalogue_category_failed_with_validation_message( @@ -336,8 +336,8 @@ def test_create_leaf_property_with_empty_allowed_values_list(self): ) def test_create_leaf_with_string_property_with_allowed_values_list_invalid_value(self): - """Test creating a leaf catalogue category with a string property with an allowed values list with an invalid - number value in it.""" + """Test creating a leaf catalogue category with a string property with an allowed values list containing an + invalid number.""" self.post_leaf_catalogue_category_with_allowed_values("string", {"type": "list", "values": ["1", "2", 3, "4"]}) self.check_post_catalogue_category_failed_with_validation_message( @@ -348,7 +348,7 @@ def test_create_leaf_with_string_property_with_allowed_values_list_invalid_value def test_create_leaf_with_string_property_with_allowed_values_list_duplicate_value(self): """Test creating a leaf catalogue category with a string property with an allowed values list with a duplicate - string value in it.""" + string value.""" # Capitalisation is different as it shouldn't matter for this test self.post_leaf_catalogue_category_with_allowed_values( @@ -360,8 +360,8 @@ def test_create_leaf_with_string_property_with_allowed_values_list_duplicate_val ) def test_create_leaf_with_number_property_with_allowed_values_list_invalid_value(self): - """Test creating a leaf catalogue category with a number property with an allowed values list with an invalid - number value in it.""" + """Test creating a leaf catalogue category with a number property with an allowed values list containing an + invalid number.""" self.post_leaf_catalogue_category_with_allowed_values("number", {"type": "list", "values": [1, 2, "3", 4]}) self.check_post_catalogue_category_failed_with_validation_message( @@ -372,7 +372,7 @@ def test_create_leaf_with_number_property_with_allowed_values_list_invalid_value def test_create_leaf_with_number_property_with_allowed_values_list_duplicate_value(self): """Test creating a leaf catalogue category with a number property with an allowed values list with a duplicate - number value in it.""" + number value.""" self.post_leaf_catalogue_category_with_allowed_values("number", {"type": "list", "values": [1, 2, 1, 3]}) self.check_post_catalogue_category_failed_with_validation_message( @@ -411,9 +411,9 @@ def check_get_catalogue_category_success(self, expected_catalogue_category_get_d Checks that a prior call to `get_catalogue_category` gave a successful response with the expected data returned. :param expected_catalogue_category_get_data: Dictionary containing the expected catalogue category data returned - as would be required for a `CatalogueCategorySchema`. Does not need - unit IDs as they will be added automatically to check they are as - expected. + as would be required for a `CatalogueCategorySchema` but with any `unit_id`'s + replaced by the `unit` value in its properties as the IDs will be added + automatically. """ assert self._get_response_catalogue_category.status_code == 200 @@ -777,7 +777,7 @@ def patch_properties_with_property_with_allowed_values( }, ) - def check_patch_catalogue_category_response_success(self, expected_catalogue_category_get_data: dict) -> None: + def check_patch_catalogue_category_success(self, expected_catalogue_category_get_data: dict) -> None: """ Checks that a prior call to `patch_catalogue_category` gave a successful response with the expected data returned. @@ -829,7 +829,7 @@ def test_partial_update_name(self): catalogue_category_id = self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_REQUIRED_VALUES_ONLY) self.patch_catalogue_category(catalogue_category_id, {"name": "New Name"}) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, "name": "New Name", "code": "new-name"} ) @@ -842,7 +842,7 @@ def test_partial_update_parent_id(self): ) self.patch_catalogue_category(catalogue_category_id, {"parent_id": parent_id}) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_NO_PARENT_NO_PROPERTIES_B, "parent_id": parent_id} ) @@ -929,7 +929,7 @@ def test_partial_update_name_capitalisation(self): {**CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, "name": "Test catalogue category"} ) self.patch_catalogue_category(catalogue_category_id, {"name": "Test Catalogue Category"}) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( { **CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, "name": "Test Catalogue Category", @@ -950,7 +950,7 @@ def test_partial_update_non_leaf_all_valid_values_when_no_children(self): {**CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM, "parent_id": new_parent_id}, ) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM, "parent_id": new_parent_id} ) @@ -962,7 +962,7 @@ def test_partial_update_non_leaf_to_leaf_without_properties(self): self.patch_catalogue_category(catalogue_category_id, {"is_leaf": True}) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, "is_leaf": True} ) @@ -978,7 +978,7 @@ def test_partial_update_non_leaf_all_valid_values_when_has_child_catalogue_categ self.patch_catalogue_category(catalogue_category_id, update_data) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, **update_data, "code": "new-name"} ) @@ -1009,7 +1009,7 @@ def test_partial_update_leaf_all_valid_values_when_no_children(self): {**CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_NO_PARENT_NO_PROPERTIES_A, "parent_id": new_parent_id}, ) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_NO_PARENT_NO_PROPERTIES_A, "parent_id": new_parent_id} ) @@ -1025,7 +1025,7 @@ def test_partial_update_leaf_all_valid_values_when_has_child_catalogue_item(self self.patch_catalogue_category(catalogue_category_id, update_data) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_NO_PROPERTIES, **update_data, "code": "new-name"} ) @@ -1074,7 +1074,7 @@ def test_partial_update_leaf_to_non_leaf_with_properties(self): }, ) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_NO_PARENT_NO_PROPERTIES_A, "parent_id": new_parent_id} ) @@ -1083,7 +1083,7 @@ def test_partial_update_is_leaf_no_children(self): catalogue_category_id = self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_REQUIRED_VALUES_ONLY) self.patch_catalogue_category(catalogue_category_id, {"is_leaf": True}) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( {**CATALOGUE_CATEGORY_GET_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, "is_leaf": True} ) @@ -1112,7 +1112,7 @@ def test_partial_update_leaf_properties(self): {"properties": CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM["properties"]}, ) - self.check_patch_catalogue_category_response_success( + self.check_patch_catalogue_category_success( { **CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_NO_PROPERTIES, "properties": CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM["properties"], @@ -1122,7 +1122,7 @@ def test_partial_update_leaf_properties(self): def test_partial_update_leaf_with_properties_with_non_existent_unit_id(self): """Test updating a leaf catalogue category's properties to have a property with a non-existent unit ID.""" - self.add_unit_value_and_id("mm", str(ObjectId())) + self.set_unit_value_and_id("mm", str(ObjectId())) catalogue_category_id = self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_LEAF_NO_PARENT_NO_PROPERTIES) self.patch_catalogue_category( catalogue_category_id, {"properties": [CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT]} @@ -1130,9 +1130,9 @@ def test_partial_update_leaf_with_properties_with_non_existent_unit_id(self): self.check_patch_catalogue_category_failed_with_detail(422, "The specified unit does not exist") def test_partial_update_leaf_with_properties_with_invalid_unit_id(self): - """Test updating a leaf catalogue category's properties to have a property with an invalid unit ID provided.""" + """Test updating a leaf catalogue category's properties to have a property with an invalid unit ID.""" - self.add_unit_value_and_id("mm", "invalid-id") + self.set_unit_value_and_id("mm", "invalid-id") catalogue_category_id = self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_LEAF_NO_PARENT_NO_PROPERTIES) self.patch_catalogue_category( catalogue_category_id, {"properties": [CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT]} diff --git a/test/e2e/test_catalogue_category_property.py b/test/e2e/test_catalogue_category_property.py index 47ddc112..67ad5069 100644 --- a/test/e2e/test_catalogue_category_property.py +++ b/test/e2e/test_catalogue_category_property.py @@ -2,891 +2,780 @@ End-to-End tests for the properties endpoint of the catalogue category router """ -from test.conftest import add_ids_to_properties -from test.e2e.conftest import replace_unit_values_with_ids_in_properties -from test.mock_data import SYSTEM_POST_DATA_NO_PARENT_A, UNIT_POST_DATA_MM, USAGE_STATUS_POST_DATA_NEW +# Expect some duplicate code inside tests as the tests for the different entities can be very similar +# pylint: disable=too-many-lines +# pylint: disable=duplicate-code +# pylint: disable=too-many-public-methods +# pylint: disable=too-many-ancestors + +from test.e2e.conftest import E2ETestHelpers +from test.e2e.test_catalogue_category import GetDSL as CatalogueCategoryGetDSL +from test.e2e.test_catalogue_item import GetDSL as CatalogueItemGetDSL +from test.e2e.test_item import GetDSL as ItemGetDSL +from test.mock_data import ( + BASE_CATALOGUE_CATEGORY_GET_DATA_WITH_PROPERTIES_MM, + CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_REQUIRED_VALUES_ONLY, + CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT, + CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_BOOLEAN_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY, + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT, + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_STRING_MANDATORY, + CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, + ITEM_GET_DATA_WITH_ALL_PROPERTIES, + PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE, + PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE, + PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_1, + PROPERTY_DATA_STRING_MANDATORY_TEXT, + PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE, + PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_NONE, + PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE, + PROPERTY_GET_DATA_STRING_MANDATORY_TEXT, +) from typing import Optional -from unittest.mock import ANY -import pytest from bson import ObjectId from fastapi import Response -from fastapi.testclient import TestClient - - -EXISTING_CATALOGUE_CATEGORY_PROPERTY_POST = {"name": "Property A", "type": "number", "unit": "mm", "mandatory": False} -EXISTING_CATALOGUE_CATEGORY_PROPERTY_EXPECTED = {**EXISTING_CATALOGUE_CATEGORY_PROPERTY_POST, "allowed_values": None} -EXISTING_PROPERTY_EXPECTED = {"name": "Property A", "unit": "mm", "value": 20} - -# pylint:disable=duplicate-code -CATALOGUE_CATEGORY_POST_A = { - "name": "Category A", - "is_leaf": True, - "properties": [EXISTING_CATALOGUE_CATEGORY_PROPERTY_POST], -} - -CATALOGUE_ITEM_POST_A = { - "name": "Catalogue Item A", - "description": "This is Catalogue Item A", - "cost_gbp": 129.99, - "days_to_replace": 2.0, - "drawing_link": "https://drawing-link.com/", - "item_model_number": "abc123", - "is_obsolete": False, - "properties": [{"name": "Property A", "value": 20}], -} - -MANUFACTURER_POST = { - "name": "Manufacturer A", - "url": "http://example.com/", - "address": { - "address_line": "1 Example Street", - "town": "Oxford", - "county": "Oxfordshire", - "country": "United Kingdom", - "postcode": "OX1 2AB", - }, - "telephone": "0932348348", -} - - -ITEM_POST = { - "is_defective": False, - "usage_status": "New", - "warranty_end_date": "2015-11-15T23:59:59Z", - "serial_number": "xyz123", - "delivered_date": "2012-12-05T12:00:00Z", - "notes": "Test notes", - "properties": [{"name": "Property A", "value": 20}], -} - -# pylint:enable=duplicate-code - -CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY = { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": False, -} -CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY_EXPECTED = { - **CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, - "allowed_values": None, -} - -NEW_CATALOGUE_CATEGORY_PROPERTY_NON_MANDATORY_EXPECTED = CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY_EXPECTED -NEW_PROPERTY_NON_MANDATORY_EXPECTED = {"name": "Property B", "unit": "mm", "value": None} - -CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY = { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - "default_value": 20, -} -CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_EXPECTED = { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - "allowed_values": None, -} - -NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED = CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_EXPECTED -NEW_PROPERTY_MANDATORY_EXPECTED = {"name": "Property B", "unit": "mm", "value": 20} - -CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES = { - **CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY, - "allowed_values": {"type": "list", "values": [10, 20, 30]}, -} -CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED = { - **NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED, - "allowed_values": {"type": "list", "values": [10, 20, 30]}, -} -NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_ALLOWED_VALUES_EXPECTED = ( - CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED -) - -CATALOGUE_CATEGORY_PROPERTY_PATCH = { - "name": "New property name", - "allowed_values": {"type": "list", "values": [10, 20, 30, 40]}, -} - -CATALOGUE_CATEGORY_PROPERTY_PATCH_EXPECTED = { - **CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED, - **CATALOGUE_CATEGORY_PROPERTY_PATCH, -} -NEW_CATALOGUE_CATEGORY_PROPERTY_PATCH_EXPECTED = CATALOGUE_CATEGORY_PROPERTY_PATCH_EXPECTED -NEW_PROPERTY_PATCH_EXPECTED = {"name": "New property name", "unit": "mm", "value": 20} -CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY = { - "allowed_values": {"type": "list", "values": [10, 20, 30, 40]}, -} -CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY_EXPECTED = { - **CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED, - **CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY, -} +class CreateDSL(ItemGetDSL, CatalogueCategoryGetDSL, CatalogueItemGetDSL): + """Base class for create tests.""" + item_id: Optional[str] -class CreateDSL: - """Base class for create tests""" + _post_response_property: Response + _existing_catalogue_category_properties: list[dict] - test_client: TestClient - catalogue_category: dict - catalogue_item: dict - item: dict - units: list[dict] + def post_test_item_and_prerequisites(self) -> None: + """Posts an item for testing having first posted the required prerequisite entities. The item posted + starts with a single property already defined.""" - _catalogue_item_post_response: Response - property: dict - - @pytest.fixture(autouse=True) - def setup(self, test_client): - """Setup fixtures""" - - self.test_client = test_client - self.units = [] - - def post_catalogue_category_and_items(self): - """Posts a catalogue category, catalogue item and item for create tests to act on""" + self.item_id = self.post_item_and_prerequisites_with_given_properties( + [CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY], + [PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE], + [PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE], + ) + self._existing_catalogue_category_properties = self._post_response_catalogue_category.json()["properties"] - # pylint:disable=duplicate-code + def post_property(self, property_data: dict) -> Optional[str]: + """Posts a property to a catalogue category. - response = self.test_client.post("/v1/units", json=UNIT_POST_DATA_MM) - unit_mm = response.json() + :param property_data: Dictionary containing the basic catalogue category property data data as would be required + for a `CatalogueCategoryPropertyPostSchema` but with any `unit_id`'s replaced by the + `unit` value in its properties as the IDs will be added automatically. + :return: ID of the created property (or `None` if not successful). + """ - self.units = [unit_mm] + # First need to replace all unit values with unit IDs + full_property_data = property_data.copy() + full_property_data = E2ETestHelpers.replace_unit_values_with_ids_in_properties( + {"properties": [full_property_data]}, self.unit_value_id_dict + )["properties"][0] - response = self.test_client.post( - "/v1/catalogue-categories", - json={ - **CATALOGUE_CATEGORY_POST_A, - "properties": replace_unit_values_with_ids_in_properties( - CATALOGUE_CATEGORY_POST_A["properties"], self.units - ), - }, + self._post_response_property = self.test_client.post( + "/v1/catalogue-categories/" f"{self.catalogue_category_id}/properties", json=full_property_data ) - self.catalogue_category = response.json() + property_id = ( + self._post_response_property.json()["id"] if self._post_response_property.status_code == 201 else None + ) - response = self.test_client.post("/v1/systems", json=SYSTEM_POST_DATA_NO_PARENT_A) - system_id = response.json()["id"] + # Append to the property name id dict for the new property + if property_id: + property_data = self._post_response_property.json() + self.property_name_id_dict[property_data["name"]] = property_data["id"] + self._unit_data_lookup_dict[property_data["id"]] = { + "unit_id": property_data["unit_id"], + "unit": property_data["unit"], + } - response = self.test_client.post("/v1/manufacturers", json=MANUFACTURER_POST) - manufacturer_id = response.json()["id"] + return property_id - catalogue_item_post = { - **CATALOGUE_ITEM_POST_A, - "catalogue_category_id": self.catalogue_category["id"], - "manufacturer_id": manufacturer_id, - "properties": add_ids_to_properties( - self.catalogue_category["properties"], - CATALOGUE_ITEM_POST_A["properties"], - ), - } - response = self.test_client.post("/v1/catalogue-items", json=catalogue_item_post) - self.catalogue_item = response.json() - catalogue_item_id = self.catalogue_item["id"] - - response = self.test_client.post("/v1/usage-statuses", json=USAGE_STATUS_POST_DATA_NEW) - usage_status_id = response.json()["id"] - item_post = { - **ITEM_POST, - "catalogue_item_id": catalogue_item_id, - "system_id": system_id, - "usage_status_id": usage_status_id, - "properties": add_ids_to_properties(self.catalogue_category["properties"], ITEM_POST["properties"]), - } - response = self.test_client.post("/v1/items", json=item_post) - self.item = response.json() - # pylint:enable=duplicate-code + def post_property_with_allowed_values(self, property_type: str, allowed_values_post_data: dict) -> None: + """ + Utility method that posts property named 'property' of a given type with a given set of allowed values. - def post_non_leaf_catalogue_category(self): - """Posts a non leaf catalogue category""" + :param property_type: Type of the property to post. + :param allowed_values_post_data: Dictionary containing the allowed values data as would be required for an + `AllowedValuesSchema`. + """ - response = self.test_client.post( - "/v1/catalogue-categories", json={**CATALOGUE_CATEGORY_POST_A, "is_leaf": False} + self.post_property( + { + "name": "property", + "type": property_type, + "mandatory": False, + "allowed_values": allowed_values_post_data, + } ) - self.catalogue_category = response.json() - def post_property(self, property_post, catalogue_category_id: Optional[str] = None): - """Posts a property to the catalogue category""" + def check_post_property_success(self, expected_property_get_data: dict) -> None: + """ + Checks that a prior call to `post_property` gave a successful response with the expected data returned. - property_post = replace_unit_values_with_ids_in_properties([property_post], self.units)[0] - self._catalogue_item_post_response = self.test_client.post( - "/v1/catalogue-categories/" - f"{catalogue_category_id if catalogue_category_id else self.catalogue_category['id']}/properties", - json=property_post, - ) + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `CatalogueCategoryPropertySchema` but with any `unit_id`'s + replaced by the `unit` value in its properties as the IDs will be added + automatically. + """ - def check_property_post_response_success(self, property_expected): - """Checks the response of posting a property succeeded as expected""" + assert self._post_response_property.status_code == 201 + assert ( + self._post_response_property.json() + == E2ETestHelpers.add_unit_ids_to_properties( + {"properties": [expected_property_get_data]}, self.unit_value_id_dict + )["properties"][0] + ) - assert self._catalogue_item_post_response.status_code == 201 - self.property = self._catalogue_item_post_response.json() + def check_post_property_failed_with_detail(self, status_code: int, detail: str) -> None: + """ + Checks that a prior call to `post_property` gave a failed response with the expected code and error message. - assert self.property == {**property_expected, "id": ANY, "unit_id": ANY} + :param status_code: Expected status code of the response. + :param detail: Expected detail given in the response. + """ - def check_property_post_response_failed_with_message(self, status_code, detail): - """Checks the response of posting a property failed as expected""" + assert self._post_response_property.status_code == status_code + assert self._post_response_property.json()["detail"] == detail - assert self._catalogue_item_post_response.status_code == status_code - assert self._catalogue_item_post_response.json()["detail"] == detail + def check_post_property_failed_with_validation_message(self, status_code: int, message: str) -> None: + """ + Checks that a prior call to `post_property` gave a failed response with the expected code and pydantic + validation error message. - def check_property_post_response_failed_with_validation_message(self, status_code, message): - """Checks the response of posting a property failed as expected with a pydantic validation - message""" + :param status_code: Expected status code of the response. + :param message: Expected validation error message given in the response. + """ - assert self._catalogue_item_post_response.status_code == status_code - assert self._catalogue_item_post_response.json()["detail"][0]["msg"] == message + assert self._post_response_property.status_code == status_code + assert self._post_response_property.json()["detail"][0]["msg"] == message - def check_catalogue_category_updated(self, property_expected): - """Checks the catalogue category is updated correctly with the new property""" + def check_catalogue_category_updated(self, expected_property_get_data: dict) -> None: + """Checks the catalogue category is updated correctly with the new property. - new_catalogue_category = self.test_client.get( - f"/v1/catalogue-categories/{self.catalogue_category['id']}" - ).json() + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `CatalogueCategoryPropertySchema` but with any `unit_id`'s + replaced by the `unit` value in its properties as the IDs will be added + automatically. + """ - assert new_catalogue_category["properties"] == add_ids_to_properties( - [*self.catalogue_category["properties"], self.property], - [ - EXISTING_CATALOGUE_CATEGORY_PROPERTY_EXPECTED, - property_expected, - ], + self.get_catalogue_category(self.catalogue_category_id) + self.check_get_catalogue_category_success( + { + **BASE_CATALOGUE_CATEGORY_GET_DATA_WITH_PROPERTIES_MM, + "properties": [ + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_BOOLEAN_MANDATORY, + expected_property_get_data, + ], + } + ) + E2ETestHelpers.check_created_and_modified_times_updated_correctly( + self._post_response_catalogue_category, self._get_response_catalogue_category ) - def check_catalogue_item_updated(self, property_expected): - """Checks the catalogue item is updated correctly with the new property""" + def check_catalogue_item_updated(self, expected_property_get_data: dict) -> None: + """Checks the catalogue item is updated correctly with the new property. - new_catalogue_item = self.test_client.get(f"/v1/catalogue-items/{self.catalogue_item['id']}").json() + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `PropertySchema`. Does not need mandatory IDs + (e.g. property/unit IDs) as they will be added automatically to check they + are as expected. + """ - assert new_catalogue_item["properties"] == add_ids_to_properties( - [*self.catalogue_category["properties"], self.property], - [EXISTING_PROPERTY_EXPECTED, property_expected], + self.get_catalogue_item(self.catalogue_item_id) + self.check_get_catalogue_item_success( + { + **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, + "properties": [PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE, expected_property_get_data], + } ) - - def check_item_updated(self, property_expected): - """Checks the item is updated correctly with the new property""" - - new_item = self.test_client.get(f"/v1/items/{self.item['id']}").json() - assert new_item["properties"] == add_ids_to_properties( - [*self.catalogue_category["properties"], self.property], - [EXISTING_PROPERTY_EXPECTED, property_expected], + E2ETestHelpers.check_created_and_modified_times_updated_correctly( + self._post_response_catalogue_item, self._get_response_catalogue_item ) + def check_item_updated(self, expected_property_get_data: dict) -> None: + """Checks the item is updated correctly with the new property. -class TestCreate(CreateDSL): - """Tests for creating a property at the catalogue category level""" - - def test_create_non_mandatory_property(self): + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `PropertySchema`. Does not need mandatory IDs + (e.g. property/unit IDs) as they will be added automatically to check they + are as expected. """ - Test adding a non-mandatory property to an already existing catalogue category, catalogue item and item - """ - - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY_EXPECTED) - self.check_catalogue_category_updated(NEW_CATALOGUE_CATEGORY_PROPERTY_NON_MANDATORY_EXPECTED) - self.check_catalogue_item_updated(NEW_PROPERTY_NON_MANDATORY_EXPECTED) - self.check_item_updated(NEW_PROPERTY_NON_MANDATORY_EXPECTED) - - def test_create_non_mandatory_property_with_no_unit(self): - """ - Test adding a non-mandatory property to an already existing catalogue category, catalogue item and item - with no unit - """ + self.get_item(self.item_id) + self.check_get_item_success( + { + **ITEM_GET_DATA_WITH_ALL_PROPERTIES, + "properties": [ + PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE, + expected_property_get_data, + ], + } + ) + E2ETestHelpers.check_created_and_modified_times_updated_correctly( + self._post_response_item, self._get_response_item + ) - self.post_catalogue_category_and_items() - self.post_property({**CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, "unit": None}) - self.check_property_post_response_success( - {**CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY_EXPECTED, "unit": None} - ) - self.check_catalogue_category_updated({**NEW_CATALOGUE_CATEGORY_PROPERTY_NON_MANDATORY_EXPECTED, "unit": None}) - self.check_catalogue_item_updated({**NEW_PROPERTY_NON_MANDATORY_EXPECTED, "unit": None}) - self.check_item_updated({**NEW_PROPERTY_NON_MANDATORY_EXPECTED, "unit": None}) +class TestCreate(CreateDSL): + """Tests for creating a property at the catalogue category level.""" - def test_create_mandatory_property(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item - """ + def test_create_non_mandatory(self): + """Test creating a non mandatory property.""" - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY) + self.post_test_item_and_prerequisites() + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_EXPECTED) - self.check_catalogue_category_updated(NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED) - self.check_catalogue_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) - self.check_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) + self.check_post_property_success(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY) + self.check_catalogue_category_updated(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY) + self.check_catalogue_item_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE) + self.check_item_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE) - def test_create_mandatory_property_with_allowed_values_list(self): - """ - Test adding a mandatory property with an allowed values list to an already existing catalogue category, - catalogue item and item (ensures the default_value is allowed) - """ + def test_create_non_mandatory_with_unit(self): + """Test creating a non mandatory property with a unit.""" - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) + self.post_test_item_and_prerequisites() + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) - self.check_catalogue_category_updated(NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_ALLOWED_VALUES_EXPECTED) - self.check_catalogue_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) - self.check_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) + self.check_post_property_success(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT) + self.check_catalogue_category_updated(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT) + self.check_catalogue_item_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE) + self.check_item_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE) - def test_create_property_with_non_existent_catalogue_category_id(self): - """Test adding a property when the specified catalogue category id is invalid""" + def test_create_mandatory(self): + """Test creating a mandatory property.""" + self.post_test_item_and_prerequisites() self.post_property( - CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, - catalogue_category_id=str(ObjectId()), + { + **CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY, + "default_value": PROPERTY_DATA_STRING_MANDATORY_TEXT["value"], + } ) - self.check_property_post_response_failed_with_message(404, "Catalogue category not found") - def test_create_property_with_invalid_catalogue_category_id(self): - """Test adding a property when given an invalid catalogue category id""" + self.check_post_property_success(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_STRING_MANDATORY) + self.check_catalogue_category_updated(CATALOGUE_CATEGORY_PROPERTY_GET_DATA_STRING_MANDATORY) + self.check_catalogue_item_updated(PROPERTY_GET_DATA_STRING_MANDATORY_TEXT) + self.check_item_updated(PROPERTY_GET_DATA_STRING_MANDATORY_TEXT) - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, catalogue_category_id="invalid") - self.check_property_post_response_failed_with_message(404, "Catalogue category not found") + def test_create_mandatory_with_boolean_int_default_value(self): + """Test creating a mandatory property with a default value that is a boolean value while the type of the + property is an int (this can cause an issue if not implemented property as boolean is a subclass of int + - technically also applies to other endpoints' type checks but they occur in the same place in code anyway). + """ - def test_create_property_with_invalid_unit_id(self): - """Test adding a property when given an invalid unit id""" + self.post_test_item_and_prerequisites() + self.post_property({**CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_MANDATORY, "default_value": True}) - self.post_catalogue_category_and_items() + self.check_post_property_failed_with_validation_message( + 422, "Value error, default_value must be the same type as the property itself" + ) - self.post_property({**CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, "unit_id": "invalid"}) - self.check_property_post_response_failed_with_message(422, "The specified unit does not exist") + def test_create_mandatory_without_default_value(self): + """Test creating a mandatory property without a default value.""" - def test_create_property_with_non_existent_unit_id(self): - """Test adding a property when the specified unit id is invalid""" + self.post_test_item_and_prerequisites() + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY) - self.post_catalogue_category_and_items() + self.check_post_property_failed_with_detail(422, "Cannot add a mandatory property without a default value") - self.post_property({**CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY, "unit_id": str(ObjectId())}) - self.check_property_post_response_failed_with_message(422, "The specified unit does not exist") + def test_create_with_allowed_values_list(self): + """Test creating a property with an allowed values list.""" - def test_create_mandatory_property_without_default_value(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item without - a default value - """ + self.post_test_item_and_prerequisites() + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST) - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - } + self.check_post_property_success( + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST ) - self.check_property_post_response_failed_with_message( - 422, "Cannot add a mandatory property without a default value" - ) - - def test_create_mandatory_property_with_invalid_default_value_boolean_int(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item without - with a default value that is a boolean value while the type of the property is an int (this can cause an - issue if not implemented property as boolean is a subclass of int - technically also applies to other - endpoints' type checks but they occur in the same place in code anyway) - """ - - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - "allowed_values": {"type": "list", "values": [1, 2, 3]}, - "default_value": True, - } + self.check_catalogue_category_updated( + CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST ) - self.check_property_post_response_failed_with_validation_message( - 422, "Value error, default_value must be the same type as the property itself" + self.check_catalogue_item_updated( + {**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_1, "value": None} ) + self.check_item_updated({**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_1, "value": None}) - def test_create_mandatory_property_with_invalid_default_value_not_in_allowed_values_list(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with a - default value that is excluded by an allowed_values list - """ + def test_create_with_allowed_values_list_with_invalid_default_value_not_in_list(self): + """Test creating a property with an allowed values list with a default value that is not present in that + list.""" - self.post_catalogue_category_and_items() + self.post_test_item_and_prerequisites() self.post_property( - { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - "allowed_values": {"type": "list", "values": [1, 2, 3]}, - "default_value": 42, - } + {**CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, "default_value": 9001} ) - self.check_property_post_response_failed_with_validation_message( + + self.check_post_property_failed_with_validation_message( 422, "Value error, default_value is not one of the allowed_values" ) - def test_create_mandatory_property_with_invalid_default_value_type(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a default value with an invalid type - """ + def test_create_with_allowed_values_list_with_invalid_default_value_type(self): + """Test creating a property with an allowed values list with a default value that has an incorrect type.""" - self.post_catalogue_category_and_items() + self.post_test_item_and_prerequisites() self.post_property( - { - "name": "Property B", - "type": "number", - "unit": "mm", - "mandatory": True, - "default_value": "wrong_type", - } + {**CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, "default_value": False} ) - self.check_property_post_response_failed_with_validation_message( + + self.check_post_property_failed_with_validation_message( 422, "Value error, default_value must be the same type as the property itself" ) - def test_create_mandatory_property_with_invalid_allowed_values_list_number(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a number type and an allowed_values list with an invalid number - """ + def test_create_with_invalid_allowed_values_type(self): + """Test creating a string property with an invalid allowed values type.""" - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "number", - "mandatory": True, - "allowed_values": {"type": "list", "values": [2, "4", 6]}, - "default_value": False, - } - ) - self.check_property_post_response_failed_with_validation_message( + self.post_property_with_allowed_values("string", {"type": "invalid-type"}) + + self.check_post_property_failed_with_validation_message( 422, - "Value error, allowed_values of type 'list' must only contain values of the same type as the property " - "itself", + "Input tag 'invalid-type' found using 'type' does not match any of the expected tags: 'list'", ) - def test_create_mandatory_property_with_invalid_allowed_values_list_string(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a string type and an allowed_values list with an invalid number - """ + def test_create_with_empty_allowed_values_list(self): + """Test creating a string property with an empty allowed values list.""" - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "string", - "mandatory": True, - "allowed_values": {"type": "list", "values": ["red", "green", 6]}, - "default_value": False, - } + self.post_property_with_allowed_values("string", {"type": "list", "values": []}) + + self.check_post_property_failed_with_validation_message( + 422, + "List should have at least 1 item after validation, not 0", ) - self.check_property_post_response_failed_with_validation_message( + + def test_create_string_with_allowed_values_list_with_invalid_value(self): + """Test creating a string property with an allowed values list containing an invalid string value.""" + + self.post_property_with_allowed_values("string", {"type": "list", "values": ["1", "2", 3, "4"]}) + + self.check_post_property_failed_with_validation_message( 422, "Value error, allowed_values of type 'list' must only contain values of the same type as the property " "itself", ) - def test_create_mandatory_property_with_invalid_allowed_values_list_duplicate_number(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a number type and an allowed_values list with a duplicate number value in it - """ + def test_create_string_with_allowed_values_list_with_duplicate_value(self): + """Test creating a string property with an allowed values list containing a duplicate string value.""" - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "number", - "mandatory": True, - "allowed_values": {"type": "list", "values": [42, 10.2, 12, 42]}, - "default_value": False, - } + self.post_property_with_allowed_values( + "string", {"type": "list", "values": ["value1", "value2", "Value1", "value3"]} ) - self.check_property_post_response_failed_with_validation_message( - 422, "Value error, allowed_values of type 'list' contains a duplicate value: 42" + + self.check_post_property_failed_with_validation_message( + 422, + "Value error, allowed_values of type 'list' contains a duplicate value: Value1", ) - def test_create_mandatory_property_with_invalid_allowed_values_list_duplicate_string(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a string type and an allowed_values list with a duplicate string value in it - """ + def test_create_number_with_allowed_values_list_with_invalid_value(self): + """Test creating a number property with an allowed values list containing an invalid number value.""" - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "string", - "mandatory": True, - "allowed_values": {"type": "list", "values": ["value1", "value2", "value3", "Value2"]}, - "default_value": False, - } - ) - self.check_property_post_response_failed_with_validation_message( - 422, "Value error, allowed_values of type 'list' contains a duplicate value: Value2" + self.post_property_with_allowed_values("number", {"type": "list", "values": [1, 2, "3", 4]}) + + self.check_post_property_failed_with_validation_message( + 422, + "Value error, allowed_values of type 'list' must only contain values of the same type as the property " + "itself", ) - def test_create_mandatory_property_with_boolean_allowed_values_list(self): - """ - Test adding a mandatory property to an already existing catalogue category, catalogue item and item with - a boolean type and an allowed values list is rejected appropriately - """ + def test_create_number_with_allowed_values_list_with_duplicate_value(self): + """Test creating a number property with an allowed values list containing a duplicate number value.""" - self.post_catalogue_category_and_items() - self.post_property( - { - "name": "Property B", - "type": "boolean", - "mandatory": True, - "allowed_values": {"type": "list", "values": [1, 2, 3]}, - "default_value": False, - } - ) - self.check_property_post_response_failed_with_validation_message( - 422, "Value error, allowed_values not allowed for a boolean property 'Property B'" + self.post_property_with_allowed_values("number", {"type": "list", "values": [1, 2, 1, 3]}) + + self.check_post_property_failed_with_validation_message( + 422, + "Value error, allowed_values of type 'list' contains a duplicate value: 1", ) - def test_create_property_non_leaf_catalogue_category(self): - """ - Test adding a property to an non leaf catalogue category - """ + def test_create_boolean_with_allowed_values_list(self): + """Test creating a boolean property with an allowed values list.""" - self.post_non_leaf_catalogue_category() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_NON_MANDATORY) - self.check_property_post_response_failed_with_message( - 422, "Cannot add a property to a non-leaf catalogue category" + self.post_property_with_allowed_values("boolean", {"type": "list", "values": [True, False]}) + + self.check_post_property_failed_with_validation_message( + 422, + "Value error, allowed_values not allowed for a boolean property 'property'", ) - def test_create_property_with_duplicate_name(self): - """ - Test adding a mandatory property with a name equal to one already existing - """ + def test_create_with_non_existent_catalogue_category_id(self): + """Test creating a property with a non-existent catalogue item ID.""" - self.post_catalogue_category_and_items() - self.post_property(EXISTING_CATALOGUE_CATEGORY_PROPERTY_POST) - self.check_property_post_response_failed_with_message(422, "Duplicate property name: Property A") + self.catalogue_item_id = str(ObjectId()) + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) + self.check_post_property_failed_with_detail(404, "Catalogue category not found") -class UpdateDSL(CreateDSL): - """Base class for update tests (inherits CreateDSL to gain access to posts)""" + def test_create_with_invalid_catalogue_category_id(self): + """Test creating a property with an invalid catalogue item ID.""" - _catalogue_item_patch_response: Response + self.catalogue_item_id = "invalid-id" + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - def patch_property( - self, - property_patch, - catalogue_category_id: Optional[str] = None, - property_id: Optional[str] = None, - ): - """Patches a posted property""" - self._catalogue_item_patch_response = self.test_client.patch( - "/v1/catalogue-categories/" - f"{catalogue_category_id if catalogue_category_id else self.catalogue_category['id']}" - "/properties/" - f"{property_id if property_id else self.property['id']}", - json=property_patch, - ) + self.check_post_property_failed_with_detail(404, "Catalogue category not found") - def check_property_patch_response_success(self, property_expected): - """Checks the response of patching a property succeeded as expected""" + def test_create_with_non_existent_unit_id(self): + """Test creating a property with a non-existent unit ID.""" - assert self._catalogue_item_patch_response.status_code == 200 - self.property = self._catalogue_item_patch_response.json() - assert self.property == {**property_expected, "id": ANY, "unit_id": ANY} + self.post_test_item_and_prerequisites() + self.set_unit_value_and_id("mm", str(ObjectId())) + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT) - def check_property_patch_response_failed_with_message(self, status_code, detail): - """Checks the response of patching a property failed as expected""" + self.check_post_property_failed_with_detail(422, "The specified unit does not exist") - assert self._catalogue_item_patch_response.status_code == status_code - assert self._catalogue_item_patch_response.json()["detail"] == detail + def test_create_with_invalid_unit_id(self): + """Test creating a property with an invalid unit ID.""" + self.post_test_item_and_prerequisites() + self.set_unit_value_and_id("mm", "invalid_id") + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT) -class TestUpdate(UpdateDSL): - """Tests for updating a property at the catalogue category level""" + self.check_post_property_failed_with_detail(422, "The specified unit does not exist") - def test_update(self): - """ - Test updating a property at the catalogue category level - """ + def test_create_with_non_leaf_catalogue_category(self): + """Test creating a property within a non-leaf catalogue category.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_NON_LEAF_REQUIRED_VALUES_ONLY) + self.post_property(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH) + self.check_post_property_failed_with_detail(422, "Cannot add a property to a non-leaf catalogue category") - # Check updated correctly down the tree - self.check_property_patch_response_success(CATALOGUE_CATEGORY_PROPERTY_PATCH_EXPECTED) - self.check_catalogue_category_updated(NEW_CATALOGUE_CATEGORY_PROPERTY_PATCH_EXPECTED) - self.check_catalogue_item_updated(NEW_PROPERTY_PATCH_EXPECTED) - self.check_item_updated(NEW_PROPERTY_PATCH_EXPECTED) + def test_create_with_duplicate_name(self): + """Test creating a property within with the same name as an existing one.""" - def test_update_category_only(self): - """ - Test updating a property at the catalogue category level but with an update that should leave the catalogue - items and items alone (i.e. only updating the allowed values) - """ + self.post_test_item_and_prerequisites() + self.post_property({**CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY, "default_value": False}) - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + self.check_post_property_failed_with_detail( + 422, f"Duplicate property name: {CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY['name']}" + ) - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY) - # Check updated correctly - self.check_property_patch_response_success(CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY_EXPECTED) - self.check_catalogue_category_updated(CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY_EXPECTED) - # These are testing values are the same as they should have been prior to the patch (NEW_ is only there from - # the create tests) - self.check_catalogue_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) - self.check_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) +class UpdateDSL(CreateDSL): + """Base class for update tests.""" - def test_update_category_no_changes_allowed_values_none(self): - """ - Test updating a property at the catalogue category level but with an update that shouldn't change anything - (in this case also passing allowed_values as None and keeping it None on the patch request) + _patch_response_property: Response + + def post_test_property_and_prerequisites(self, property_data: dict) -> Optional[str]: + """Posts an property for testing having first posted the required prerequisite entities. + + :param property_data: Dictionary containing the basic catalogue category property data data as would be required + for a `CatalogueCategoryPropertyPostSchema` but with any `unit_id`'s replaced by the + `unit` value in its properties as the IDs will be added automatically. + :return: ID of the created property (or `None` if not successful). """ - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY) - self.check_property_post_response_success(NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED) + # Add in property at the start rather than following a post so created & modified times are accurate to before + # any patch is done during update tests + self.item_id = self.post_item_and_prerequisites_with_given_properties( + [CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY, property_data], + [PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE, property_data], + [PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE, property_data], + ) + self._existing_catalogue_category_properties = self._post_response_catalogue_category.json()["properties"] - # Patch the property - self.patch_property({"name": CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY["name"], "allowed_values": None}) + return self._existing_catalogue_category_properties[1]["id"] - # Check updated correctly - self.check_property_patch_response_success(NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED) - # These are testing values are the same as they should have been prior to the patch (NEW_ is only there from - # the create tests) - self.check_catalogue_category_updated(NEW_CATALOGUE_CATEGORY_PROPERTY_MANDATORY_EXPECTED) - self.check_catalogue_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) - self.check_item_updated(NEW_PROPERTY_MANDATORY_EXPECTED) + def post_test_property_and_prerequisites_with_allowed_values_list(self) -> tuple[Optional[str], dict]: + """Posts an property for testing having first posted the required prerequisite entities. - def test_update_non_existent_catalogue_category_id(self): + :return: Tuple with + - ID of the created property (or `None` if not successful). + - Dictionary of the `allowed_values` data used in the creation of the property """ - Test updating a property at the catalogue category level when the specified catalogue category doesn't exist + + return ( + self.post_test_property_and_prerequisites( + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST + ), + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST["allowed_values"], + ) + + def patch_property(self, property_id: str, property_update_data: dict) -> None: """ + Updates an property with the given ID. - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + :param property_id: ID of the property to patch. + :param property_update_data: Dictionary containing the basic catalogue category property data data as would be + required for a `CatalogueCategoryPropertyPatchSchema`. + """ - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH, catalogue_category_id=str(ObjectId())) + # Ensure the property ID is updated and the old one removed + if "name" in property_update_data: + self.property_name_id_dict = { + key: value for key, value in self.property_name_id_dict.items() if value != property_id + } + self.property_name_id_dict[property_update_data["name"]] = property_id - # Check - self.check_property_patch_response_failed_with_message(404, "Catalogue category not found") + self._patch_response_property = self.test_client.patch( + f"/v1/catalogue-categories/{self.catalogue_category_id}/properties/{property_id}", json=property_update_data + ) - def test_update_invalid_catalogue_category_id(self): + def check_patch_property_success(self, expected_property_get_data: dict) -> None: """ - Test updating a property at the catalogue category level when the specified catalogue category id is invalid + Checks that a prior call to `patch_property` gave a successful response with the expected data returned. + + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `CatalogueCategoryPropertySchema` but with any `unit_id`'s + replaced by the `unit` value in its properties as the IDs will be added + automatically. """ - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + assert self._patch_response_property.status_code == 200 + assert ( + self._patch_response_property.json() + == E2ETestHelpers.add_unit_ids_to_properties( + {"properties": [expected_property_get_data]}, self.unit_value_id_dict + )["properties"][0] + ) + + def check_patch_property_failed_with_detail(self, status_code: int, detail: str) -> None: + """ + Checks that a prior call to `patch_property` gave a failed response with the expected code and error message. + + :param status_code: Expected status code of the response. + :param detail: Expected detail given in the response. + """ - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH, catalogue_category_id="invalid") + assert self._patch_response_property.status_code == status_code + assert self._patch_response_property.json()["detail"] == detail - # Check - self.check_property_patch_response_failed_with_message(404, "Catalogue category not found") + def check_catalogue_item_not_updated(self, expected_property_get_data: dict) -> None: + """Checks the catalogue item has not been updated. - def test_update_non_existent_property_id(self): + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `PropertySchema`. Does not need mandatory IDs + (e.g. property/unit IDs) as they will be added automatically to check they + are as expected. """ - Test updating a property at the catalogue category level when the specified property doesn't - exist in the specified catalogue category + + self.get_catalogue_item(self.catalogue_item_id) + self.check_get_catalogue_item_success( + { + **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, + "properties": [PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE, expected_property_get_data], + } + ) + E2ETestHelpers.check_created_and_modified_times_not_updated( + self._post_response_catalogue_item, self._get_response_catalogue_item + ) + + def check_item_not_updated(self, expected_property_get_data: dict) -> None: + """Checks the catalogue item has not been updated. + + :param expected_property_get_data: Dictionary containing the expected property data returned as would be + required for a `PropertySchema`. Does not need mandatory IDs + (e.g. property/unit IDs) as they will be added automatically to check they + are as expected. """ - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + self.get_item(self.item_id) + self.check_get_item_success( + { + **ITEM_GET_DATA_WITH_ALL_PROPERTIES, + "properties": [ + PROPERTY_DATA_BOOLEAN_MANDATORY_FALSE, + expected_property_get_data, + ], + } + ) + E2ETestHelpers.check_created_and_modified_times_not_updated(self._post_response_item, self._get_response_item) + - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH, property_id=str(ObjectId())) +class TestUpdate(UpdateDSL): + """Tests for updating a property at the catalogue category level.""" - # Check - self.check_property_patch_response_failed_with_message(404, "Catalogue category property not found") + def test_partial_update_name(self): + """Test updating the name of a property.""" - def test_update_invalid_property_id(self): - """ - Test updating a property at the catalogue category level when the specified catalogue item id is invalid - """ + property_id = self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_update_data = {"name": "New property name"} + self.patch_property(property_id, property_update_data) - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH, property_id="invalid") + self.check_patch_property_success( + {**CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY, **property_update_data} + ) + self.check_catalogue_category_updated( + {**CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY, **property_update_data} + ) + self.check_catalogue_item_updated({**PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE, **property_update_data}) + self.check_item_updated({**PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE, **property_update_data}) - # Check - self.check_property_patch_response_failed_with_message(404, "Catalogue category property not found") + def test_partial_update_name_to_duplicate(self): + """Test updating the name of a property to conflict with a pre-existing one.""" - def test_updating_property_to_have_duplicate_name(self): - """ - Test updating a property to have a name matching another already existing one - """ + property_id = self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + self.patch_property(property_id, {"name": CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY["name"]}) - # Patch the property - self.patch_property({"name": EXISTING_CATALOGUE_CATEGORY_PROPERTY_POST["name"]}) - self.check_property_patch_response_failed_with_message(422, "Duplicate property name: Property A") + self.check_patch_property_failed_with_detail( + 422, f"Duplicate property name: {CATALOGUE_CATEGORY_PROPERTY_DATA_BOOLEAN_MANDATORY['name']}" + ) - def test_updating_property_allowed_values_from_none_to_value(self): - """ - Test updating a property to have a allowed_values when it was initially None - """ + def test_update_allowed_values_list_adding_value(self): + """Test updating the allowed values list of a property by adding an additional value to it.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_EXPECTED) + property_id, existing_allowed_values = self.post_test_property_and_prerequisites_with_allowed_values_list() - # Patch the property - self.patch_property(CATALOGUE_CATEGORY_PROPERTY_PATCH_ALLOWED_VALUES_ONLY) - self.check_property_patch_response_failed_with_message(422, "Cannot add allowed_values to an existing property") + property_update_data = { + "allowed_values": {**existing_allowed_values, "values": [*existing_allowed_values["values"], 9001]} + } + self.patch_property(property_id, property_update_data) - def test_updating_property_allowed_values_from_value_to_none(self): - """ - Test updating a property to have no allowed_values when it initially had - """ + self.check_patch_property_success( + { + **CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, + **property_update_data, + } + ) + self.check_catalogue_category_updated( + { + **CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, + **property_update_data, + } + ) + self.check_catalogue_item_not_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_NONE) + self.check_item_not_updated(PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_NONE) + + def test_update_allowed_values_list_adding_value_with_different_type(self): + """Test updating the allowed values list of a property by adding an additional value with a different type to + it.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_id, existing_allowed_values = self.post_test_property_and_prerequisites_with_allowed_values_list() - # Patch the property - self.patch_property({"allowed_values": None}) - self.check_property_patch_response_failed_with_message( - 422, "Cannot remove allowed_values from an existing property" + self.patch_property( + property_id, + {"allowed_values": {**existing_allowed_values, "values": [*existing_allowed_values["values"], True]}}, ) - def test_updating_property_removing_allowed_values_list(self): - """ - Test updating a property to remove an element from an allowed values list - """ + self.check_patch_property_failed_with_detail( + 422, "allowed_values of type 'list' must only contain values of the same type as the property itself" + ) + + def test_update_allowed_values_list_adding_duplicate_value(self): + """Test updating the allowed values list of a property by adding a duplicate value to it.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_id, existing_allowed_values = self.post_test_property_and_prerequisites_with_allowed_values_list() - # Patch the property self.patch_property( + property_id, { "allowed_values": { - "type": "list", - # Remove a single value - "values": CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES["allowed_values"]["values"][ - 0:-1 - ], + **existing_allowed_values, + "values": [*existing_allowed_values["values"], existing_allowed_values["values"][0]], } - } + }, ) - self.check_property_patch_response_failed_with_message( - 422, "Cannot modify existing values inside allowed_values of type 'list', you may only add more values" + + self.check_patch_property_failed_with_detail( + 422, f"allowed_values of type 'list' contains a duplicate value: {existing_allowed_values['values'][0]}" ) - def test_updating_property_modifying_allowed_values_list(self): - """ - Test updating a property to modify a value in an allowed values list - """ + def test_update_allowed_values_list_removing_value(self): + """Test updating the allowed values list of a property by removing a value from it.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_id, existing_allowed_values = self.post_test_property_and_prerequisites_with_allowed_values_list() - # Patch the property self.patch_property( + property_id, { "allowed_values": { - "type": "list", - # Change only the last value - "values": [ - *CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES["allowed_values"]["values"][0:-1], - 42, - ], + **existing_allowed_values, + "values": [*existing_allowed_values["values"][1:]], } - } + }, ) - self.check_property_patch_response_failed_with_message( + + self.check_patch_property_failed_with_detail( 422, "Cannot modify existing values inside allowed_values of type 'list', you may only add more values" ) - def test_updating_property_allowed_values_list_adding_with_different_type(self): - """ - Test updating a property to add a value to an allowed values list but with a different type to the rest - """ + def test_update_allowed_values_list_modifying_value(self): + """Test updating the allowed values list of a property by modifying a value within it.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_id, existing_allowed_values = self.post_test_property_and_prerequisites_with_allowed_values_list() - # Patch the property self.patch_property( + property_id, { "allowed_values": { - "type": "list", - # Change only the last value + **existing_allowed_values, "values": [ - *CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES["allowed_values"]["values"], - "different type", + 9001, + *existing_allowed_values["values"][1:], ], } - } + }, ) - self.check_property_patch_response_failed_with_message( - 422, "allowed_values of type 'list' must only contain values of the same type as the property itself" + + self.check_patch_property_failed_with_detail( + 422, "Cannot modify existing values inside allowed_values of type 'list', you may only add more values" ) - def test_updating_property_allowed_values_list_adding_duplicate_value(self): - """ - Test updating a property to add a duplicate value to an allowed values list - """ + def test_update_allowed_values_from_none_to_value(self): + """Test updating the allowed values of a property from None to a value.""" - # Setup by creating a property to update - self.post_catalogue_category_and_items() - self.post_property(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES) - self.check_property_post_response_success(CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES_EXPECTED) + property_id = self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) - # Patch the property self.patch_property( + property_id, { - "allowed_values": { - "type": "list", - # Change only the last value - "values": [ - *CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES["allowed_values"]["values"], - CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES["allowed_values"]["values"][0], - ], - } - } + "allowed_values": CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST[ + "allowed_values" + ], + }, ) - self.check_property_patch_response_failed_with_message( - 422, - "allowed_values of type 'list' contains a duplicate value: " - f"{CATALOGUE_CATEGORY_PROPERTY_POST_MANDATORY_ALLOWED_VALUES['allowed_values']['values'][0]}", + + self.check_patch_property_failed_with_detail(422, "Cannot add allowed_values to an existing property") + + def test_update_allowed_values_from_value_to_none(self): + """Test updating the allowed values list of a property.""" + + property_id = self.post_test_property_and_prerequisites( + CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST ) + + self.patch_property(property_id, {"allowed_values": None}) + + self.check_patch_property_failed_with_detail(422, "Cannot remove allowed_values from an existing property") + + def test_partial_update_with_non_existent_catalogue_category_id(self): + """Test updating a property in a non-existent catalogue category.""" + + property_id = self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) + self.catalogue_category_id = str(ObjectId()) + + self.patch_property(property_id, {}) + + self.check_patch_property_failed_with_detail(404, "Catalogue category not found") + + def test_partial_update_with_catalogue_category_invalid_id(self): + """Test updating a property with an invalid ID.""" + + property_id = self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) + self.catalogue_category_id = "invalid-id" + + self.patch_property(property_id, {}) + + self.check_patch_property_failed_with_detail(404, "Catalogue category not found") + + def test_partial_update_with_non_existent_property_id(self): + """Test updating a non-existent property.""" + + self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) + + self.patch_property(str(ObjectId()), {}) + + self.check_patch_property_failed_with_detail(404, "Catalogue category property not found") + + def test_partial_update_with_invalid_property_id(self): + """Test updating a property with an invalid ID.""" + + self.post_test_property_and_prerequisites(CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY) + + self.patch_property("invalid-id", {}) + + self.check_patch_property_failed_with_detail(404, "Catalogue category property not found") diff --git a/test/e2e/test_catalogue_item.py b/test/e2e/test_catalogue_item.py index 1816d945..66e04f5a 100644 --- a/test/e2e/test_catalogue_item.py +++ b/test/e2e/test_catalogue_item.py @@ -623,7 +623,7 @@ def check_get_catalogue_item_success(self, expected_catalogue_item_get_data: dic """ Checks that a prior call to `get_catalogue_item` gave a successful response with the expected data returned. - :param expected_catalogue_item_get_data: Dictionary containing the expected catalogue tiem data returned as + :param expected_catalogue_item_get_data: Dictionary containing the expected catalogue item data returned as would be required for a `CatalogueItemSchema`. Does not need mandatory IDs (e.g. `manufacturer_id`) as they will be added automatically to check they are as expected. @@ -685,10 +685,10 @@ def get_catalogue_items(self, filters: dict) -> None: self._get_response_catalogue_item = self.test_client.get("/v1/catalogue-items", params=filters) - def post_test_catalogue_items(self) -> list[dict]: + def post_test_catalogue_items_and_prerequisites(self) -> list[dict]: """ - Posts two catalogue items each in a separate catalogue category and returns their expected responses when - returned by the list endpoint. + Posts two catalogue items having first posted the required prerequisite entities. Each catalogue item is in a + separate catalogue category. :return: List of dictionaries containing the expected catalogue item data returned from a get endpoint in the form of a `CatalogueItemSchema`. @@ -739,7 +739,7 @@ def test_list_with_no_filters(self): Posts two catalogue items in different catalogue categories and expects both to be returned. """ - catalogue_items = self.post_test_catalogue_items() + catalogue_items = self.post_test_catalogue_items_and_prerequisites() self.get_catalogue_items(filters={}) self.check_get_catalogue_items_success(catalogue_items) @@ -751,7 +751,7 @@ def test_list_with_catalogue_category_id_filter(self): expecting only the second catalogue item to be returned. """ - catalogue_items = self.post_test_catalogue_items() + catalogue_items = self.post_test_catalogue_items_and_prerequisites() self.get_catalogue_items(filters={"catalogue_category_id": catalogue_items[1]["catalogue_category_id"]}) self.check_get_catalogue_items_success([catalogue_items[1]]) @@ -818,7 +818,7 @@ def post_child_item(self) -> None: } self.test_client.post("/v1/items", json=item_post) - def check_patch_catalogue_item_response_success(self, expected_catalogue_item_get_data: dict) -> None: + def check_patch_catalogue_item_success(self, expected_catalogue_item_get_data: dict) -> None: """ Checks that a prior call to `patch_catalogue_item` gave a successful response with the expected data returned. @@ -875,7 +875,7 @@ def test_partial_update_all_fields_except_ids_or_properties_with_no_children(sel ) self.patch_catalogue_item(catalogue_item_id, CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_NOT_OBSOLETE_NO_PROPERTIES) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_NOT_OBSOLETE_NO_PROPERTIES) def test_partial_update_all_fields_except_ids_or_properties_with_children(self): """Test updating all fields of a catalogue item except any of its `_id` fields or properties when it has @@ -887,7 +887,7 @@ def test_partial_update_all_fields_except_ids_or_properties_with_children(self): self.post_child_item() self.patch_catalogue_item(catalogue_item_id, CATALOGUE_ITEM_DATA_NOT_OBSOLETE_NO_PROPERTIES) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_NOT_OBSOLETE_NO_PROPERTIES) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_NOT_OBSOLETE_NO_PROPERTIES) def test_partial_update_catalogue_category_id_no_properties(self): """Test updating the `catalogue_category_id` of a catalogue item when no properties are involved.""" @@ -898,7 +898,7 @@ def test_partial_update_catalogue_category_id_no_properties(self): new_catalogue_category_id = self.post_catalogue_category(CATALOGUE_CATEGORY_POST_DATA_LEAF_REQUIRED_VALUES_ONLY) self.patch_catalogue_item(catalogue_item_id, {"catalogue_category_id": new_catalogue_category_id}) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) def test_partial_update_catalogue_category_id_with_same_defined_properties(self): """Test updating the `catalogue_category_id` of a catalogue item when both the old and new catalogue category @@ -912,7 +912,7 @@ def test_partial_update_catalogue_category_id_with_same_defined_properties(self) ) self.patch_catalogue_item(catalogue_item_id, {"catalogue_category_id": new_catalogue_category_id}) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES) def test_partial_update_catalogue_category_id_and_properties_with_same_defined_properties(self): """Test updating the `catalogue_category_id` and `properties` of a catalogue item when both the old and new @@ -932,7 +932,7 @@ def test_partial_update_catalogue_category_id_and_properties_with_same_defined_p "properties": CATALOGUE_ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY["properties"], }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": CATALOGUE_ITEM_GET_DATA_WITH_MANDATORY_PROPERTIES_ONLY["properties"], @@ -982,7 +982,7 @@ def test_partial_update_catalogue_category_id_and_properties_with_same_defined_p "properties": CATALOGUE_ITEM_DATA_WITH_ALL_PROPERTIES["properties"][::-1], }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES["properties"][::-1], @@ -1032,7 +1032,7 @@ def test_partial_update_catalogue_category_id_and_properties_with_different_defi "properties": [PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_42], }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_42], @@ -1079,7 +1079,7 @@ def test_partial_update_catalogue_category_id_and_properties_while_removing_all_ catalogue_item_id, {"catalogue_category_id": new_catalogue_category_id, "properties": []}, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [], @@ -1103,7 +1103,7 @@ def test_partial_update_catalogue_category_id_and_properties_with_missing_non_ma "properties": CATALOGUE_ITEM_DATA_WITH_MANDATORY_PROPERTIES_ONLY["properties"], }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY, "properties": CATALOGUE_ITEM_GET_DATA_WITH_MANDATORY_PROPERTIES_ONLY["properties"], @@ -1173,7 +1173,7 @@ def test_partial_update_manufacturer_id_with_no_children(self): new_manufacturer_id = self.post_manufacturer(MANUFACTURER_POST_DATA_ALL_VALUES) self.patch_catalogue_item(catalogue_item_id, {"manufacturer_id": new_manufacturer_id}) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) def test_partial_update_manufacturer_id_with_children(self): """Test updating the `manufacturer_id` of a catalogue item when it has children.""" @@ -1219,7 +1219,7 @@ def test_partial_update_properties_with_no_children(self): ) self.patch_catalogue_item(catalogue_item_id, {"properties": []}) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY) def test_partial_update_properties_with_mandatory_properties_given_none(self): """Test updating the `properties` of a catalogue item to have mandatory properties with a value of `None`.""" @@ -1256,7 +1256,7 @@ def test_partial_update_properties_with_non_mandatory_properties_given_none(self ] }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": CATALOGUE_ITEM_GET_DATA_WITH_MANDATORY_PROPERTIES_ONLY["properties"], @@ -1277,7 +1277,7 @@ def test_partial_update_properties_adding_non_mandatory_property(self): self.patch_catalogue_item( catalogue_item_id, {"properties": CATALOGUE_ITEM_DATA_WITH_ALL_PROPERTIES["properties"]} ) - self.check_patch_catalogue_item_response_success(CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES) + self.check_patch_catalogue_item_success(CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES) def test_partial_update_properties_removing_non_mandatory_property(self): """Test updating the `properties` of a catalogue item to remove a previously defined non-mandatory property.""" @@ -1289,7 +1289,7 @@ def test_partial_update_properties_removing_non_mandatory_property(self): self.patch_catalogue_item( catalogue_item_id, {"properties": CATALOGUE_ITEM_DATA_WITH_ALL_PROPERTIES["properties"][0:2]} ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [ @@ -1388,7 +1388,7 @@ def test_partial_update_properties_with_allowed_values_list(self): ] }, ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [ @@ -1479,7 +1479,7 @@ def test_partial_update_obsolete_replacement_catalogue_item_id(self): self.patch_catalogue_item( catalogue_item_id, {"obsolete_replacement_catalogue_item_id": obsolete_replacement_catalogue_item_id} ) - self.check_patch_catalogue_item_response_success( + self.check_patch_catalogue_item_success( { **CATALOGUE_ITEM_GET_DATA_REQUIRED_VALUES_ONLY, "obsolete_replacement_catalogue_item_id": obsolete_replacement_catalogue_item_id, diff --git a/test/e2e/test_item.py b/test/e2e/test_item.py index 01d6a155..0b420a61 100644 --- a/test/e2e/test_item.py +++ b/test/e2e/test_item.py @@ -675,10 +675,11 @@ def get_items(self, filters: dict) -> None: self._get_response_item = self.test_client.get("/v1/items", params=filters) - def post_test_items(self) -> list[dict]: + def post_test_items_and_prerequisites(self) -> list[dict]: """ - Posts three items. The first two have the same catalogue item but different systems, and the last has a - different catalogue item but the same system as the second catalogue item. + Posts three items having first posted the required prerequisite entities. The first two have the same catalogue + item but different systems, and the last has a different catalogue item but the same system as the second + catalogue item. :return: List of dictionaries containing the expected item data returned from a get endpoint in the form of an `ItemSchema`. In the form [CATALOGUE_ITEM_A_SYSTEM_A, CATALOGUE_ITEM_A_SYSTEM_B, @@ -743,7 +744,7 @@ def test_list_with_no_filters(self): catalogue item but the same system as the second. Expects all three to be returned. """ - items = self.post_test_items() + items = self.post_test_items_and_prerequisites() self.get_items(filters={}) self.check_get_items_success(items) @@ -755,7 +756,7 @@ def test_list_with_system_id_filter(self): catalogue item but the same system as the second. Expects just the latter two systems to be returned. """ - items = self.post_test_items() + items = self.post_test_items_and_prerequisites() self.get_items(filters={"system_id": items[1]["system_id"]}) self.check_get_items_success(items[1:]) @@ -773,7 +774,7 @@ def test_list_with_catalogue_item_id_filter(self): catalogue item but the same system as the second. Expects just the former two systems to be returned. """ - items = self.post_test_items() + items = self.post_test_items_and_prerequisites() self.get_items(filters={"catalogue_item_id": items[0]["catalogue_item_id"]}) self.check_get_items_success(items[0:2]) @@ -791,7 +792,7 @@ def test_list_with_system_id_and_catalogue_item_id_filters(self): catalogue item but the same system as the second. Expects just second item to be returned. """ - items = self.post_test_items() + items = self.post_test_items_and_prerequisites() self.get_items(filters={"system_id": items[2]["system_id"], "catalogue_item_id": items[0]["catalogue_item_id"]}) self.check_get_items_success([items[1]]) @@ -827,14 +828,14 @@ def patch_item(self, item_id: str, item_update_data: dict) -> None: self._patch_response_item = self.test_client.patch(f"/v1/items/{item_id}", json=item_update_data) - def check_patch_item_response_success(self, expected_item_get_data: dict) -> None: + def check_patch_item_success(self, expected_item_get_data: dict) -> None: """ Checks that a prior call to `patch_item` gave a successful response with the expected data returned. Also merges in any properties that were defined in the catalogue item but are not given in the expected data. :param expected_item_get_data: Dictionary containing the expected item data returned as would - be required for a `ItemSchema`. Does not need mandatory IDs + be required for an `ItemSchema`. Does not need mandatory IDs (e.g. `system_id`) as they will be added automatically to check they are as expected. """ @@ -882,7 +883,7 @@ def test_partial_update_all_fields_except_ids_or_properties(self): item_id = self.post_item_and_prerequisites_no_properties(ITEM_DATA_REQUIRED_VALUES_ONLY) self.patch_item(item_id, ITEM_DATA_ALL_VALUES_NO_PROPERTIES) - self.check_patch_item_response_success(ITEM_GET_DATA_ALL_VALUES_NO_PROPERTIES) + self.check_patch_item_success(ITEM_GET_DATA_ALL_VALUES_NO_PROPERTIES) def test_partial_update_catalogue_item_id(self): """Test updating the `catalogue_item_id` of an item.""" @@ -899,7 +900,7 @@ def test_partial_update_system_id(self): new_system_id = self.post_system(SYSTEM_POST_DATA_ALL_VALUES_NO_PARENT) self.patch_item(item_id, {"system_id": new_system_id}) - self.check_patch_item_response_success(ITEM_GET_DATA_REQUIRED_VALUES_ONLY) + self.check_patch_item_success(ITEM_GET_DATA_REQUIRED_VALUES_ONLY) def test_partial_update_system_id_with_non_existent_id(self): """Test updating the `system_id` of an item to a non-existent system.""" @@ -924,7 +925,7 @@ def test_partial_update_usage_status_id(self): new_usage_status_id = self.post_usage_status(USAGE_STATUS_POST_DATA_NEW) self.patch_item(item_id, {"usage_status_id": new_usage_status_id}) - self.check_patch_item_response_success( + self.check_patch_item_success( {**ITEM_GET_DATA_REQUIRED_VALUES_ONLY, "usage_status": USAGE_STATUS_POST_DATA_NEW["value"]} ) @@ -951,7 +952,7 @@ def test_partial_update_properties_with_no_properties_provided(self): item_id = self.post_item_and_prerequisites_with_properties(ITEM_DATA_WITH_ALL_PROPERTIES) self.patch_item(item_id, {"properties": []}) - self.check_patch_item_response_success({**ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": []}) + self.check_patch_item_success({**ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": []}) def test_partial_update_properties_with_some_properties_provided(self): """Test updating the `properties` of an item to override some of the catalogue item properties.""" @@ -960,7 +961,7 @@ def test_partial_update_properties_with_some_properties_provided(self): item_id = self.post_item_and_prerequisites_with_properties({**ITEM_DATA_WITH_ALL_PROPERTIES, "properties": []}) self.patch_item(item_id, {"properties": ITEM_GET_DATA_WITH_ALL_PROPERTIES["properties"][1::]}) - self.check_patch_item_response_success( + self.check_patch_item_success( {**ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": ITEM_GET_DATA_WITH_ALL_PROPERTIES["properties"][1::]} ) @@ -971,7 +972,7 @@ def test_partial_update_properties_with_all_properties_provided(self): item_id = self.post_item_and_prerequisites_with_properties({**ITEM_DATA_WITH_ALL_PROPERTIES, "properties": []}) self.patch_item(item_id, {"properties": ITEM_GET_DATA_WITH_ALL_PROPERTIES["properties"]}) - self.check_patch_item_response_success(ITEM_GET_DATA_WITH_ALL_PROPERTIES) + self.check_patch_item_success(ITEM_GET_DATA_WITH_ALL_PROPERTIES) def test_partial_update_properties_with_mandatory_property_given_none(self): """Test updating the `properties` of an item to have a mandatory property with a value of `None`.""" @@ -991,7 +992,7 @@ def test_partial_update_properties_with_non_mandatory_property_given_none(self): item_id = self.post_item_and_prerequisites_with_properties(ITEM_DATA_WITH_ALL_PROPERTIES) self.patch_item(item_id, {"properties": [{**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1, "value": None}]}) - self.check_patch_item_response_success( + self.check_patch_item_success( { **ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [{**PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1, "value": None}], @@ -1075,7 +1076,7 @@ def test_partial_update_properties_with_allowed_values_list(self): ] }, ) - self.check_patch_item_response_success( + self.check_patch_item_success( { **ITEM_GET_DATA_WITH_ALL_PROPERTIES, "properties": [ diff --git a/test/e2e/test_system.py b/test/e2e/test_system.py index 30956f90..2520920c 100644 --- a/test/e2e/test_system.py +++ b/test/e2e/test_system.py @@ -432,7 +432,7 @@ def patch_system(self, system_id: str, system_patch_data: dict) -> None: self._patch_response_system = self.test_client.patch(f"/v1/systems/{system_id}", json=system_patch_data) - def check_patch_system_response_success(self, expected_system_get_data: dict) -> None: + def check_patch_system_success(self, expected_system_get_data: dict) -> None: """ Checks that a prior call to `patch_system` gave a successful response with the expected data returned. @@ -467,7 +467,7 @@ def test_partial_update_all_fields(self): system_id = self.post_system(SYSTEM_POST_DATA_REQUIRED_VALUES_ONLY) self.patch_system(system_id, SYSTEM_POST_DATA_ALL_VALUES_NO_PARENT) - self.check_patch_system_response_success(SYSTEM_GET_DATA_ALL_VALUES_NO_PARENT) + self.check_patch_system_success(SYSTEM_GET_DATA_ALL_VALUES_NO_PARENT) def test_partial_update_parent_id(self): """Test updating the `parent_id` of a system.""" @@ -476,7 +476,7 @@ def test_partial_update_parent_id(self): system_id = self.post_system(SYSTEM_POST_DATA_ALL_VALUES_NO_PARENT) self.patch_system(system_id, {"parent_id": parent_id}) - self.check_patch_system_response_success({**SYSTEM_GET_DATA_ALL_VALUES_NO_PARENT, "parent_id": parent_id}) + self.check_patch_system_success({**SYSTEM_GET_DATA_ALL_VALUES_NO_PARENT, "parent_id": parent_id}) def test_partial_update_parent_id_to_one_with_a_duplicate_name(self): """Test updating the `parent_id` of a system so that its name conflicts with one already in that other @@ -530,7 +530,7 @@ def test_partial_update_name_capitalisation(self): system_id = self.post_system({**SYSTEM_POST_DATA_REQUIRED_VALUES_ONLY, "name": "Test system"}) self.patch_system(system_id, {"name": "Test System"}) - self.check_patch_system_response_success( + self.check_patch_system_success( {**SYSTEM_GET_DATA_REQUIRED_VALUES_ONLY, "name": "Test System", "code": "test-system"} ) diff --git a/test/mock_data.py b/test/mock_data.py index 79bb14c3..01718dfe 100644 --- a/test/mock_data.py +++ b/test/mock_data.py @@ -157,6 +157,7 @@ **CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY, "id": ANY, "allowed_values": None, + "unit": None, } # Number, Non Mandatory, Allowed values list @@ -168,6 +169,19 @@ "allowed_values": {"type": "list", "values": [1, 2, 3]}, } +CATALOGUE_CATEGORY_PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST = { + **CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST, + "id": ANY, + "unit": None, +} + +# Number, Mandatory, No unit +CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_MANDATORY = { + "name": "Mandatory Number Property", + "type": "number", + "mandatory": True, +} + # String, Mandatory CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY = { @@ -176,6 +190,13 @@ "mandatory": True, } +CATALOGUE_CATEGORY_PROPERTY_GET_DATA_STRING_MANDATORY = { + **CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY, + "id": ANY, + "allowed_values": None, + "unit": None, +} + # String, Non Mandatory, Allowed values list CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST = { @@ -292,7 +313,7 @@ "properties": [], } -# Leaf, No parent, Properties - with mm unit +# Leaf, No parent, Properties, mm unit # Put _MM at end to signify what units this data would require CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM = { @@ -349,6 +370,13 @@ PROPERTY_GET_DATA_BOOLEAN_MANDATORY_TRUE = {**PROPERTY_DATA_BOOLEAN_MANDATORY_TRUE} +# Number, Non Mandatory, Allowed Values List, None + +PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_NONE = { + "name": CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST["name"], + "value": None, +} + # Number, Non Mandatory, Allowed Values List, 1 PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_1 = { @@ -371,7 +399,7 @@ **PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_2 } -# Number, Non Mandatory, 1 +# Number, Non Mandatory, mm unit, 1 PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1 = { "name": CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT["name"], @@ -380,7 +408,7 @@ PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1 = {**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1} -# Number, Non Mandatory, 42 +# Number, Non Mandatory, mm unit, 42 PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_42 = { **PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_1, @@ -389,7 +417,7 @@ PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_42 = {**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_42} -# Number, Non Mandatory, None +# Number, Non Mandatory, mm unit, None PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE = { "name": CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT["name"], @@ -398,12 +426,20 @@ PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE = {**PROPERTY_DATA_NUMBER_NON_MANDATORY_WITH_MM_UNIT_NONE} +# Number, Non Mandatory, No unit, None +PROPERTY_GET_DATA_NUMBER_NON_MANDATORY_NONE = { + "name": CATALOGUE_CATEGORY_PROPERTY_DATA_NUMBER_NON_MANDATORY["name"], + "value": None, +} + # String, Mandatory, text PROPERTY_DATA_STRING_MANDATORY_TEXT = { "name": CATALOGUE_CATEGORY_PROPERTY_DATA_STRING_MANDATORY["name"], "value": "text", } +PROPERTY_GET_DATA_STRING_MANDATORY_TEXT = {**PROPERTY_DATA_STRING_MANDATORY_TEXT} + # String, Non Mandatory, Allowed Values List, value1 PROPERTY_DATA_STRING_NON_MANDATORY_WITH_ALLOWED_VALUES_LIST_VALUE1 = { @@ -442,6 +478,7 @@ # This is the base catalogue category to be used in tests with properties BASE_CATALOGUE_CATEGORY_DATA_WITH_PROPERTIES_MM = CATALOGUE_CATEGORY_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM BASE_CATALOGUE_CATEGORY_IN_DATA_WITH_PROPERTIES_MM = CATALOGUE_CATEGORY_IN_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM +BASE_CATALOGUE_CATEGORY_GET_DATA_WITH_PROPERTIES_MM = CATALOGUE_CATEGORY_GET_DATA_LEAF_NO_PARENT_WITH_PROPERTIES_MM # No properties