"""Tests fonctions API produits avec mocks.""" from datetime import datetime from unittest.mock import MagicMock, patch import pytest from fastapi import HTTPException from sqlalchemy.exc import IntegrityError, SQLAlchemyError from pricewatch.app.api.main import ( create_product, get_product, update_product, delete_product, list_prices, create_price, update_price, delete_price, ) from pricewatch.app.api.schemas import ProductCreate, ProductUpdate, PriceHistoryCreate, PriceHistoryUpdate class MockProduct: """Mock Product model.""" def __init__(self, **kwargs): self.id = kwargs.get("id", 1) self.source = kwargs.get("source", "amazon") self.reference = kwargs.get("reference", "REF123") self.url = kwargs.get("url", "https://example.com") self.title = kwargs.get("title", "Test Product") self.category = kwargs.get("category") self.description = kwargs.get("description") self.currency = kwargs.get("currency", "EUR") self.msrp = kwargs.get("msrp") self.first_seen_at = kwargs.get("first_seen_at", datetime.now()) self.last_updated_at = kwargs.get("last_updated_at", datetime.now()) class MockPrice: """Mock PriceHistory model.""" def __init__(self, **kwargs): self.id = kwargs.get("id", 1) self.product_id = kwargs.get("product_id", 1) self.price = kwargs.get("price", 99.99) self.shipping_cost = kwargs.get("shipping_cost") self.stock_status = kwargs.get("stock_status", "in_stock") self.fetch_method = kwargs.get("fetch_method", "http") self.fetch_status = kwargs.get("fetch_status", "success") self.fetched_at = kwargs.get("fetched_at", datetime.now()) class TestCreateProduct: """Tests create_product.""" def test_create_success(self): """Cree un produit avec succes.""" session = MagicMock() session.add = MagicMock() session.commit = MagicMock() session.refresh = MagicMock() payload = ProductCreate( source="amazon", reference="NEW123", url="https://amazon.fr/dp/NEW123", title="New Product", currency="EUR", ) with patch("pricewatch.app.api.main.Product") as MockProductClass: mock_product = MockProduct(reference="NEW123") MockProductClass.return_value = mock_product with patch("pricewatch.app.api.main._product_to_out") as mock_to_out: mock_to_out.return_value = MagicMock() result = create_product(payload, session) session.add.assert_called_once() session.commit.assert_called_once() def test_create_duplicate(self): """Cree un produit duplique leve 409.""" session = MagicMock() session.add = MagicMock() session.commit = MagicMock(side_effect=IntegrityError("duplicate", {}, None)) session.rollback = MagicMock() payload = ProductCreate( source="amazon", reference="DUPE", url="https://amazon.fr/dp/DUPE", title="Duplicate", currency="EUR", ) with patch("pricewatch.app.api.main.Product"): with pytest.raises(HTTPException) as exc_info: create_product(payload, session) assert exc_info.value.status_code == 409 def test_create_db_error(self): """Erreur DB leve 500.""" session = MagicMock() session.add = MagicMock() session.commit = MagicMock(side_effect=SQLAlchemyError("db error")) session.rollback = MagicMock() payload = ProductCreate( source="amazon", reference="ERR", url="https://amazon.fr/dp/ERR", title="Error", currency="EUR", ) with patch("pricewatch.app.api.main.Product"): with pytest.raises(HTTPException) as exc_info: create_product(payload, session) assert exc_info.value.status_code == 500 class TestGetProduct: """Tests get_product.""" def test_get_not_found(self): """Produit non trouve leve 404.""" session = MagicMock() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = None session.query.return_value = mock_query with pytest.raises(HTTPException) as exc_info: get_product(99999, session) assert exc_info.value.status_code == 404 class TestUpdateProduct: """Tests update_product.""" def test_update_not_found(self): """Update produit non trouve leve 404.""" session = MagicMock() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = None session.query.return_value = mock_query payload = ProductUpdate(title="Updated") with pytest.raises(HTTPException) as exc_info: update_product(99999, payload, session) assert exc_info.value.status_code == 404 def test_update_db_error(self): """Erreur DB lors d'update leve 500.""" session = MagicMock() mock_product = MockProduct() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = mock_product session.query.return_value = mock_query session.commit = MagicMock(side_effect=SQLAlchemyError("error")) session.rollback = MagicMock() payload = ProductUpdate(title="Updated") with pytest.raises(HTTPException) as exc_info: update_product(1, payload, session) assert exc_info.value.status_code == 500 class TestDeleteProduct: """Tests delete_product.""" def test_delete_not_found(self): """Delete produit non trouve leve 404.""" session = MagicMock() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = None session.query.return_value = mock_query with pytest.raises(HTTPException) as exc_info: delete_product(99999, session) assert exc_info.value.status_code == 404 def test_delete_success(self): """Delete produit avec succes.""" session = MagicMock() mock_product = MockProduct() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = mock_product session.query.return_value = mock_query session.delete = MagicMock() session.commit = MagicMock() result = delete_product(1, session) assert result == {"status": "deleted"} session.delete.assert_called_once() def test_delete_db_error(self): """Erreur DB lors de delete leve 500.""" session = MagicMock() mock_product = MockProduct() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = mock_product session.query.return_value = mock_query session.delete = MagicMock() session.commit = MagicMock(side_effect=SQLAlchemyError("error")) session.rollback = MagicMock() with pytest.raises(HTTPException) as exc_info: delete_product(1, session) assert exc_info.value.status_code == 500 class TestCreatePrice: """Tests create_price.""" def test_create_price_db_error(self): """Erreur DB lors de creation prix.""" session = MagicMock() session.add = MagicMock() session.commit = MagicMock(side_effect=SQLAlchemyError("error")) session.rollback = MagicMock() payload = PriceHistoryCreate( product_id=1, price=99.99, fetch_method="http", fetch_status="success", fetched_at=datetime.now(), ) with patch("pricewatch.app.api.main.PriceHistory"): with pytest.raises(HTTPException) as exc_info: create_price(payload, session) assert exc_info.value.status_code == 500 class TestUpdatePrice: """Tests update_price.""" def test_update_price_not_found(self): """Update prix non trouve leve 404.""" session = MagicMock() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = None session.query.return_value = mock_query payload = PriceHistoryUpdate(price=149.99) with pytest.raises(HTTPException) as exc_info: update_price(99999, payload, session) assert exc_info.value.status_code == 404 class TestDeletePrice: """Tests delete_price.""" def test_delete_price_not_found(self): """Delete prix non trouve leve 404.""" session = MagicMock() mock_query = MagicMock() mock_query.filter.return_value.one_or_none.return_value = None session.query.return_value = mock_query with pytest.raises(HTTPException) as exc_info: delete_price(99999, session) assert exc_info.value.status_code == 404