diff --git a/inventory_management_system_api/core/exceptions.py b/inventory_management_system_api/core/exceptions.py index 52fc91ca..964beb7a 100644 --- a/inventory_management_system_api/core/exceptions.py +++ b/inventory_management_system_api/core/exceptions.py @@ -57,6 +57,10 @@ class ChildrenElementsExistError(DatabaseError): """ +class PartOfCatalogueItemError(DatabaseError): + """ + Exception raised when attempting to delete a manufacturer that is a part of a catalogue item + """ class DatabaseIntegrityError(DatabaseError): """ Exception raised when something is found in the database that shouldn't have been diff --git a/inventory_management_system_api/models/catalogue_item.py b/inventory_management_system_api/models/catalogue_item.py index 63adda10..7b88dd39 100644 --- a/inventory_management_system_api/models/catalogue_item.py +++ b/inventory_management_system_api/models/catalogue_item.py @@ -3,21 +3,11 @@ """ from typing import Optional, List, Any -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Field, HttpUrl, validator from inventory_management_system_api.models.custom_object_id_data_types import CustomObjectIdField, StringObjectIdField -class Manufacturer(BaseModel): - """ - Model representing a catalogue item manufacturer. - """ - - name: str - address: str - web_url: str - - class Property(BaseModel): """ Model representing a catalogue item property. @@ -28,6 +18,14 @@ class Property(BaseModel): unit: Optional[str] = None +class Manufacturer(BaseModel): + """Input database model for a manufacturer""" + + name: str + url: HttpUrl + address: str + + class CatalogueItemIn(BaseModel): """ Input database model for a catalogue item. @@ -37,6 +35,8 @@ class CatalogueItemIn(BaseModel): name: str description: str properties: List[Property] = [] + # pylint: disable=fixme + # TODO - Change from manufacturer to manufacturer id manufacturer: Manufacturer @validator("properties", pre=True, always=True) diff --git a/inventory_management_system_api/repositories/manufacturer.py b/inventory_management_system_api/repositories/manufacturer.py index d3fb77c2..af3a0302 100644 --- a/inventory_management_system_api/repositories/manufacturer.py +++ b/inventory_management_system_api/repositories/manufacturer.py @@ -10,7 +10,11 @@ from inventory_management_system_api.core.custom_object_id import CustomObjectId from inventory_management_system_api.core.database import get_database -from inventory_management_system_api.core.exceptions import DuplicateRecordError, MissingRecordError +from inventory_management_system_api.core.exceptions import ( + DuplicateRecordError, + MissingRecordError, + PartOfCatalogueItemError, +) from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut @@ -28,6 +32,7 @@ def __init__(self, database: Database = Depends(get_database)) -> None: self._database = database self._collection: Collection = self._database.manufacturer + self._catalogue_item_collection: Collection = self._database.catalogue_items def create(self, manufacturer: ManufacturerIn) -> ManufacturerOut: """ @@ -104,6 +109,23 @@ def update(self, manufacturer_id: str, manufacturer: ManufacturerIn) -> Manufact manufacturer = self.get(str(manufacturer_id)) return manufacturer + def delete(self, manufacturer_id: str) -> None: + """ + Delete a manufacturer by its ID from MongoDB database. + Checks if manufactuer is a part of an item, and does not delete if it is + + :param manufacturer_id: The ID of the manufacturer to delete + :raises ExistIn + """ + manufacturer_id = CustomObjectId(manufacturer_id) + if self.is_manufacturer_in_catalogue_item(str(manufacturer_id)): + raise PartOfCatalogueItemError("The specified manufacturer is a part of a Catalogue Item") + + logger.info("Deleting manufacturer with ID %s from the database", manufacturer_id) + result = self._collection.delete_one({"_id": manufacturer_id}) + if result.deleted_count == 0: + raise MissingRecordError(f"No manufacturer found with ID: {str(manufacturer_id)}") + def _is_duplicate_manufacturer(self, code: str) -> bool: """ Check if manufacturer with the same url already exists in the manufacturer collection @@ -114,3 +136,13 @@ def _is_duplicate_manufacturer(self, code: str) -> bool: logger.info("Checking if manufacturer with code '%s' already exists", code) count = self._collection.count_documents({"code": code}) return count > 0 + + def is_manufacturer_in_catalogue_item(self, manufacturer_id: str) -> bool: + """Checks to see if any of the documents in the database have a specific manufactuer id + + :param manufacturer_id: The ID of the manufacturer that is looked for + :return Returns True if 1 or more documents have the manufacturer ID, false if none do + """ + manufacturer_id = CustomObjectId(manufacturer_id) + count = self._catalogue_item_collection.count_documents({"manufacturer_id": manufacturer_id}) + return count > 0 diff --git a/inventory_management_system_api/routers/v1/manufacturer.py b/inventory_management_system_api/routers/v1/manufacturer.py index 2386ab44..4bd017a2 100644 --- a/inventory_management_system_api/routers/v1/manufacturer.py +++ b/inventory_management_system_api/routers/v1/manufacturer.py @@ -9,6 +9,7 @@ DuplicateRecordError, InvalidObjectIdError, MissingRecordError, + PartOfCatalogueItemError, ) from inventory_management_system_api.schemas.manufacturer import ( @@ -110,3 +111,26 @@ def edit_manufacturer( message = "A manufacturer with the same name has been found" logger.exception(message) raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=message) from exc + + +@router.delete( + path="/{manufacturer_id}", + summary="Delete a manufacturer by its ID", + response_description="Manufacturer deleted successfully", + status_code=status.HTTP_204_NO_CONTENT, +) +def delete_manufacturer(manufacturer_id: str, manufacturer_service: ManufacturerService = Depends()) -> None: + # pylint: disable=missing-function-docstring + logger.info("Deleting manufacturer with ID: %s", manufacturer_id) + try: + manufacturer_service.delete(manufacturer_id) + except (MissingRecordError, InvalidObjectIdError) as exc: + logger.exception("The specified manufacturer does not exist") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="The specified manufacturer does not exist" + ) from exc + except PartOfCatalogueItemError as exc: + logger.exception("The specified manufacturer is a part of a Catalogue Item") + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="The specified manufacturer is a part of a Catalogue Item" + ) from exc diff --git a/inventory_management_system_api/schemas/catalogue_item.py b/inventory_management_system_api/schemas/catalogue_item.py index 4e6d2b48..ec3ee849 100644 --- a/inventory_management_system_api/schemas/catalogue_item.py +++ b/inventory_management_system_api/schemas/catalogue_item.py @@ -6,14 +6,12 @@ from pydantic import BaseModel, Field, HttpUrl -class ManufacturerSchema(BaseModel): - """ - Schema model for a catalogue item manufacturer creation request. - """ +class ManufacturerPostRequestSchema(BaseModel): + """Schema model for a manufacturer creation request""" - name: str = Field(description="The name of the manufacturer") - address: str = Field(description="The address of the manufacturer") - web_url: HttpUrl = Field(description="The website URL of the manufacturer") + name: str + url: HttpUrl + address: str class PropertyPostRequestSchema(BaseModel): @@ -44,7 +42,9 @@ class CatalogueItemPostRequestSchema(BaseModel): name: str = Field(description="The name of the catalogue item") description: str = Field(description="The catalogue item description") properties: Optional[List[PropertyPostRequestSchema]] = Field(description="The catalogue item properties") - manufacturer: ManufacturerSchema = Field(description="The details of the manufacturer") + # pylint: disable=fixme + # TODO - Change from manufacturer to manufacturer id + manufacturer: ManufacturerPostRequestSchema = Field(description="The details of the manufacturer") class CatalogueItemPatchRequestSchema(CatalogueItemPostRequestSchema): @@ -57,7 +57,9 @@ class CatalogueItemPatchRequestSchema(CatalogueItemPostRequestSchema): ) name: Optional[str] = Field(description="The name of the catalogue item") description: Optional[str] = Field(description="The catalogue item description") - manufacturer: Optional[ManufacturerSchema] = Field(description="The details of the manufacturer") + # pylint: disable=fixme + # TODO - Change from manufacturer to manufacturer id + manufacturer: Optional[ManufacturerPostRequestSchema] = Field(description="The details of the manufacturer") class CatalogueItemSchema(CatalogueItemPostRequestSchema): diff --git a/inventory_management_system_api/services/manufacturer.py b/inventory_management_system_api/services/manufacturer.py index e8c21e2e..f36daf26 100644 --- a/inventory_management_system_api/services/manufacturer.py +++ b/inventory_management_system_api/services/manufacturer.py @@ -8,7 +8,6 @@ from fastapi import Depends from inventory_management_system_api.core.exceptions import MissingRecordError from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut - from inventory_management_system_api.repositories.manufacturer import ManufacturerRepo from inventory_management_system_api.schemas.manufacturer import ( ManufacturerPatchRequstSchema, @@ -21,7 +20,10 @@ class ManufacturerService: """Service for managing manufacturers""" - def __init__(self, manufacturer_repository: ManufacturerRepo = Depends(ManufacturerRepo)) -> None: + def __init__( + self, + manufacturer_repository: ManufacturerRepo = Depends(ManufacturerRepo), + ) -> None: """ Initialise the manufacturer service with a ManufacturerRepo @@ -91,3 +93,12 @@ def update(self, manufacturer_id: str, manufacturer: ManufacturerPatchRequstSche logger.info(stored_manufacturer.address) return self._manufacturer_repository.update(manufacturer_id, ManufacturerIn(**stored_manufacturer.dict())) + + def delete(self, manufacturer_id: str) -> None: + """ + Delete a manufacturer by its ID + + :param manufacturer_id: The ID of the manufacturer to delete + + """ + return self._manufacturer_repository.delete(manufacturer_id) diff --git a/test/e2e/test_catalogue_category.py b/test/e2e/test_catalogue_category.py index a1ba4b8f..957cf6c5 100644 --- a/test/e2e/test_catalogue_category.py +++ b/test/e2e/test_catalogue_category.py @@ -319,9 +319,10 @@ def test_delete_catalogue_category_with_child_catalogue_items(test_client): "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } + test_client.post("/v1/catalogue-items", json=catalogue_item_post) response = test_client.delete(f"/v1/catalogue-categories/{catalogue_category_id}") @@ -574,7 +575,7 @@ def test_partial_update_catalogue_category_change_valid_when_has_child_catalogue "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } test_client.post("/v1/catalogue-items", json=catalogue_item_post) @@ -694,7 +695,7 @@ def test_partial_update_catalogue_category_change_from_leaf_to_non_leaf_has_chil "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } test_client.post("/v1/catalogue-items", json=catalogue_item_post) @@ -819,7 +820,7 @@ def test_partial_update_catalogue_category_change_parent_id_has_child_catalogue_ "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } test_client.post("/v1/catalogue-items", json=catalogue_item_post) @@ -1010,7 +1011,7 @@ def test_partial_update_catalogue_category_change_catalogue_item_properties_has_ "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } test_client.post("/v1/catalogue-items", json=catalogue_item_post) diff --git a/test/e2e/test_catalogue_item.py b/test/e2e/test_catalogue_item.py index 303b503d..40834ed5 100644 --- a/test/e2e/test_catalogue_item.py +++ b/test/e2e/test_catalogue_item.py @@ -44,7 +44,7 @@ def get_catalogue_item_a_dict(catalogue_category_id: str) -> Dict: "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } @@ -66,7 +66,7 @@ def get_catalogue_item_b_dict(catalogue_category_id: str) -> Dict: "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } @@ -831,7 +831,7 @@ def test_partial_update_catalogue_item_change_manufacturer(test_client): "manufacturer": { "name": "Manufacturer B", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-b.co.uk", + "url": "https://www.manufacturer-b.co.uk", } } response = test_client.patch(f"/v1/catalogue-items/{response.json()['id']}", json=catalogue_item_patch) diff --git a/test/e2e/test_manufacturer.py b/test/e2e/test_manufacturer.py index e2331738..fa6399c8 100644 --- a/test/e2e/test_manufacturer.py +++ b/test/e2e/test_manufacturer.py @@ -186,3 +186,112 @@ def test_update_with_nonexistent_id(test_client): assert response.status_code == 422 assert response.json()["detail"] == "The specified manufacturer does not exist" + + +def test_update_duplicate_name(test_client): + """Test updating a manufacturer with a duplicate name""" + manufacturer_post = { + "name": "Manufacturer A", + "url": "http://example.com", + "address": "Street A", + } + + response = test_client.post("/v1/manufacturer", json=manufacturer_post) + + manufacturer_patch = { + "name": "Manufacturer A", + "url": "http://test.co.uk", + "address": "Street B", + } + response = test_client.patch(f"/v1/manufacturer/{response.json()['id']}", json=manufacturer_patch) + + assert response.status_code == 409 + assert response.json()["detail"] == "A manufacturer with the same name has been found" + + +def test_delete(test_client): + """Test deleting a manufacturer""" + manufacturer_post = { + "name": "Manufacturer A", + "url": "http://example.com", + "address": "Street A", + } + + response = test_client.post("/v1/manufacturer", json=manufacturer_post) + manufacturer = response.json() + + response = test_client.delete(f"/v1/manufacturer/{manufacturer['id']}") + assert response.status_code == 204 + + +def test_delete_with_an_invalid_id(test_client): + """Test trying to delete a manufacturer with an invalid ID""" + manufacturer_post = { + "name": "Manufacturer A", + "url": "http://example.com", + "address": "Street A", + } + test_client.post("/v1/manufacturer", json=manufacturer_post) + + response = test_client.delete("/v1/manufacturer/invalid") + + assert response.status_code == 404 + assert response.json()["detail"] == "The specified manufacturer does not exist" + + +def test_delete_with_a_nonexistent_id(test_client): + """Test trying to delete a manufacturer with a non-existent ID""" + manufacturer_post = { + "name": "Manufacturer A", + "url": "http://example.com", + "address": "Street A", + } + test_client.post("/v1/manufacturer", json=manufacturer_post) + + response = test_client.delete(f"/v1/manufacturer/{str(ObjectId())}") + + assert response.status_code == 404 + assert response.json()["detail"] == "The specified manufacturer does not exist" + + +def test_delete_manufacturer_that_is_a_part_of_catalogue_item(): + """Test trying to delete a manufacturer that is a part of a Catalogue Item""" + # pylint: disable=fixme + # TODO - Uncomment test when catalogue item logic changes back to using manufacturer Id + + # manufacturer_post = { + # "name": "Manufacturer A", + # "url": "http://example.com", + # "address": "Street A", + # } + # response = test_client.post("/v1/manufacturer", json=manufacturer_post) + # manufacturer_id = response.json()["id"] + # # pylint: disable=duplicate-code + # catalogue_category_post = { + # "name": "Category A", + # "is_leaf": True, + # "catalogue_item_properties": [ + # {"name": "Property A", "type": "number", "unit": "mm", "mandatory": False}, + # {"name": "Property B", "type": "boolean", "mandatory": True}, + # ], + # } + # # pylint: enable=duplicate-code + # response = test_client.post("/v1/catalogue-categories", json=catalogue_category_post) + + # # pylint: disable=duplicate-code + # catalogue_category_id = response.json()["id"] + + # catalogue_item_post = { + # "catalogue_category_id": catalogue_category_id, + # "name": "Catalogue Item A", + # "description": "This is Catalogue Item A", + # "properties": [{"name": "Property B", "value": False}], + # "manufacturer": manufacturer_post, + # } + # # pylint: enable=duplicate-code + # test_client.post("/v1/catalogue-items", json=catalogue_item_post) + + # response = test_client.delete(f"/v1/manufacturer/{manufacturer_id}") + + # assert response.status_code == 409 + # assert response.json()["detail"] == "The specified manufacturer is a part of a Catalogue Item" diff --git a/test/unit/repositories/conftest.py b/test/unit/repositories/conftest.py index fd7dcbf1..9471a952 100644 --- a/test/unit/repositories/conftest.py +++ b/test/unit/repositories/conftest.py @@ -61,6 +61,8 @@ def fixture_manufacturer_repository(database_mock: Mock) -> ManufacturerRepo: Fixture to create ManufacturerRepo instance """ return ManufacturerRepo(database_mock) + + @pytest.fixture(name="system_repository") def fixture_system_repository(database_mock: Mock) -> SystemRepo: """ @@ -164,6 +166,7 @@ def mock_update_one(collection_mock: Mock) -> None: update_one_result_mock.acknowledged = True collection_mock.insert_one.return_value = update_one_result_mock + @pytest.fixture(name="test_helpers") def fixture_test_helpers() -> Type[RepositoryTestHelpers]: """ diff --git a/test/unit/repositories/test_catalogue_item.py b/test/unit/repositories/test_catalogue_item.py index 1432ab9f..48373cae 100644 --- a/test/unit/repositories/test_catalogue_item.py +++ b/test/unit/repositories/test_catalogue_item.py @@ -35,7 +35,9 @@ def test_create(test_helpers, database_mock, catalogue_item_repository): Property(name="Property C", value="20x15x10", unit="cm"), ], manufacturer=Manufacturer( - name="Manufacturer A", address="1 Address, City, Country, Postcode", web_url="www.manufacturer-a.co.uk" + name="Manufacturer A", + address="1 Address, City, Country, Postcode", + url="https://www.manufacturer-a.co.uk", ), ) # pylint: enable=duplicate-code @@ -159,7 +161,9 @@ def test_get(test_helpers, database_mock, catalogue_item_repository): Property(name="Property C", value="20x15x10", unit="cm"), ], manufacturer=Manufacturer( - name="Manufacturer A", address="1 Address, City, Country, Postcode", web_url="www.manufacturer-a.co.uk" + name="Manufacturer A", + address="1 Address, City, Country, Postcode", + url="https://www.manufacturer-a.co.uk", ), ) # pylint: enable=duplicate-code @@ -229,7 +233,9 @@ def test_list(test_helpers, database_mock, catalogue_item_repository): Property(name="Property C", value="20x15x10", unit="cm"), ], manufacturer=Manufacturer( - name="Manufacturer A", address="1 Address, City, Country, Postcode", web_url="www.manufacturer-a.co.uk" + name="Manufacturer A", + address="1 Address, City, Country, Postcode", + url="https://www.manufacturer-a.co.uk", ), ) @@ -240,7 +246,9 @@ def test_list(test_helpers, database_mock, catalogue_item_repository): description="This is Catalogue Item B", properties=[Property(name="Property A", value=True)], manufacturer=Manufacturer( - name="Manufacturer A", address="1 Address, City, Country, Postcode", web_url="www.manufacturer-a.co.uk" + name="Manufacturer A", + address="1 Address, City, Country, Postcode", + url="https://www.manufacturer-a.co.uk", ), ) # pylint: enable=duplicate-code @@ -292,7 +300,9 @@ def test_list_with_catalogue_category_id_filter(test_helpers, database_mock, cat Property(name="Property C", value="20x15x10", unit="cm"), ], manufacturer=Manufacturer( - name="Manufacturer A", address="1 Address, City, Country, Postcode", web_url="www.manufacturer-a.co.uk" + name="Manufacturer A", + address="1 Address, City, Country, Postcode", + url="https://www.manufacturer-a.co.uk", ), ) @@ -371,7 +381,7 @@ def test_update(test_helpers, database_mock, catalogue_item_repository): "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } # pylint: enable=duplicate-code diff --git a/test/unit/repositories/test_manufacturer.py b/test/unit/repositories/test_manufacturer.py index 8f0657bb..6d8f4074 100644 --- a/test/unit/repositories/test_manufacturer.py +++ b/test/unit/repositories/test_manufacturer.py @@ -10,6 +10,7 @@ DuplicateRecordError, InvalidObjectIdError, MissingRecordError, + PartOfCatalogueItemError, ) from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut @@ -298,3 +299,65 @@ def test_update_with_nonexistent_id(test_helpers, database_mock, manufacturer_re with pytest.raises(MissingRecordError) as exc: manufacturer_repository.update(manufacturer_id, updated_manufacturer) assert str(exc.value) == "The specified manufacturer does not exist" + + +def update_with_duplicate_name(test_helpers, database_mock, manufacturer_repository): + """Test trying to update a manufacturer with a duplicate name""" + # pylint: disable=duplicate-code + updated_manufacturer = ManufacturerIn( + name="Manufacturer B", + code="manufacturer-b", + url="http://testUrl.co.uk", + address="1 Example street", + ) + # pylint: enable=duplicate-code + manufacturer_id = str(ObjectId()) + test_helpers.mock_count_documents(database_mock.manufacturer, 1) + + with pytest.raises(DuplicateRecordError) as exc: + manufacturer_repository.update(manufacturer_id, updated_manufacturer) + assert str(exc.value) == "The specified manufacturer does not exist" + + +def test_delete(test_helpers, database_mock, manufacturer_repository): + """Test trying to delete a manufacturer""" + manufacturer_id = str(ObjectId()) + + test_helpers.mock_delete_one(database_mock.manufacturer, 1) + test_helpers.mock_count_documents(database_mock.catalogue_items, 0) + manufacturer_repository.delete(manufacturer_id) + + database_mock.manufacturer.delete_one.assert_called_once_with({"_id": CustomObjectId(manufacturer_id)}) + + +def test_delete_with_an_invalid_id(manufacturer_repository): + """Test trying to delete a manufacturer with an invalid ID""" + manufacturer_id = "invalid" + + with pytest.raises(InvalidObjectIdError) as exc: + manufacturer_repository.delete(manufacturer_id) + assert str(exc.value) == "Invalid ObjectId value 'invalid'" + + +def test_delete_with_a_nonexistent_id(test_helpers, database_mock, manufacturer_repository): + """Test trying to delete a manufacturer with a non-existent ID""" + manufacturer_id = str(ObjectId()) + + test_helpers.mock_delete_one(database_mock.manufacturer, 0) + test_helpers.mock_count_documents(database_mock.catalogue_items, 0) + + with pytest.raises(MissingRecordError) as exc: + manufacturer_repository.delete(manufacturer_id) + assert str(exc.value) == f"No manufacturer found with ID: {manufacturer_id}" + database_mock.manufacturer.delete_one.assert_called_once_with({"_id": CustomObjectId(manufacturer_id)}) + + +def test_delete_manufacturer_that_is_part_of_an_catalogue_item(test_helpers, database_mock, manufacturer_repository): + """Test trying to delete a manufacturer that is part of a Catalogue Items""" + manufacturer_id = str(ObjectId()) + + test_helpers.mock_count_documents(database_mock.catalogue_items, 1) + + with pytest.raises(PartOfCatalogueItemError) as exc: + manufacturer_repository.delete(manufacturer_id) + assert str(exc.value) == "The specified manufacturer is a part of a Catalogue Item" diff --git a/test/unit/services/conftest.py b/test/unit/services/conftest.py index 32c3ac68..9531b425 100644 --- a/test/unit/services/conftest.py +++ b/test/unit/services/conftest.py @@ -15,6 +15,7 @@ from inventory_management_system_api.schemas.breadcrumbs import BreadcrumbsGetSchema from inventory_management_system_api.services.catalogue_category import CatalogueCategoryService from inventory_management_system_api.services.catalogue_item import CatalogueItemService +from inventory_management_system_api.services.manufacturer import ManufacturerService from inventory_management_system_api.services.system import SystemService @@ -47,6 +48,7 @@ def fixture_manufacturer_repository_mock() -> Mock: """ return Mock(ManufacturerRepo) + @pytest.fixture(name="system_repository_mock") def fixture_system_repository_mock() -> Mock: """ @@ -83,6 +85,17 @@ def fixture_catalogue_item_service( return CatalogueItemService(catalogue_item_repository_mock, catalogue_category_repository_mock) +@pytest.fixture(name="manufacturer_service") +def fixture_manufacturer_service(manufacturer_repository_mock: Mock) -> ManufacturerService: + """ + Fixture to create a `ManufacturerService` instance with a mocked `ManufacturerRepo` + + :param: manufacturer_repository_mock: Mocked `ManufacturerRepo` instance. + :return: `ManufacturerService` instance with mocked dependency + """ + return ManufacturerService(manufacturer_repository_mock) + + @pytest.fixture(name="system_service") def fixture_system_service(system_repository_mock: Mock) -> SystemService: """ diff --git a/test/unit/services/test_catalogue_item.py b/test/unit/services/test_catalogue_item.py index d4e4c46b..3e0db937 100644 --- a/test/unit/services/test_catalogue_item.py +++ b/test/unit/services/test_catalogue_item.py @@ -16,14 +16,13 @@ from inventory_management_system_api.models.catalogue_category import CatalogueCategoryOut, CatalogueItemProperty from inventory_management_system_api.models.catalogue_item import ( CatalogueItemOut, + Manufacturer, Property, CatalogueItemIn, - Manufacturer, ) from inventory_management_system_api.schemas.catalogue_item import ( PropertyPostRequestSchema, CatalogueItemPostRequestSchema, - ManufacturerSchema, CatalogueItemPatchRequestSchema, ) @@ -52,7 +51,7 @@ def test_create( manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), ) @@ -130,13 +129,14 @@ def test_create_with_nonexistent_catalogue_category_id( PropertyPostRequestSchema(name="Property B", value=False), PropertyPostRequestSchema(name="Property C", value="20x15x10"), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), - ) + ), ) + assert str(exc.value) == f"No catalogue category found with ID: {catalogue_category_id}" catalogue_category_repository_mock.get.assert_called_once_with(catalogue_category_id) @@ -176,13 +176,14 @@ def test_create_in_non_leaf_catalogue_category( PropertyPostRequestSchema(name="Property B", value=False), PropertyPostRequestSchema(name="Property C", value="20x15x10"), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), - ) + ), ) + assert str(exc.value) == "Cannot add catalogue item to a non-leaf catalogue category" catalogue_category_repository_mock.get.assert_called_once_with(catalogue_category.id) @@ -205,9 +206,10 @@ def test_create_without_properties( manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), ) + # pylint: enable=duplicate-code # Mock `get` to return the catalogue category @@ -288,13 +290,14 @@ def test_create_with_missing_mandatory_properties( properties=[ PropertyPostRequestSchema(name="Property C", value="20x15x10"), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), - ) + ), ) + assert ( str(exc.value) == f"Missing mandatory catalogue item property: '{catalogue_category.catalogue_item_properties[1].name}'" @@ -342,13 +345,14 @@ def test_create_with_with_invalid_value_type_for_string_property( PropertyPostRequestSchema(name="Property B", value=False), PropertyPostRequestSchema(name="Property C", value=True), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), - ) + ), ) + assert ( str(exc.value) == f"Invalid value type for catalogue item property '{catalogue_category.catalogue_item_properties[2].name}'. " @@ -397,10 +401,10 @@ def test_create_with_with_invalid_value_type_for_number_property( PropertyPostRequestSchema(name="Property B", value=False), PropertyPostRequestSchema(name="Property C", value="20x15x10"), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), ) ) @@ -452,10 +456,10 @@ def test_create_with_with_invalid_value_type_for_boolean_property( PropertyPostRequestSchema(name="Property B", value="False"), PropertyPostRequestSchema(name="Property C", value="20x15x10"), ], - manufacturer=ManufacturerSchema( + manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), ) ) @@ -500,7 +504,7 @@ def test_get(test_helpers, catalogue_item_repository_mock, catalogue_item_servic manufacturer=Manufacturer( name="Manufacturer A", address="1 Address, City, Country, Postcode", - web_url="https://www.manufacturer-a.co.uk", + url="https://www.manufacturer-a.co.uk", ), ) # pylint: enable=duplicate-code @@ -545,7 +549,6 @@ def test_list(catalogue_item_repository_mock, catalogue_item_service): catalogue_item_repository_mock.list.assert_called_once_with(catalogue_category_id) assert result == catalogue_item_repository_mock.list.return_value - def test_update(test_helpers, catalogue_item_repository_mock, catalogue_item_service): """ Test updating a catalogue item. @@ -564,7 +567,7 @@ def test_update(test_helpers, catalogue_item_repository_mock, catalogue_item_ser "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } # pylint: enable=duplicate-code @@ -632,7 +635,7 @@ def test_update_change_catalogue_category_id_same_defined_properties_without_sup "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } full_catalogue_item_info = { @@ -694,7 +697,7 @@ def test_update_change_catalogue_category_id_same_defined_properties_with_suppli "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } full_catalogue_item_info = { @@ -770,7 +773,7 @@ def test_update_change_catalogue_category_id_different_defined_properties_withou "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -827,7 +830,7 @@ def test_update_change_catalogue_category_id_different_defined_properties_with_s "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, } full_catalogue_item_info = { @@ -899,7 +902,7 @@ def test_update_with_nonexistent_catalogue_category_id( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [], } @@ -932,7 +935,7 @@ def test_update_change_catalogue_category_id_non_leaf_catalogue_category( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [], } @@ -979,7 +982,7 @@ def test_update_add_non_mandatory_property( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property B", "value": False}, @@ -1045,7 +1048,7 @@ def test_update_remove_non_mandatory_property( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -1109,7 +1112,7 @@ def test_update_remove_mandatory_property( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -1167,7 +1170,7 @@ def test_update_change_property_value( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -1234,7 +1237,7 @@ def test_update_change_value_for_string_property_invalid_type( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -1291,7 +1294,7 @@ def test_update_change_value_for_number_property_invalid_type( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, @@ -1348,7 +1351,7 @@ def test_update_change_value_for_boolean_property_invalid_type( "manufacturer": { "name": "Manufacturer A", "address": "1 Address, City, Country, Postcode", - "web_url": "https://www.manufacturer-a.co.uk", + "url": "https://www.manufacturer-a.co.uk", }, "properties": [ {"name": "Property A", "value": 20, "unit": "mm"}, diff --git a/test/unit/services/test_manufacturer.py b/test/unit/services/test_manufacturer.py index b48a5f49..65b6ce2d 100644 --- a/test/unit/services/test_manufacturer.py +++ b/test/unit/services/test_manufacturer.py @@ -2,24 +2,10 @@ Unit tests for the `ManufacturerService` service. """ -from unittest.mock import Mock -import pytest from bson import ObjectId from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut from inventory_management_system_api.schemas.manufacturer import ManufacturerPostRequestSchema -from inventory_management_system_api.services.manufacturer import ManufacturerService - - -@pytest.fixture(name="manufacturer_service") -def fixture_manufacturer_service(manufacturer_repository_mock: Mock) -> ManufacturerService: - """ - Fixture to create a `ManufacturerService` instance with a mocked `ManufacturerRepo` - - :param: manufacturer_repository_mock: Mocked `ManufacturerRepo` instance. - :return: `ManufacturerService` instance with mocked dependency - """ - return ManufacturerService(manufacturer_repository_mock) def test_create(manufacturer_repository_mock, manufacturer_service): @@ -111,3 +97,11 @@ def test_get_with_nonexistent_id(manufacturer_repository_mock, manufacturer_serv assert retrieved_manufacturer is None manufacturer_repository_mock.get.assert_called_once_with(manufactuer_id) + + +def test_delete(manufacturer_repository_mock, manufacturer_service): + """Test deleting a manufacturer""" + manufacturer_id = str(ObjectId()) + manufacturer_service.delete(manufacturer_id) + + manufacturer_repository_mock.delete.assert_called_once_with(manufacturer_id)