From 61cbbf03666eefc65eb42504e37f729f071d13f6 Mon Sep 17 00:00:00 2001 From: Julien Aldon Date: Wed, 25 Feb 2026 16:39:12 +0100 Subject: [PATCH] add tests forn forms, products, productors --- README.md | 13 - backend/README.md | 5 + backend/pyproject.toml | 5 +- backend/src/forms/exceptions.py | 17 ++ backend/src/forms/forms.py | 25 +- backend/src/forms/service.py | 18 +- backend/src/messages.py | 31 +- backend/src/productors/exceptions.py | 11 + backend/src/productors/productors.py | 21 +- backend/src/productors/service.py | 21 +- backend/src/products/exceptions.py | 14 + backend/src/products/products.py | 24 +- backend/src/products/service.py | 14 +- backend/src/settings.py | 7 +- backend/tests/conftest.py | 58 ++++ backend/tests/factories/contracts.py | 72 +++++ backend/tests/factories/forms.py | 84 ++++++ backend/tests/factories/productors.py | 59 ++++ backend/tests/factories/products.py | 64 +++++ backend/tests/factories/shipments.py | 53 ++++ backend/tests/factories/users.py | 48 ++++ backend/tests/fixtures.py | 119 ++++++++ backend/tests/routers/test_contracts.py | 186 ++++++++++++ backend/tests/routers/test_forms.py | 249 +++++++++++++++++ backend/tests/routers/test_productors.py | 262 +++++++++++++++++ backend/tests/routers/test_products.py | 264 ++++++++++++++++++ backend/tests/routers/test_shipments.py | 263 +++++++++++++++++ backend/tests/routers/test_users.py | 259 +++++++++++++++++ backend/tests/services/test_forms_service.py | 154 ++++++++++ .../tests/services/test_productors_service.py | 143 ++++++++++ .../tests/services/test_products_service.py | 191 +++++++++++++ 31 files changed, 2694 insertions(+), 60 deletions(-) create mode 100644 backend/src/forms/exceptions.py create mode 100644 backend/src/productors/exceptions.py create mode 100644 backend/src/products/exceptions.py create mode 100644 backend/tests/conftest.py create mode 100644 backend/tests/factories/contracts.py create mode 100644 backend/tests/factories/forms.py create mode 100644 backend/tests/factories/productors.py create mode 100644 backend/tests/factories/products.py create mode 100644 backend/tests/factories/shipments.py create mode 100644 backend/tests/factories/users.py create mode 100644 backend/tests/fixtures.py create mode 100644 backend/tests/routers/test_contracts.py create mode 100644 backend/tests/routers/test_forms.py create mode 100644 backend/tests/routers/test_productors.py create mode 100644 backend/tests/routers/test_products.py create mode 100644 backend/tests/routers/test_shipments.py create mode 100644 backend/tests/routers/test_users.py create mode 100644 backend/tests/services/test_forms_service.py create mode 100644 backend/tests/services/test_productors_service.py create mode 100644 backend/tests/services/test_products_service.py diff --git a/README.md b/README.md index c3aed71..f4a2d9a 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,12 @@ - Extract recap -## Payment method max cheque number - ## Link products to a form ## Wording - all translations -## Draft / Publish form - -- By default form is in draft mode -- Validate a form (button) - - check if productor - - check if shipments - - check products -- Publish - -## Only show productors / products / forms for referent of type - ## Footer ### Legal diff --git a/backend/README.md b/backend/README.md index 286c544..9264553 100644 --- a/backend/README.md +++ b/backend/README.md @@ -29,6 +29,11 @@ alembic revision --autogenerate -m "message" ```console alembic upgrade head ``` +## Tests +``` +hatch run pytest +hatch run pytest --cov=src -vv +``` ## License diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 7af776e..d7b8006 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -30,7 +30,10 @@ dependencies = [ "requests", "weasyprint", "odfdo", - "alembic" + "alembic", + "pytest", + "pytest-cov", + "pytest-mock", ] [project.urls] diff --git a/backend/src/forms/exceptions.py b/backend/src/forms/exceptions.py new file mode 100644 index 0000000..2ce2650 --- /dev/null +++ b/backend/src/forms/exceptions.py @@ -0,0 +1,17 @@ +class FormServiceError(Exception): + def __init__(self, message: str): + super().__init__(message) + +class UserNotFoundError(FormServiceError): + pass + +class ProductorNotFoundError(FormServiceError): + pass + +class FormNotFoundError(FormServiceError): + pass + +class FormCreateError(FormServiceError): + def __init__(self, message: str, field: str | None = None): + super().__init__(message) + self.field = field \ No newline at end of file diff --git a/backend/src/forms/forms.py b/backend/src/forms/forms.py index 2254259..b7f78f6 100644 --- a/backend/src/forms/forms.py +++ b/backend/src/forms/forms.py @@ -4,6 +4,7 @@ import src.models as models from src.database import get_session from sqlmodel import Session import src.forms.service as service +import src.forms.exceptions as exceptions from src.auth.auth import get_current_user router = APIRouter(prefix='/forms') @@ -40,7 +41,15 @@ async def create_form( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - return service.create_one(session, form) + try: + form = service.create_one(session, form) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) + except exceptions.UserNotFoundError: + raise HTTPException(status_code=404, detail=messages.usernotfound) + except exceptions.FormCreateError: + raise HTTPException(status_code=400, detail=messages.forminputinvalid) + return form @router.put('/{id}', response_model=models.FormPublic) async def update_form( @@ -48,9 +57,14 @@ async def update_form( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.update_one(session, id, form) - if result is None: + try: + result = service.update_one(session, id, form) + except exceptions.FormNotFoundError: raise HTTPException(status_code=404, detail=messages.notfound) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) + except exceptions.UserNotFoundError: + raise HTTPException(status_code=404, detail=messages.usernotfound) return result @router.delete('/{id}', response_model=models.FormPublic) @@ -59,7 +73,8 @@ async def delete_form( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.delete_one(session, id) - if result is None: + try: + result = service.delete_one(session, id) + except exceptions.FormNotFoundError: raise HTTPException(status_code=404, detail=messages.notfound) return result diff --git a/backend/src/forms/service.py b/backend/src/forms/service.py index 6a49f64..205a2a6 100644 --- a/backend/src/forms/service.py +++ b/backend/src/forms/service.py @@ -1,7 +1,9 @@ from sqlmodel import Session, select -import src.models as models from sqlalchemy import func +import src.models as models +import src.forms.exceptions as exceptions + def get_all( session: Session, seasons: list[str], @@ -46,6 +48,12 @@ def get_one(session: Session, form_id: int) -> models.FormPublic: return session.get(models.Form, form_id) def create_one(session: Session, form: models.FormCreate) -> models.FormPublic: + if not form: + raise exceptions.FormCreateError('FormCreate input cannot be None') + if not session.get(models.Productor, form.productor_id): + raise exceptions.ProductorNotFoundError(f'Productor {form.productor_id} not found') + if not session.get(models.User, form.referer_id): + raise exceptions.UserNotFoundError(f'User {form.referer_id} not found') form_create = form.model_dump(exclude_unset=True) new_form = models.Form(**form_create) session.add(new_form) @@ -58,7 +66,11 @@ def update_one(session: Session, id: int, form: models.FormUpdate) -> models.For result = session.exec(statement) new_form = result.first() if not new_form: - return None + raise exceptions.FormNotFoundError(f'Form {id} not found') + if form.productor_id and not session.get(models.Productor, form.productor_id): + raise exceptions.ProductorNotFoundError(f'Productor {form.productor_id} not found') + if form.referer_id and not session.get(models.User, form.referer_id): + raise exceptions.UserNotFoundError(f'User {form.referer_id} not found') form_updates = form.model_dump(exclude_unset=True) for key, value in form_updates.items(): setattr(new_form, key, value) @@ -72,7 +84,7 @@ def delete_one(session: Session, id: int) -> models.FormPublic: result = session.exec(statement) form = result.first() if not form: - return None + raise exceptions.FormNotFoundError(f'Form {id} not found') result = models.FormPublic.model_validate(form) session.delete(form) session.commit() diff --git a/backend/src/messages.py b/backend/src/messages.py index 84b02d5..318960d 100644 --- a/backend/src/messages.py +++ b/backend/src/messages.py @@ -1,10 +1,21 @@ -notfound = "Resource was not found." -pdferror = "An error occured during PDF generation please contact administrator" -tokenexipired = "Token expired" -invalidtoken = "Invalid token" -notauthenticated = "Not authenticated" -usernotfound = "User not found" -userloggedout = "User logged out" -failtogettoken = "Failed to get token" -unauthorized = "Unauthorized" -notallowed = "Not Allowed" \ No newline at end of file +pdferror = 'An error occured during PDF generation please contact administrator' + +tokenexipired = 'Token expired' +invalidtoken = 'Invalid token' +notauthenticated = 'Not authenticated' +failtogettoken = 'Failed to get token' +unauthorized = 'Unauthorized' +notallowed = 'Not Allowed' + +notfound = 'Resource was not found.' +usernotfound = 'User not found' +userloggedout = 'User logged out' + +productorinputinvalid = 'Invalid productor input' +productornotfound = 'Productor not found' + +forminputinvalid = 'Invalid form input' +formnotfound = 'Form not found' + +productinputinvalid = 'Invalid product input' +productnotfound = 'Product not found' diff --git a/backend/src/productors/exceptions.py b/backend/src/productors/exceptions.py new file mode 100644 index 0000000..32b0beb --- /dev/null +++ b/backend/src/productors/exceptions.py @@ -0,0 +1,11 @@ +class ProductorServiceError(Exception): + def __init__(self, message: str): + super().__init__(message) + +class ProductorNotFoundError(ProductorServiceError): + pass + +class ProductorCreateError(ProductorServiceError): + def __init__(self, message: str, field: str | None = None): + super().__init__(message) + self.field = field \ No newline at end of file diff --git a/backend/src/productors/productors.py b/backend/src/productors/productors.py index 4e89a81..a6ec468 100644 --- a/backend/src/productors/productors.py +++ b/backend/src/productors/productors.py @@ -4,6 +4,7 @@ import src.models as models from src.database import get_session from sqlmodel import Session import src.productors.service as service +import src.productors.exceptions as exceptions from src.auth.auth import get_current_user router = APIRouter(prefix='/productors') @@ -34,7 +35,11 @@ def create_productor( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - return service.create_one(session, productor) + try: + result = service.create_one(session, productor) + except exceptions.ProductorCreateError: + raise HTTPException(status_code=400, detail=messages.productorinputinvalid) + return result @router.put('/{id}', response_model=models.ProductorPublic) def update_productor( @@ -42,9 +47,10 @@ def update_productor( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.update_one(session, id, productor) - if result is None: - raise HTTPException(status_code=404, detail=messages.notfound) + try: + result = service.update_one(session, id, productor) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) return result @router.delete('/{id}', response_model=models.ProductorPublic) @@ -53,7 +59,8 @@ def delete_productor( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.delete_one(session, id) - if result is None: - raise HTTPException(status_code=404, detail=messages.notfound) + try: + result = service.delete_one(session, id) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) return result diff --git a/backend/src/productors/service.py b/backend/src/productors/service.py index dd7a822..ae26b2b 100644 --- a/backend/src/productors/service.py +++ b/backend/src/productors/service.py @@ -1,5 +1,6 @@ from sqlmodel import Session, select import src.models as models +import src.productors.exceptions as exceptions def get_all( session: Session, @@ -20,7 +21,9 @@ def get_one(session: Session, productor_id: int) -> models.ProductorPublic: return session.get(models.Productor, productor_id) def create_one(session: Session, productor: models.ProductorCreate) -> models.ProductorPublic: - productor_create = productor.model_dump(exclude_unset=True, exclude="payment_methods") + if not productor: + raise exceptions.ProductorCreateError('ProductorCreate input cannot be None') + productor_create = productor.model_dump(exclude_unset=True, exclude='payment_methods') new_productor = models.Productor(**productor_create) new_productor.payment_methods = [ @@ -40,21 +43,21 @@ def update_one(session: Session, id: int, productor: models.ProductorUpdate) -> result = session.exec(statement) new_productor = result.first() if not new_productor: - return None + raise exceptions.ProductorNotFoundError(f'Productor {id} not found') productor_updates = productor.model_dump(exclude_unset=True) - if "payment_methods" in productor_updates: + if 'payment_methods' in productor_updates: new_productor.payment_methods.clear() - for pm in productor_updates["payment_methods"]: + for pm in productor_updates['payment_methods']: new_productor.payment_methods.append( models.PaymentMethod( - name=pm["name"], - details=pm["details"], + name=pm['name'], + details=pm['details'], productor_id=id, - max=pm["max"] + max=pm['max'] ) ) - del productor_updates["payment_methods"] + del productor_updates['payment_methods'] for key, value in productor_updates.items(): setattr(new_productor, key, value) @@ -68,7 +71,7 @@ def delete_one(session: Session, id: int) -> models.ProductorPublic: result = session.exec(statement) productor = result.first() if not productor: - return None + raise exceptions.ProductorNotFoundError(f'Productor {id} not found') result = models.ProductorPublic.model_validate(productor) session.delete(productor) session.commit() diff --git a/backend/src/products/exceptions.py b/backend/src/products/exceptions.py new file mode 100644 index 0000000..ae71d08 --- /dev/null +++ b/backend/src/products/exceptions.py @@ -0,0 +1,14 @@ +class ProductServiceError(Exception): + def __init__(self, message: str): + super().__init__(message) + +class ProductorNotFoundError(ProductServiceError): + pass + +class ProductNotFoundError(ProductServiceError): + pass + +class ProductCreateError(ProductServiceError): + def __init__(self, message: str, field: str | None = None): + super().__init__(message) + self.field = field \ No newline at end of file diff --git a/backend/src/products/products.py b/backend/src/products/products.py index b7dced1..47e3245 100644 --- a/backend/src/products/products.py +++ b/backend/src/products/products.py @@ -4,7 +4,9 @@ import src.models as models from src.database import get_session from sqlmodel import Session import src.products.service as service +import src.products.exceptions as exceptions from src.auth.auth import get_current_user + router = APIRouter(prefix='/products') @router.get('', response_model=list[models.ProductPublic], ) @@ -40,7 +42,13 @@ def create_product( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - return service.create_one(session, product) + try: + result = service.create_one(session, product) + except exceptions.ProductCreateError: + raise HTTPException(status_code=400, detail=messages.productinputinvalid) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) + return result @router.put('/{id}', response_model=models.ProductPublic) def update_product( @@ -48,9 +56,12 @@ def update_product( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.update_one(session, id, product) - if result is None: - raise HTTPException(status_code=404, detail=messages.notfound) + try: + result = service.update_one(session, id, product) + except exceptions.ProductNotFoundError: + raise HTTPException(status_code=404, detail=messages.productnotfound) + except exceptions.ProductorNotFoundError: + raise HTTPException(status_code=404, detail=messages.productornotfound) return result @router.delete('/{id}', response_model=models.ProductPublic) @@ -59,7 +70,8 @@ def delete_product( user: models.User = Depends(get_current_user), session: Session = Depends(get_session) ): - result = service.delete_one(session, id) - if result is None: + try: + result = service.delete_one(session, id) + except exceptions.ProductNotFoundError: raise HTTPException(status_code=404, detail=messages.notfound) return result diff --git a/backend/src/products/service.py b/backend/src/products/service.py index b898cda..a276355 100644 --- a/backend/src/products/service.py +++ b/backend/src/products/service.py @@ -1,5 +1,6 @@ from sqlmodel import Session, select import src.models as models +import src.products.exceptions as exceptions def get_all( session: Session, @@ -15,7 +16,7 @@ def get_all( if len(names) > 0: statement = statement.where(models.Product.name.in_(names)) if len(productors) > 0: - statement = statement.join(models.Productor).where(models.Productor.name.in_(productors)) + statement = statement.where(models.Productor.name.in_(productors)) if len(types) > 0: statement = statement.where(models.Product.type.in_(types)) return session.exec(statement.order_by(models.Product.name)).all() @@ -24,6 +25,10 @@ def get_one(session: Session, product_id: int) -> models.ProductPublic: return session.get(models.Product, product_id) def create_one(session: Session, product: models.ProductCreate) -> models.ProductPublic: + if not product: + raise exceptions.ProductCreateError('ProductCreate input cannot be None') + if not session.get(models.Productor, product.productor_id): + raise exceptions.ProductorNotFoundError(f'Productor {product.productor_id} not found') product_create = product.model_dump(exclude_unset=True) new_product = models.Product(**product_create) session.add(new_product) @@ -36,7 +41,10 @@ def update_one(session: Session, id: int, product: models.ProductUpdate) -> mode result = session.exec(statement) new_product = result.first() if not new_product: - return None + raise exceptions.ProductNotFoundError(f'Product {id} not found') + if product.productor_id and not session.get(models.Productor, product.productor_id): + raise exceptions.ProductorNotFoundError(f'Productor {product.productor_id} not found') + product_updates = product.model_dump(exclude_unset=True) for key, value in product_updates.items(): setattr(new_product, key, value) @@ -51,7 +59,7 @@ def delete_one(session: Session, id: int) -> models.ProductPublic: result = session.exec(statement) product = result.first() if not product: - return None + raise exceptions.ProductNotFoundError(f'Product {id} not found') result = models.ProductPublic.model_validate(product) session.delete(product) session.commit() diff --git a/backend/src/settings.py b/backend/src/settings.py index 22e703d..44649ca 100644 --- a/backend/src/settings.py +++ b/backend/src/settings.py @@ -1,4 +1,4 @@ -from pydantic_settings import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): origins: str @@ -16,8 +16,9 @@ class Settings(BaseSettings): max_age: int debug: bool - class Config: - env_file = "../.env" + model_config = SettingsConfigDict( + env_file='../.env' + ) settings = Settings() diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..6383466 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,58 @@ +import pytest +from fastapi.testclient import TestClient +from sqlmodel import SQLModel, Session, create_engine +from sqlalchemy.pool import StaticPool + +from .fixtures import * +from src.main import app +import src.models as models +from src.database import get_session +from src.auth.auth import get_current_user + +@pytest.fixture +def mock_session(mocker): + session = mocker.Mock() + + def override(): + return session + + app.dependency_overrides[get_session] = override + yield session + app.dependency_overrides.clear() + +@pytest.fixture +def mock_user(): + user = models.User(id=1, name='test user', email='test@user.com') + + def override(): + return user + + app.dependency_overrides[get_current_user] = override + yield user + app.dependency_overrides.clear() + +@pytest.fixture +def client(): + return TestClient(app) + +@pytest.fixture(name='session') +def session_fixture(): + engine = create_engine( + "sqlite://", + connect_args={"check_same_thread": False}, + poolclass=StaticPool, + ) + + SQLModel.metadata.create_all(engine) + + connection = engine.connect() + transaction = connection.begin() + session = Session(bind=connection) + + try: + yield session + finally: + transaction.rollback() + session.close() + connection.close() + engine.dispose() \ No newline at end of file diff --git a/backend/tests/factories/contracts.py b/backend/tests/factories/contracts.py new file mode 100644 index 0000000..a315708 --- /dev/null +++ b/backend/tests/factories/contracts.py @@ -0,0 +1,72 @@ +import src.models as models +from .forms import form_factory + +def contract_factory(**kwargs): + data = dict( + firstname="test", + lastname="test", + email="test@test.test", + phone="00000000", + payment_method="cheque", + cheque_quantity=1, + form_id=1, + products=[], + cheques=[], + ) + data.update(kwargs) + return models.Contract(**data) + +def contract_public_factory(**kwargs): + data = dict( + id=1, + firstname="test", + lastname="test", + email="test@test.test", + phone="00000000", + payment_method="cheque", + cheque_quantity=1, + total_price=10, + products=[], + form=form_factory() + ) + data.update(kwargs) + return models.ContractPublic(**data) + +def contract_create_factory(**kwargs): + data = dict( + firstname="test", + lastname="test", + email="test@test.test", + phone="00000000", + payment_method="cheque", + cheque_quantity=1, + products=[], + cheques=[], + form_id=1, + ) + data.update(kwargs) + return models.ContractCreate(**data) + +def contract_update_factory(**kwargs): + data = dict( + firstname="test", + lastname="test", + email="test@test.test", + phone="00000000", + payment_method="cheque", + cheque_quantity=1, + ) + data.update(kwargs) + return models.ContractUpdate(**data) + +def contract_body_factory(**kwargs): + data = dict( + firstname="test", + lastname="test", + email="test@test.test", + phone="00000000", + payment_method="cheque", + cheque_quantity=1, + ) + data.update(kwargs) + return data \ No newline at end of file diff --git a/backend/tests/factories/forms.py b/backend/tests/factories/forms.py new file mode 100644 index 0000000..bb2291e --- /dev/null +++ b/backend/tests/factories/forms.py @@ -0,0 +1,84 @@ +import src.models as models +from .productors import productor_public_factory +from .shipments import shipment_public_factory +from .users import user_factory +import datetime + +def form_factory(**kwargs): + data = dict( + id=1, + name="form 1", + productor_id=1, + referer_id=1, + season="hiver-2026", + start=datetime.date(2025, 10, 10), + end=datetime.date(2025, 10, 10), + minimum_shipment_value=0, + visible=True, + referer=user_factory(), + shipments=[], + productor=productor_public_factory(), + ) + data.update(kwargs) + return models.Form(**data) + + +def form_body_factory(**kwargs): + data = dict( + name="form 1", + productor_id=1, + referer_id=1, + season="hiver-2026", + start="2025-10-10", + end="2025-10-10", + minimum_shipment_value=0, + visible=True + ) + data.update(kwargs) + return data + +def form_create_factory(**kwargs): + data = dict( + name="form 1", + productor_id=1, + referer_id=1, + season="hiver-2026", + start=datetime.date(2025, 10, 10), + end=datetime.date(2025, 10, 10), + minimum_shipment_value=0, + visible=True + ) + data.update(kwargs) + return models.FormCreate(**data) + +def form_update_factory(**kwargs): + data = dict( + name="form 1", + productor_id=1, + referer_id=1, + season="hiver-2026", + start=datetime.date(2025, 10, 10), + end=datetime.date(2025, 10, 10), + minimum_shipment_value=0, + visible=True + ) + data.update(kwargs) + return models.FormUpdate(**data) + +def form_public_factory(form=None, shipments=[],**kwargs): + data = dict( + id=1, + name="form 1", + productor_id=1, + referer_id=1, + season="hiver-2026", + start=datetime.date(2025, 10, 10), + end=datetime.date(2025, 10, 10), + minimum_shipment_value=0, + visible=True, + referer=user_factory(), + shipments=[], + productor=productor_public_factory(), + ) + data.update(kwargs) + return models.FormPublic(**data) \ No newline at end of file diff --git a/backend/tests/factories/productors.py b/backend/tests/factories/productors.py new file mode 100644 index 0000000..8e7ee63 --- /dev/null +++ b/backend/tests/factories/productors.py @@ -0,0 +1,59 @@ +import src.models as models + +def productor_factory(**kwargs): + data = dict( + id=1, + name="test productor", + address="test address", + type="test type" + ) + data.update(kwargs) + return models.Productor(**data) + +def productor_public_factory(**kwargs): + data = dict( + id=1, + name="test productor", + address="test address", + type="test type", + products=[], + payment_methods=[], + ) + data.update(kwargs) + return models.ProductorPublic(**data) + +def productor_create_factory(**kwargs): + data = dict( + id=1, + name="test productor", + address="test address", + type="test type", + products=[], + payment_methods=[], + ) + data.update(kwargs) + return models.ProductorCreate(**data) + +def productor_update_factory(**kwargs): + data = dict( + id=1, + name="test productor", + address="test address", + type="test type", + products=[], + payment_methods=[], + ) + data.update(kwargs) + return models.ProductorUpdate(**data) + +def productor_body_factory(**kwargs): + data = dict( + id=1, + name="test productor", + address="test address", + type="test type", + products=[], + payment_methods=[], + ) + data.update(kwargs) + return data \ No newline at end of file diff --git a/backend/tests/factories/products.py b/backend/tests/factories/products.py new file mode 100644 index 0000000..b9c7af9 --- /dev/null +++ b/backend/tests/factories/products.py @@ -0,0 +1,64 @@ +import src.models as models +from .productors import productor_factory +from .shipments import shipment_factory + +def product_body_factory(**kwargs): + data = dict( + name='product test 1', + unit=models.Unit.PIECE, + price=10.2, + price_kg=20.4, + quantity=500, + quantity_unit='g', + type=models.ProductType.OCCASIONAL, + productor_id=1, + ) + data.update(kwargs) + return data + +def product_create_factory(**kwargs): + data = dict( + name='product test 1', + unit=models.Unit.PIECE, + price=10.2, + price_kg=20.4, + quantity=500, + quantity_unit='g', + type=models.ProductType.OCCASIONAL, + productor_id=1, + ) + data.update(kwargs) + return models.ProductCreate(**data) + +def product_update_factory(**kwargs): + data = dict( + name='product test 1', + unit=models.Unit.PIECE, + price=10.2, + price_kg=20.4, + quantity=500, + quantity_unit='g', + type=models.ProductType.OCCASIONAL, + productor_id=1, + ) + data.update(kwargs) + return models.ProductUpdate(**data) + +def product_public_factory(productor=None, shipments=[],**kwargs): + if productor is None: + productor = productor_factory() + data = dict( + id=1, + name='product test 1', + unit=models.Unit.PIECE, + price=10.2, + price_kg=20.4, + quantity=500, + quantity_unit='g', + type=models.ProductType.OCCASIONAL, + productor_id=1, + productor=productor, + shipments=shipments, + ) + data.update(kwargs) + return models.ProductPublic(**data) \ No newline at end of file diff --git a/backend/tests/factories/shipments.py b/backend/tests/factories/shipments.py new file mode 100644 index 0000000..18b9181 --- /dev/null +++ b/backend/tests/factories/shipments.py @@ -0,0 +1,53 @@ +import src.models as models +import datetime + +def shipment_factory(**kwargs): + data = dict( + id=1, + name="test shipment", + date=datetime.date(2025, 10, 10), + form_id=1, + ) + data.update(kwargs) + return models.Shipment(**data) + +def shipment_public_factory(**kwargs): + data = dict( + id=1, + name="test shipment", + date=datetime.date(2025, 10, 10), + form_id=1, + products=[], + form=models.Form(id=1, name="test") + ) + data.update(kwargs) + return models.ShipmentPublic(**data) + +def shipment_create_factory(**kwargs): + data = dict( + name="test shipment", + form_id=1, + date='2025-10-10', + product_ids=[], + ) + data.update(kwargs) + return models.ShipmentCreate(**data) + +def shipment_update_factory(**kwargs): + data = dict( + name="test shipment", + form_id=1, + date='2025-10-10', + product_ids=[], + ) + data.update(kwargs) + return models.ShipmentUpdate(**data) + +def shipment_body_factory(**kwargs): + data = dict( + name="test shipment", + form_id=1, + date="2025-10-10", + ) + data.update(kwargs) + return data \ No newline at end of file diff --git a/backend/tests/factories/users.py b/backend/tests/factories/users.py new file mode 100644 index 0000000..25257e4 --- /dev/null +++ b/backend/tests/factories/users.py @@ -0,0 +1,48 @@ +import src.models as models + +def user_factory(**kwargs): + data = dict( + id=1, + name="test user", + email="test.test@test.test", + roles=[] + ) + data.update(kwargs) + return models.User(**data) + +def user_public_factory(**kwargs): + data = dict( + id=1, + name="test user", + email="test.test@test.test", + roles=[] + ) + data.update(kwargs) + return models.UserPublic(**data) + +def user_create_factory(**kwargs): + data = dict( + name="test user", + email="test.test@test.test", + role_names=[], + ) + data.update(kwargs) + return models.UserCreate(**data) + +def user_update_factory(**kwargs): + data = dict( + name="test user", + email="test.test@test.test", + role_names=[], + ) + data.update(kwargs) + return models.UserUpdate(**data) + +def user_body_factory(**kwargs): + data = dict( + name="test user", + email="test.test@test.test", + role_names=[], + ) + data.update(kwargs) + return data \ No newline at end of file diff --git a/backend/tests/fixtures.py b/backend/tests/fixtures.py new file mode 100644 index 0000000..1303789 --- /dev/null +++ b/backend/tests/fixtures.py @@ -0,0 +1,119 @@ +import pytest +from sqlmodel import Session + +import src.models as models +import src.forms.service as forms_service +import src.productors.service as productors_service +import src.products.service as products_service +import src.users.service as users_service +import tests.factories.forms as forms_factory +import tests.factories.productors as productors_factory +import tests.factories.products as products_factory +import tests.factories.users as users_factory + +@pytest.fixture +def productor(session: Session) -> models.ProductorPublic: + productor = productors_service.create_one( + session, + productors_factory.productor_create_factory( + name='test productor', + type='Légumineuses', + ) + ) + return productor + + +@pytest.fixture +def productors(session: Session) -> models.ProductorPublic: + productors = [ + productors_service.create_one( + session, + productors_factory.productor_create_factory( + name='test productor 1', + type='Légumineuses', + ) + ), + productors_service.create_one( + session, + productors_factory.productor_create_factory( + name='test productor 2', + type='Légumes', + ) + ) + ] + return productors + +@pytest.fixture +def products(session: Session, productor: models.ProductorPublic) -> list[models.ProductPublic]: + products = [ + products_service.create_one( + session, + products_factory.product_create_factory( + name='product 1 occasionnal', + type=models.ProductType.OCCASIONAL, + productor_id=productor.id + ) + ), + products_service.create_one( + session, + products_factory.product_create_factory( + name='product 2 recurrent', + type=models.ProductType.RECCURENT, + productor_id=productor.id + ) + ), + ] + return products + +@pytest.fixture +def user(session: Session) -> models.UserPublic: + user = users_service.create_one( + session, + users_factory.user_create_factory( + name='test user', + email='test@test.com', + role_names=['Légumineuses'] + ) + ) + return user + +@pytest.fixture +def referer(session: Session) -> models.UserPublic: + referer = users_service.create_one( + session, + users_factory.user_create_factory( + name='test referer', + email='test@test.com', + role_names=['Légumineuses'], + ) + ) + return referer + +@pytest.fixture +def forms( + session: Session, + productor: models.ProductorPublic, + referer: models.UserPublic +) -> list[models.FormPublic]: + forms = [ + forms_service.create_one( + session, + forms_factory.form_create_factory( + name='test form 1', + productor_id=productor.id, + referer_id=referer.id, + season='test season 1', + ) + ), + forms_service.create_one( + session, + forms_factory.form_create_factory( + name='test form 2', + productor_id=productor.id, + referer_id=referer.id, + season='test season 2', + ) + ) + ] + return forms + diff --git a/backend/tests/routers/test_contracts.py b/backend/tests/routers/test_contracts.py new file mode 100644 index 0000000..7aec6dc --- /dev/null +++ b/backend/tests/routers/test_contracts.py @@ -0,0 +1,186 @@ +import src.contracts.service as service +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.contracts as contract_factory + +from fastapi.exceptions import HTTPException + +class TestContracts: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + contract_factory.contract_public_factory(name="test 1", id=1), + contract_factory.contract_public_factory(name="test 2", id=2), + contract_factory.contract_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/contracts') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + [], + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + contract_factory.contract_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/contracts?forms=form test') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + ['form test'], + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.contracts.service.get_all') + response = client.get('/api/contracts') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = contract_factory.contract_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + mock_is_allowed = mocker.patch.object( + service, + 'is_allowed', + return_value=True + ) + response = client.get('/api/contracts/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + mock_is_allowed = mocker.patch.object( + service, + 'is_allowed', + return_value=True + ) + response = client.get('/api/contracts/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.contracts.service.get_one') + response = client.get('/api/contracts/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + contract_result = contract_factory.contract_public_factory(name='test contract delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=contract_result + ) + + mock_is_allowed = mocker.patch.object( + service, + 'is_allowed', + return_value=True + ) + + response = client.delete('/api/contracts/2') + response_data = response.json() + + assert response.status_code == 200 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + contract_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=contract_result + ) + + mock_is_allowed = mocker.patch.object( + service, + 'is_allowed', + return_value=True + ) + + response = client.delete('/api/contracts/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + contract_body = contract_factory.contract_body_factory(name='test contract delete') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.contracts.service.delete_one') + response = client.delete('/api/contracts/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/routers/test_forms.py b/backend/tests/routers/test_forms.py new file mode 100644 index 0000000..4836aa4 --- /dev/null +++ b/backend/tests/routers/test_forms.py @@ -0,0 +1,249 @@ +import src.forms.service as service +import src.forms.exceptions as forms_exceptions +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.forms as form_factory +from fastapi.exceptions import HTTPException + +class TestForms: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + form_factory.form_public_factory(name="test 1", id=1), + form_factory.form_public_factory(name="test 2", id=2), + form_factory.form_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/forms/referents') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + [], + [], + False, + mock_user, + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + form_factory.form_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/forms/referents?current_season=true&seasons=hiver-2025&productors=test productor') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + ['hiver-2025'], + ['test productor'], + True, + mock_user, + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.forms.service.get_all') + response = client.get('/api/forms/referents') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = form_factory.form_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + response = client.get('/api/forms/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + response = client.get('/api/forms/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + + def test_create_one(self, client, mocker, mock_session, mock_user): + form_body = form_factory.form_body_factory(name='test form create') + form_create = form_factory.form_create_factory(name='test form create') + form_result = form_factory.form_public_factory(name='test form create') + + mock = mocker.patch.object( + service, + 'create_one', + return_value=form_result + ) + + response = client.post('/api/forms', json=form_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test form create' + mock.assert_called_once_with( + mock_session, + form_create + ) + + def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + form_body = form_factory.form_body_factory(name='test form create') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.forms.service.create_one') + response = client.post('/api/forms', json=form_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_update_one(self, client, mocker, mock_session, mock_user): + form_body = form_factory.form_body_factory(name='test form update') + form_update = form_factory.form_update_factory(name='test form update') + form_result = form_factory.form_public_factory(name='test form update') + + mock = mocker.patch.object( + service, + 'update_one', + return_value=form_result + ) + + response = client.put('/api/forms/2', json=form_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test form update' + mock.assert_called_once_with( + mock_session, + 2, + form_update + ) + + def test_update_one_notfound(self, client, mocker, mock_session, mock_user): + form_body = form_factory.form_body_factory(name='test form update') + form_update = form_factory.form_update_factory(name='test form update') + form_result = None + + mock = mocker.patch.object( + service, + 'update_one', + side_effect=forms_exceptions.FormNotFoundError('Form 1 not found') + ) + + response = client.put('/api/forms/2', json=form_body) + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + form_update + ) + + def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + form_body = form_factory.form_body_factory(name='test form update') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.forms.service.update_one') + response = client.put('/api/forms/2', json=form_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + form_result = form_factory.form_public_factory(name='test form delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=form_result + ) + + response = client.delete('/api/forms/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test form delete' + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + form_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + side_effect=forms_exceptions.FormNotFoundError('Form 2 not found') + ) + + response = client.delete('/api/forms/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.forms.service.delete_one') + response = client.delete('/api/forms/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/routers/test_productors.py b/backend/tests/routers/test_productors.py new file mode 100644 index 0000000..4227823 --- /dev/null +++ b/backend/tests/routers/test_productors.py @@ -0,0 +1,262 @@ +import src.productors.service as service +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.productors as productor_factory +import src.productors.exceptions as exceptions + +from fastapi.exceptions import HTTPException + +class TestProductors: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + productor_factory.productor_public_factory(name="test 1", id=1), + productor_factory.productor_public_factory(name="test 2", id=2), + productor_factory.productor_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/productors') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + [], + [], + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + productor_factory.productor_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/productors?types=Légumineuses&names=test 2') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + ['test 2'], + ['Légumineuses'], + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.productors.service.get_all') + response = client.get('/api/productors') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = productor_factory.productor_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + response = client.get('/api/productors/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + response = client.get('/api/productors/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.productors.service.get_one') + response = client.get('/api/productors/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_create_one(self, client, mocker, mock_session, mock_user): + productor_body = productor_factory.productor_body_factory(name='test productor create') + productor_create = productor_factory.productor_create_factory(name='test productor create') + productor_result = productor_factory.productor_public_factory(name='test productor create') + + mock = mocker.patch.object( + service, + 'create_one', + return_value=productor_result + ) + + response = client.post('/api/productors', json=productor_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test productor create' + mock.assert_called_once_with( + mock_session, + productor_create + ) + + def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + productor_body = productor_factory.productor_body_factory(name='test productor create') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.productors.service.create_one') + response = client.post('/api/productors', json=productor_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_update_one(self, client, mocker, mock_session, mock_user): + productor_body = productor_factory.productor_body_factory(name='test productor update') + productor_update = productor_factory.productor_update_factory(name='test productor update') + productor_result = productor_factory.productor_public_factory(name='test productor update') + + mock = mocker.patch.object( + service, + 'update_one', + return_value=productor_result + ) + + response = client.put('/api/productors/2', json=productor_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test productor update' + mock.assert_called_once_with( + mock_session, + 2, + productor_update + ) + + def test_update_one_notfound(self, client, mocker, mock_session, mock_user): + productor_body = productor_factory.productor_body_factory(name='test productor update') + productor_update = productor_factory.productor_update_factory(name='test productor update') + productor_result = None + + mock = mocker.patch.object( + service, + 'update_one', + side_effect=exceptions.ProductorNotFoundError('Productor 1 not found') + ) + + response = client.put('/api/productors/2', json=productor_body) + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + productor_update + ) + + def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + productor_body = productor_factory.productor_body_factory(name='test productor update') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.productors.service.update_one') + response = client.put('/api/productors/2', json=productor_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + productor_result = productor_factory.productor_public_factory(name='test productor delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=productor_result + ) + + response = client.delete('/api/productors/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test productor delete' + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + productor_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + side_effect=exceptions.ProductorNotFoundError('Productor 1 not found') + ) + + response = client.delete('/api/productors/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + productor_body = productor_factory.productor_body_factory(name='test productor delete') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.productors.service.delete_one') + response = client.delete('/api/productors/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/routers/test_products.py b/backend/tests/routers/test_products.py new file mode 100644 index 0000000..56fd5b6 --- /dev/null +++ b/backend/tests/routers/test_products.py @@ -0,0 +1,264 @@ +import src.products.service as service +import src.products.exceptions as exceptions +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.products as product_factory + +from fastapi.exceptions import HTTPException + +class TestProducts: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + product_factory.product_public_factory(name="test 1", id=1), + product_factory.product_public_factory(name="test 2", id=2), + product_factory.product_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/products') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + [], + [], + [] + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + product_factory.product_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/products?types=1&names=test 2') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + ['test 2'], + [], + ['1'], + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.products.service.get_all') + response = client.get('/api/products') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = product_factory.product_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + response = client.get('/api/products/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + response = client.get('/api/products/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.products.service.get_one') + response = client.get('/api/products/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_create_one(self, client, mocker, mock_session, mock_user): + product_body = product_factory.product_body_factory(name='test product create') + product_create = product_factory.product_create_factory(name='test product create') + product_result = product_factory.product_public_factory(name='test product create') + + mock = mocker.patch.object( + service, + 'create_one', + return_value=product_result + ) + + response = client.post('/api/products', json=product_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test product create' + mock.assert_called_once_with( + mock_session, + product_create + ) + + def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + product_body = product_factory.product_body_factory(name='test product create') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.products.service.create_one') + response = client.post('/api/products', json=product_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_update_one(self, client, mocker, mock_session, mock_user): + product_body = product_factory.product_body_factory(name='test product update') + product_update = product_factory.product_update_factory(name='test product update') + product_result = product_factory.product_public_factory(name='test product update') + + mock = mocker.patch.object( + service, + 'update_one', + return_value=product_result + ) + + response = client.put('/api/products/2', json=product_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test product update' + mock.assert_called_once_with( + mock_session, + 2, + product_update + ) + + def test_update_one_notfound(self, client, mocker, mock_session, mock_user): + product_body = product_factory.product_body_factory(name='test product update') + product_update = product_factory.product_update_factory(name='test product update') + product_result = None + + mock = mocker.patch.object( + service, + 'update_one', + side_effect=exceptions.ProductNotFoundError('Product not found') + ) + + response = client.put('/api/products/2', json=product_body) + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + product_update + ) + + def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + product_body = product_factory.product_body_factory(name='test product update') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.products.service.update_one') + response = client.put('/api/products/2', json=product_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + product_result = product_factory.product_public_factory(name='test product delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=product_result + ) + + response = client.delete('/api/products/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test product delete' + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + product_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + side_effect=exceptions.ProductNotFoundError('Product not found') + ) + + response = client.delete('/api/products/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + product_body = product_factory.product_body_factory(name='test product delete') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.products.service.delete_one') + response = client.delete('/api/products/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/routers/test_shipments.py b/backend/tests/routers/test_shipments.py new file mode 100644 index 0000000..49a3810 --- /dev/null +++ b/backend/tests/routers/test_shipments.py @@ -0,0 +1,263 @@ +import src.shipments.service as service +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.shipments as shipment_factory + +from fastapi.exceptions import HTTPException + +class TestShipments: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + shipment_factory.shipment_public_factory(name="test 1", id=1), + shipment_factory.shipment_public_factory(name="test 2", id=2), + shipment_factory.shipment_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/shipments') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + [], + [], + [], + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + shipment_factory.shipment_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/shipments?dates=2025-10-10&names=test 2&forms=contract form 1') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + mock_user, + ['test 2'], + ['2025-10-10'], + ['contract form 1'], + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.shipments.service.get_all') + response = client.get('/api/shipments') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = shipment_factory.shipment_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + response = client.get('/api/shipments/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + response = client.get('/api/shipments/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.shipments.service.get_one') + response = client.get('/api/shipments/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_create_one(self, client, mocker, mock_session, mock_user): + shipment_body = shipment_factory.shipment_body_factory(name='test shipment create') + shipment_create = shipment_factory.shipment_create_factory(name='test shipment create') + shipment_result = shipment_factory.shipment_public_factory(name='test shipment create') + + mock = mocker.patch.object( + service, + 'create_one', + return_value=shipment_result + ) + + response = client.post('/api/shipments', json=shipment_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test shipment create' + mock.assert_called_once_with( + mock_session, + shipment_create + ) + + def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + shipment_body = shipment_factory.shipment_body_factory(name='test shipment create') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.shipments.service.create_one') + response = client.post('/api/shipments', json=shipment_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_update_one(self, client, mocker, mock_session, mock_user): + shipment_body = shipment_factory.shipment_body_factory(name='test shipment update') + shipment_update = shipment_factory.shipment_update_factory(name='test shipment update') + shipment_result = shipment_factory.shipment_public_factory(name='test shipment update') + + mock = mocker.patch.object( + service, + 'update_one', + return_value=shipment_result + ) + + response = client.put('/api/shipments/2', json=shipment_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test shipment update' + mock.assert_called_once_with( + mock_session, + 2, + shipment_update + ) + + def test_update_one_notfound(self, client, mocker, mock_session, mock_user): + shipment_body = shipment_factory.shipment_body_factory(name='test shipment update') + shipment_update = shipment_factory.shipment_update_factory(name='test shipment update') + shipment_result = None + + mock = mocker.patch.object( + service, + 'update_one', + return_value=shipment_result + ) + + response = client.put('/api/shipments/2', json=shipment_body) + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + shipment_update + ) + + def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + shipment_body = shipment_factory.shipment_body_factory(name='test shipment update') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.shipments.service.update_one') + response = client.put('/api/shipments/2', json=shipment_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + shipment_result = shipment_factory.shipment_public_factory(name='test shipment delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=shipment_result + ) + + response = client.delete('/api/shipments/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test shipment delete' + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + shipment_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=shipment_result + ) + + response = client.delete('/api/shipments/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + shipment_body = shipment_factory.shipment_body_factory(name='test shipment delete') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.shipments.service.delete_one') + response = client.delete('/api/shipments/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/routers/test_users.py b/backend/tests/routers/test_users.py new file mode 100644 index 0000000..9409b28 --- /dev/null +++ b/backend/tests/routers/test_users.py @@ -0,0 +1,259 @@ +import src.users.service as service +import src.models as models +from src.main import app +from src.auth.auth import get_current_user +import tests.factories.users as user_factory + +from fastapi.exceptions import HTTPException + +class TestUsers: + def test_get_all(self, client, mocker, mock_session, mock_user): + mock_results = [ + user_factory.user_public_factory(name="test 1", id=1), + user_factory.user_public_factory(name="test 2", id=2), + user_factory.user_public_factory(name="test 3", id=3), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/users') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 1 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + [], + [], + ) + def test_get_all_filters(self, client, mocker, mock_session, mock_user): + mock_results = [ + user_factory.user_public_factory(name="test 2", id=2), + ] + mock = mocker.patch.object( + service, + 'get_all', + return_value=mock_results + ) + + response = client.get('/api/users?emails=test@test.test&names=test 2') + response_data = response.json() + assert response.status_code == 200 + assert response_data[0]['id'] == 2 + assert len(response_data) == len(mock_results) + mock.assert_called_once_with( + mock_session, + ['test 2'], + ['test@test.test'], + ) + + def test_get_all_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.users.service.get_all') + response = client.get('/api/users') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_get_one(self, client, mocker, mock_session, mock_user): + mock_result = user_factory.user_public_factory(name="test 2", id=2) + + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + + response = client.get('/api/users/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['id'] == 2 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_notfound(self, client, mocker, mock_session, mock_user): + mock_result = None + mock = mocker.patch.object( + service, + 'get_one', + return_value=mock_result + ) + response = client.get('/api/users/2') + response_data = response.json() + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2 + ) + + def test_get_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.users.service.get_one') + response = client.get('/api/users/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_create_one(self, client, mocker, mock_session, mock_user): + user_body = user_factory.user_body_factory(name='test user create') + user_create = user_factory.user_create_factory(name='test user create') + user_result = user_factory.user_public_factory(name='test user create') + + mock = mocker.patch.object( + service, + 'create_one', + return_value=user_result + ) + + response = client.post('/api/users', json=user_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test user create' + mock.assert_called_once_with( + mock_session, + user_create + ) + + def test_create_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + user_body = user_factory.user_body_factory(name='test user create') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.users.service.create_one') + response = client.post('/api/users', json=user_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_update_one(self, client, mocker, mock_session, mock_user): + user_body = user_factory.user_body_factory(name='test user update') + user_update = user_factory.user_update_factory(name='test user update') + user_result = user_factory.user_public_factory(name='test user update') + + mock = mocker.patch.object( + service, + 'update_one', + return_value=user_result + ) + + response = client.put('/api/users/2', json=user_body) + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test user update' + mock.assert_called_once_with( + mock_session, + 2, + user_update + ) + + def test_update_one_notfound(self, client, mocker, mock_session, mock_user): + user_body = user_factory.user_body_factory(name='test user update') + user_update = user_factory.user_update_factory(name='test user update') + user_result = None + + mock = mocker.patch.object( + service, + 'update_one', + return_value=user_result + ) + + response = client.put('/api/users/2', json=user_body) + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + user_update + ) + + def test_update_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + user_body = user_factory.user_body_factory(name='test user update') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.users.service.update_one') + response = client.put('/api/users/2', json=user_body) + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() + + def test_delete_one(self, client, mocker, mock_session, mock_user): + user_result = user_factory.user_public_factory(name='test user delete') + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=user_result + ) + + response = client.delete('/api/users/2') + response_data = response.json() + + assert response.status_code == 200 + assert response_data['name'] == 'test user delete' + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_notfound(self, client, mocker, mock_session, mock_user): + user_result = None + + mock = mocker.patch.object( + service, + 'delete_one', + return_value=user_result + ) + + response = client.delete('/api/users/2') + response_data = response.json() + + assert response.status_code == 404 + mock.assert_called_once_with( + mock_session, + 2, + ) + + def test_delete_one_unauthorized(self, client, mocker, mock_session, mock_user): + def unauthorized(): + raise HTTPException(status_code=401) + user_body = user_factory.user_body_factory(name='test user delete') + + app.dependency_overrides[get_current_user] = unauthorized + + mock = mocker.patch('src.users.service.delete_one') + response = client.delete('/api/users/2') + + assert response.status_code == 401 + mock.assert_not_called() + + app.dependency_overrides.clear() \ No newline at end of file diff --git a/backend/tests/services/test_forms_service.py b/backend/tests/services/test_forms_service.py new file mode 100644 index 0000000..839de8e --- /dev/null +++ b/backend/tests/services/test_forms_service.py @@ -0,0 +1,154 @@ +import pytest +from sqlmodel import Session + +import src.models as models +import src.forms.service as forms_service +import src.forms.exceptions as forms_exceptions +import tests.factories.forms as forms_factory + +class TestFormsService: + def test_get_all_forms(self, session: Session, forms: list[models.FormPublic]): + result = forms_service.get_all(session, [], [], False) + + assert len(result) == 2 + assert result == forms + + def test_get_all_forms_filter_productors(self, session: Session, forms: list[models.FormPublic]): + result = forms_service.get_all(session, [], ['test productor'], False) + + assert len(result) == 2 + assert result == forms + + def test_get_all_forms_filter_season(self, session: Session, forms: list[models.FormPublic]): + result = forms_service.get_all(session, ['test season 1'], [], False) + + assert len(result) == 1 + + def test_get_all_forms_all_filters(self, session: Session, forms: list[models.FormPublic]): + result = forms_service.get_all(session, ['test season 1'], ['test productor'], True) + + assert result == forms + + def test_get_one_form(self, session: Session, forms: list[models.FormPublic]): + result = forms_service.get_one(session, forms[0].id) + + assert result == forms[0] + + def test_get_one_form_notfound(self, session: Session): + result = forms_service.get_one(session, 122) + + assert result == None + + def test_create_form( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic + ): + form_create = forms_factory.form_create_factory( + name="new test form", + productor_id=productor.id, + referer=referer.id, + season="new test season", + ) + result = forms_service.create_one(session, form_create) + + assert result.id is not None + assert result.name == "new test form" + assert result.productor.name == "test productor" + + def test_create_form_invalidinput( + self, + session: Session, + productor: models.Productor + ): + form_create = None + with pytest.raises(forms_exceptions.FormCreateError): + result = forms_service.create_one(session, form_create) + + form_create = forms_factory.form_create_factory(productor_id=123) + with pytest.raises(forms_exceptions.ProductorNotFoundError): + result = forms_service.create_one(session, form_create) + + form_create = forms_factory.form_create_factory( + productor_id=productor.id, + referer_id=123 + ) + with pytest.raises(forms_exceptions.UserNotFoundError): + result = forms_service.create_one(session, form_create) + + + def test_update_form( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic, + forms: list[models.FormPublic] + ): + form_update = forms_factory.form_update_factory( + name='updated test form', + productor_id=productor.id, + referer_id=referer.id, + season='updated test season' + ) + form_id = forms[0].id + result = forms_service.update_one(session, form_id, form_update) + + assert result.id == form_id + assert result.name == 'updated test form' + assert result.season == 'updated test season' + + def test_update_form_notfound( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic, + ): + form_update = forms_factory.form_update_factory( + name='updated test form', + productor_id=productor.id, + referer_id=referer.id, + season='updated test season' + ) + form_id = 123 + with pytest.raises(forms_exceptions.FormNotFoundError): + result = forms_service.update_one(session, form_id, form_update) + + def test_update_form_invalidinput( + self, + session: Session, + productor: models.ProductorPublic, + forms: list[models.FormPublic] + ): + form_id = forms[0].id + form_update = forms_factory.form_update_factory(productor_id=123) + with pytest.raises(forms_exceptions.ProductorNotFoundError): + result = forms_service.update_one(session, form_id, form_update) + + form_update = forms_factory.form_update_factory( + productor_id=productor.id, + referer_id=123 + ) + with pytest.raises(forms_exceptions.UserNotFoundError): + result = forms_service.update_one(session, form_id, form_update) + + def test_delete_form( + self, + session: Session, + forms: list[models.FormPublic] + ): + form_id = forms[0].id + result = forms_service.delete_one(session, form_id) + + check = forms_service.get_one(session, form_id) + assert check == None + + def test_delete_form_notfound( + self, + session: Session, + forms: list[models.FormPublic] + ): + form_id = 123 + with pytest.raises(forms_exceptions.FormNotFoundError): + result = forms_service.delete_one(session, form_id) + diff --git a/backend/tests/services/test_productors_service.py b/backend/tests/services/test_productors_service.py new file mode 100644 index 0000000..21616b8 --- /dev/null +++ b/backend/tests/services/test_productors_service.py @@ -0,0 +1,143 @@ +import pytest +from sqlmodel import Session + +import src.models as models +import src.productors.service as productors_service +import src.productors.exceptions as productors_exceptions +import tests.factories.productors as productors_factory + +class TestProductorsService: + def test_get_all_productors( + self, + session: Session, + productors: list[models.ProductorPublic], + user: models.UserPublic + ): + result = productors_service.get_all(session, user, [], []) + + assert len(result) == 1 + assert result == [productors[0]] + + def test_get_all_productors_filter_names( + self, + session: Session, + productors: list[models.ProductorPublic], + user: models.UserPublic + ): + result = productors_service.get_all( + session, + user, + ['test productor 1'], + [] + ) + + assert len(result) == 1 + + def test_get_all_productors_filter_types( + self, + session: Session, + productors: list[models.ProductorPublic], + user: models.UserPublic + ): + result = productors_service.get_all( + session, + user, + [], + ['Légumineuses'], + ) + + assert len(result) == 1 + + def test_get_all_productors_all_filters( + self, + session: Session, + productors: list[models.ProductorPublic], + user: models.UserPublic + ): + result = productors_service.get_all( + session, + user, + ['test productor 1'], + ['Légumineuses'], + ) + + assert len(result) == 1 + + def test_get_one_productor(self, session: Session, productors: list[models.ProductorPublic]): + result = productors_service.get_one(session, productors[0].id) + + assert result == productors[0] + + def test_get_one_productor_notfound(self, session: Session): + result = productors_service.get_one(session, 122) + + assert result == None + + def test_create_productor( + self, + session: Session, + referer: models.ProductorPublic + ): + productor_create = productors_factory.productor_create_factory( + name="new test productor", + ) + result = productors_service.create_one(session, productor_create) + + assert result.id is not None + assert result.name == "new test productor" + + def test_create_productor_invalidinput( + self, + session: Session, + ): + productor_create = None + with pytest.raises(productors_exceptions.ProductorCreateError): + result = productors_service.create_one(session, productor_create) + + def test_update_productor( + self, + session: Session, + referer: models.ProductorPublic, + productors: list[models.ProductorPublic] + ): + productor_update = productors_factory.productor_update_factory( + name='updated test productor', + ) + productor_id = productors[0].id + result = productors_service.update_one(session, productor_id, productor_update) + + assert result.id == productor_id + assert result.name == 'updated test productor' + + def test_update_productor_notfound( + self, + session: Session, + referer: models.ProductorPublic, + ): + productor_update = productors_factory.productor_update_factory( + name='updated test productor', + ) + productor_id = 123 + with pytest.raises(productors_exceptions.ProductorNotFoundError): + result = productors_service.update_one(session, productor_id, productor_update) + + def test_delete_productor( + self, + session: Session, + productors: list[models.ProductorPublic] + ): + productor_id = productors[0].id + result = productors_service.delete_one(session, productor_id) + + check = productors_service.get_one(session, productor_id) + assert check == None + + def test_delete_productor_notfound( + self, + session: Session, + productors: list[models.ProductorPublic] + ): + productor_id = 123 + with pytest.raises(productors_exceptions.ProductorNotFoundError): + result = productors_service.delete_one(session, productor_id) + diff --git a/backend/tests/services/test_products_service.py b/backend/tests/services/test_products_service.py new file mode 100644 index 0000000..edaae0c --- /dev/null +++ b/backend/tests/services/test_products_service.py @@ -0,0 +1,191 @@ +import pytest +from sqlmodel import Session + +import src.models as models +import src.products.service as products_service +import src.products.exceptions as products_exceptions +import tests.factories.products as products_factory + +class TestProductsService: + def test_get_all_products( + self, + session: Session, + products: list[models.ProductPublic], + user: models.UserPublic + ): + result = products_service.get_all(session, user, [], [], []) + + assert len(result) == 2 + assert result == products + + def test_get_all_products_filter_productors( + self, + session: Session, + products: list[models.ProductPublic], + user: models.UserPublic + ): + result = products_service.get_all( + session, + user, + [], + ['test productor'], + [] + ) + + assert len(result) == 2 + assert result == products + + def test_get_all_products_filter_names( + self, + session: Session, + products: list[models.ProductPublic], + user: models.UserPublic + ): + result = products_service.get_all( + session, + user, + ['product 1 occasionnal'], + [], + [] + ) + + assert len(result) == 1 + + def test_get_all_products_filter_types( + self, + session: Session, + products: list[models.ProductPublic], + user: models.UserPublic + ): + result = products_service.get_all( + session, + user, + [], + [], + ['1'] + ) + + assert len(result) == 1 + + def test_get_all_products_all_filters( + self, + session: Session, + products: list[models.ProductPublic], + user: models.UserPublic + ): + result = products_service.get_all( + session, + user, + ['product 1 occasionnal'], + ['test productor'], + ['1'] + ) + + assert len(result) == 1 + + def test_get_one_product(self, session: Session, products: list[models.ProductPublic]): + result = products_service.get_one(session, products[0].id) + + assert result == products[0] + + def test_get_one_product_notfound(self, session: Session): + result = products_service.get_one(session, 122) + + assert result == None + + def test_create_product( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic + ): + product_create = products_factory.product_create_factory( + name="new test product", + productor_id=productor.id, + ) + result = products_service.create_one(session, product_create) + + assert result.id is not None + assert result.name == "new test product" + assert result.productor.name == "test productor" + + def test_create_product_invalidinput( + self, + session: Session, + productor: models.Productor + ): + product_create = None + with pytest.raises(products_exceptions.ProductCreateError): + result = products_service.create_one(session, product_create) + + product_create = products_factory.product_create_factory(productor_id=123) + with pytest.raises(products_exceptions.ProductorNotFoundError): + result = products_service.create_one(session, product_create) + + def test_update_product( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic, + products: list[models.ProductPublic] + ): + product_update = products_factory.product_update_factory( + name='updated test product', + productor_id=productor.id, + ) + product_id = products[0].id + result = products_service.update_one(session, product_id, product_update) + + assert result.id == product_id + assert result.name == 'updated test product' + + def test_update_product_notfound( + self, + session: Session, + productor: models.ProductorPublic, + referer: models.ProductorPublic, + ): + product_update = products_factory.product_update_factory( + name='updated test product', + productor_id=productor.id, + ) + product_id = 123 + with pytest.raises(products_exceptions.ProductNotFoundError): + result = products_service.update_one(session, product_id, product_update) + + def test_update_product_invalidinput( + self, + session: Session, + productor: models.ProductorPublic, + products: list[models.ProductPublic] + ): + product_id = products[0].id + product_update = products_factory.product_update_factory(productor_id=123) + with pytest.raises(products_exceptions.ProductorNotFoundError): + result = products_service.update_one(session, product_id, product_update) + + product_update = products_factory.product_update_factory( + productor_id=productor.id, + referer_id=123 + ) + + def test_delete_product( + self, + session: Session, + products: list[models.ProductPublic] + ): + product_id = products[0].id + result = products_service.delete_one(session, product_id) + + check = products_service.get_one(session, product_id) + assert check == None + + def test_delete_product_notfound( + self, + session: Session, + products: list[models.ProductPublic] + ): + product_id = 123 + with pytest.raises(products_exceptions.ProductNotFoundError): + result = products_service.delete_one(session, product_id) +