diff --git a/inventory_management_system_api/repositories/manufacturer.py b/inventory_management_system_api/repositories/manufacturer.py index e39672a2..2c7479ae 100644 --- a/inventory_management_system_api/repositories/manufacturer.py +++ b/inventory_management_system_api/repositories/manufacturer.py @@ -2,7 +2,7 @@ Module for providing a repository for managing manufacturers in a MongoDB database. """ import logging -from typing import Optional +from typing import Optional, List from fastapi import Depends from pymongo.collection import Collection @@ -64,6 +64,18 @@ def get(self, manufacturer_id: str) -> Optional[ManufacturerOut]: return ManufacturerOut(**manufacturer) return None + def list(self) -> List[ManufacturerOut]: + """Get all manufacturers from database + + :return: List of manufacturers, or empty list if no manufacturers + """ + + logger.info("Getting all manufacturers from database") + + manufacturers = self._collection.find() + + return [ManufacturerOut(**manufacturer) for manufacturer in manufacturers] + def _is_duplicate_manufacturer(self, code: str) -> bool: """ Check if manufacturer with the same url already exists in the manufacturer collection diff --git a/inventory_management_system_api/routers/v1/manufacturer.py b/inventory_management_system_api/routers/v1/manufacturer.py index 7f62b6af..5c1984c2 100644 --- a/inventory_management_system_api/routers/v1/manufacturer.py +++ b/inventory_management_system_api/routers/v1/manufacturer.py @@ -3,7 +3,7 @@ `Manufacturer` service. """ import logging - +from typing import List from fastapi import APIRouter, status, Depends, HTTPException from inventory_management_system_api.core.exceptions import DuplicateRecordError @@ -38,3 +38,16 @@ def create_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.get( + path="/", + summary="Get all manufacturers", + response_description="List of manufacturers", +) +def get_all_manufacturers(manufacturer_service: ManufacturerService = Depends()) -> List[ManufacturerSchema]: + # pylint: disable=missing-function-docstring + logger.info("Getting manufacturers") + + manufacturers = manufacturer_service.list() + return [ManufacturerSchema(**manufacturer.dict()) for manufacturer in manufacturers] diff --git a/inventory_management_system_api/schemas/manufacturer.py b/inventory_management_system_api/schemas/manufacturer.py index 24d9f7cd..210d5db3 100644 --- a/inventory_management_system_api/schemas/manufacturer.py +++ b/inventory_management_system_api/schemas/manufacturer.py @@ -2,14 +2,14 @@ Module for defining the API schema models for representing manufacturers. """ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, HttpUrl class ManufacturerPostRequestSchema(BaseModel): """Schema model for manufactuer creation request""" name: str = Field(description="Name of manufacturer") - url: str = Field(description="URL of manufacturer") + url: HttpUrl = Field(description="URL of manufacturer") address: str = Field(description="Address of manufacturer") diff --git a/inventory_management_system_api/services/manufacturer.py b/inventory_management_system_api/services/manufacturer.py index 7358c296..5dab8109 100644 --- a/inventory_management_system_api/services/manufacturer.py +++ b/inventory_management_system_api/services/manufacturer.py @@ -4,6 +4,7 @@ import logging import re +from typing import List from fastapi import Depends from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut @@ -48,3 +49,10 @@ def _generate_code(self, name: str) -> str: """ name = name.lower().strip() return re.sub(r"\s", "-", name) + + def list(self) -> List[ManufacturerOut]: + """Get all manufactuers + + :return: list of all manufacturers + """ + return self._manufacturer_repository.list() diff --git a/test/e2e/test_manufacturer.py b/test/e2e/test_manufacturer.py index e7aefcff..9fa432ef 100644 --- a/test/e2e/test_manufacturer.py +++ b/test/e2e/test_manufacturer.py @@ -21,7 +21,7 @@ def test_create_manufacturer(test_client): """Test creating a manufacturer""" manufacturer_post = { "name": "Manufacturer A", - "url": "example.com", + "url": "http://example.com", "address": "Street A", } @@ -36,19 +36,19 @@ def test_create_manufacturer(test_client): assert manufacturer["address"] == manufacturer_post["address"] -def test_check_duplicate_url_within_manufacturer(test_client): +def test_check_duplicate_name_within_manufacturer(test_client): """Test creating a manufactuer with a duplicate name""" manufacturer_post = { "name": "Manufacturer A", - "url": "example.com", + "url": "http://example.com", "address": "Street A", } test_client.post("/v1/manufacturer", json=manufacturer_post) manufacturer_post = { "name": "Manufacturer A", - "url": "test.com", + "url": "http://test.com", "address": "Street B", } @@ -56,3 +56,46 @@ def test_check_duplicate_url_within_manufacturer(test_client): assert response.status_code == 409 assert response.json()["detail"] == "A manufacturer with the same name has been found" + + +def test_list(test_client): + """Test getting all manufacturers""" + manufacturer_post = { + "name": "Manufacturer A", + "url": "http://example.com", + "address": "Street A", + } + test_client.post("/v1/manufacturer", json=manufacturer_post) + manufacturer_post = { + "name": "Manufacturer B", + "url": "http://2ndExample.com", + "address": "Street B", + } + test_client.post("/v1/manufacturer", json=manufacturer_post) + + response = test_client.get("/v1/manufacturer") + + assert response.status_code == 200 + + manufacturers = list(response.json()) + + assert len(manufacturers) == 2 + assert manufacturers[0]["name"] == "Manufacturer A" + assert manufacturers[0]["url"] == "http://example.com" + assert manufacturers[0]["address"] == "Street A" + assert manufacturers[0]["code"] == "manufacturer-a" + + assert manufacturers[1]["name"] == "Manufacturer B" + assert manufacturers[1]["url"] == "http://2ndExample.com" + assert manufacturers[1]["address"] == "Street B" + assert manufacturers[1]["code"] == "manufacturer-b" + + +def test_list_when_no_manufacturers(test_client): + """Test trying to get all manufacturers when there are none in the databse""" + + response = test_client.get("/v1/manufacturer") + + assert response.status_code == 200 + manufacturers = list(response.json()) + assert not manufacturers diff --git a/test/unit/repositories/test_manufacturer.py b/test/unit/repositories/test_manufacturer.py index 2197cb36..3e9f447e 100644 --- a/test/unit/repositories/test_manufacturer.py +++ b/test/unit/repositories/test_manufacturer.py @@ -40,6 +40,7 @@ def test_create_manufacturer(test_helpers, database_mock, manufacturer_repositor "address": manufacturer.address, }, ) + # pylint: disable=duplicate-code created_manufacturer = manufacturer_repository.create( ManufacturerIn( name=manufacturer.name, @@ -57,6 +58,7 @@ def test_create_manufacturer(test_helpers, database_mock, manufacturer_repositor "address": manufacturer.address, } ) + # pylint: enable=duplicate-code database_mock.manufacturer.find_one.assert_called_once_with({"_id": CustomObjectId(manufacturer.id)}) assert created_manufacturer == manufacturer @@ -69,7 +71,11 @@ def test_create_manufacturer_duplicate(test_helpers, database_mock, manufacturer duplicate manufacturer, and does not create the manufacturer. """ manufacturer = ManufacturerOut( - _id=str(ObjectId()), name="Manufacturer B", code="manufacturer-b", url="duplicate.co.uk", address="street B" + _id=str(ObjectId()), + name="Manufacturer B", + code="manufacturer-b", + url="http://duplicate.co.uk", + address="street B", ) # Mock count_documents to return 1 (duplicat manufacturer found) @@ -77,6 +83,7 @@ def test_create_manufacturer_duplicate(test_helpers, database_mock, manufacturer with pytest.raises(DuplicateRecordError) as exc: manufacturer_repository.create( + # pylint: disable=duplicate-code ManufacturerIn( name=manufacturer.name, code=manufacturer.code, @@ -84,5 +91,57 @@ def test_create_manufacturer_duplicate(test_helpers, database_mock, manufacturer address=manufacturer.address, ) ) - + # pylint: enable=duplicate-code assert str(exc.value) == "Duplicate manufacturer found" + + +def test_list(test_helpers, database_mock, manufacturer_repository): + """Test getting all manufacturers""" + manufacturer_1 = ManufacturerOut( + _id=str(ObjectId()), + code="manufacturer-a", + name="Manufacturer A", + url="http://testUrl.co.uk", + address="1 Example street", + ) + + manufacturer_2 = ManufacturerOut( + _id=str(ObjectId()), + code="manufacturer-b", + name="Manufacturer B", + url="http://2ndTestUrl.co.uk", + address="2 Example street", + ) + + test_helpers.mock_find( + database_mock.manufacturer, + [ + { + "_id": CustomObjectId(manufacturer_1.id), + "code": manufacturer_1.code, + "name": manufacturer_1.name, + "url": manufacturer_1.url, + "address": manufacturer_1.address, + }, + { + "_id": CustomObjectId(manufacturer_2.id), + "code": manufacturer_2.code, + "name": manufacturer_2.name, + "url": manufacturer_2.url, + "address": manufacturer_2.address, + }, + ], + ) + + retrieved_manufacturers = manufacturer_repository.list() + + database_mock.manufacturer.find.assert_called_once_with() + assert retrieved_manufacturers == [manufacturer_1, manufacturer_2] + + +def test_list_when_no_manufacturers(test_helpers, database_mock, manufacturer_repository): + """Test trying to get all manufacturers when there are none in the databse""" + test_helpers.mock_find(database_mock.manufacturer, []) + retrieved_manufacturers = manufacturer_repository.list() + + assert retrieved_manufacturers == [] diff --git a/test/unit/services/conftest.py b/test/unit/services/conftest.py index fd6d4f36..6be32022 100644 --- a/test/unit/services/conftest.py +++ b/test/unit/services/conftest.py @@ -9,6 +9,7 @@ from inventory_management_system_api.models.catalogue_category import CatalogueCategoryOut from inventory_management_system_api.models.catalogue_item import CatalogueItemOut from inventory_management_system_api.repositories.catalogue_category import CatalogueCategoryRepo +from inventory_management_system_api.repositories.manufacturer import ManufacturerRepo from inventory_management_system_api.repositories.catalogue_item import CatalogueItemRepo from inventory_management_system_api.repositories.system import SystemRepo from inventory_management_system_api.services.catalogue_category import CatalogueCategoryService @@ -36,6 +37,15 @@ def fixture_catalogue_item_repository_mock() -> Mock: return Mock(CatalogueItemRepo) +@pytest.fixture(name="manufacturer_repository_mock") +def fixture_manufacturer_repository_mock() -> Mock: + """ + Fixture to create a mock of the `ManufacturerRepo dependency + + :return: Mocked ManufacturerRepo instance + """ + return Mock(ManufacturerRepo) + @pytest.fixture(name="system_repository_mock") def fixture_system_repository_mock() -> Mock: """ diff --git a/test/unit/services/test_manufacturer.py b/test/unit/services/test_manufacturer.py new file mode 100644 index 00000000..02a39991 --- /dev/null +++ b/test/unit/services/test_manufacturer.py @@ -0,0 +1,78 @@ +""" +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): + """ + Testing creating a manufacturer + """ + manufacturer = ManufacturerOut( + _id=str(ObjectId()), + name="Manufacturer A", + code="manufacturer-a", + url="http://testUrl.co.uk", + address="1 Example street", + ) + manufacturer_repository_mock.create.return_value = manufacturer + + created_manufacturer = manufacturer_service.create( + ManufacturerPostRequestSchema( + name=manufacturer.name, + url=manufacturer.url, + address=manufacturer.address, + ) + ) + manufacturer_repository_mock.create.assert_called_once_with( + ManufacturerIn( + name=manufacturer.name, + code=manufacturer.code, + url=manufacturer.url, + address=manufacturer.address, + ) + ) + assert created_manufacturer == manufacturer + + +def test_list(manufacturer_repository_mock, manufacturer_service): + """Test getting all manufacturers""" + # pylint: disable=duplicate-code + manufacturer_1 = ManufacturerOut( + _id=str(ObjectId()), + code="manufacturer-a", + name="Manufacturer A", + url="http://testUrl.co.uk", + address="1 Example street", + ) + + manufacturer_2 = ManufacturerOut( + _id=str(ObjectId()), + code="manufacturer-b", + name="Manufacturer B", + url="http://2ndTestUrl.co.uk", + address="2 Example street", + ) + # pylint: enable=duplicate-code + manufacturer_repository_mock.list.return_value = [manufacturer_1, manufacturer_2] + retrieved_manufacturer = manufacturer_service.list() + manufacturer_repository_mock.list.assert_called_once_with() + assert retrieved_manufacturer == [manufacturer_1, manufacturer_2]