"""Tests unitaires des outils du catalogue. Ces tests sont FOURNIS COMPLETS — ils servent de spécification. Ils doivent tous passer une fois vos outils implémentés. Lancer : uv run pytest tests/unit/test_tools.py -v Progression recommandée : 1. Implémenter search_product → faire passer TestSearchProduct 2. Implémenter calculate_price → faire passer TestCalculatePrice 3. Implémenter list_products → faire passer TestListProducts 4. Implémenter check_stock (bonus) → faire passer TestCheckStock """ import pytest from src.tools.catalogue import ( calculate_price, check_stock, list_products, search_product, ) # ── search_product ──────────────────────────────────────────────────────────── class TestSearchProduct: def test_found_by_partial_name(self) -> None: result = search_product.invoke({"name": "laptop"}) assert "Laptop Pro 15" in result assert "1299" in result def test_found_case_insensitive(self) -> None: assert "Souris Ergonomique" in search_product.invoke({"name": "SOURIS"}) assert "Souris Ergonomique" in search_product.invoke({"name": "souris"}) def test_not_found_returns_message(self) -> None: result = search_product.invoke({"name": "produit-xyz-inexistant"}) assert "Aucun produit" in result def test_search_by_description_word(self) -> None: result = search_product.invoke({"name": "ergonomique"}) assert "Souris Ergonomique" in result def test_multiple_results(self) -> None: # "casque" doit trouver au moins le casque audio result = search_product.invoke({"name": "casque"}) assert "Casque Audio Pro" in result # ── calculate_price ─────────────────────────────────────────────────────────── class TestCalculatePrice: def test_price_no_discount(self) -> None: result = calculate_price.invoke({"product_name": "souris", "quantity": 2}) assert "99.80" in result # 2 × 49.90 def test_price_with_discount(self) -> None: result = calculate_price.invoke({ "product_name": "souris", "quantity": 2, "discount_percent": 10.0 }) assert "89.82" in result # 99.80 - 10% def test_quantity_one(self) -> None: result = calculate_price.invoke({"product_name": "clavier", "quantity": 1}) assert "129" in result def test_invalid_quantity_zero(self) -> None: result = calculate_price.invoke({"product_name": "souris", "quantity": 0}) assert "positif" in result.lower() def test_invalid_discount_over_100(self) -> None: result = calculate_price.invoke({ "product_name": "souris", "quantity": 1, "discount_percent": 150.0 }) assert "100" in result def test_product_not_found(self) -> None: result = calculate_price.invoke({"product_name": "inexistant", "quantity": 1}) assert "introuvable" in result.lower() def test_out_of_stock_warning(self) -> None: # enceinte-bluetooth est en stock=0 result = calculate_price.invoke({"product_name": "enceinte", "quantity": 1}) assert "rupture" in result.lower() or "RUPTURE" in result # ── list_products ───────────────────────────────────────────────────────────── class TestListProducts: def test_list_all(self) -> None: result = list_products.invoke({}) assert "Laptop Pro 15" in result assert "Casque Audio Pro" in result def test_filter_informatique(self) -> None: result = list_products.invoke({"category": "informatique"}) assert "Laptop Pro 15" in result assert "Souris" not in result def test_filter_audio(self) -> None: result = list_products.invoke({"category": "audio"}) assert "Casque Audio Pro" in result assert "Laptop" not in result def test_filter_case_insensitive(self) -> None: assert "Casque" in list_products.invoke({"category": "AUDIO"}) def test_unknown_category(self) -> None: result = list_products.invoke({"category": "meuble"}) assert "meuble" in result.lower() def test_out_of_stock_shown(self) -> None: result = list_products.invoke({}) assert "RUPTURE" in result # enceinte-bluetooth stock=0 # ── check_stock (bonus) ──────────────────────────────────────────────────────── class TestCheckStock: def test_sufficient_stock(self) -> None: result = check_stock.invoke({"product_name": "souris", "required_quantity": 5}) assert "possible" in result.lower() or "✅" in result def test_insufficient_stock(self) -> None: # écran-4k a stock=3, on en demande 10 result = check_stock.invoke({"product_name": "ecran", "required_quantity": 10}) assert "insuffisant" in result.lower() or "⚠" in result def test_out_of_stock(self) -> None: result = check_stock.invoke({"product_name": "enceinte", "required_quantity": 1}) assert "RUPTURE" in result or "impossible" in result.lower() def test_invalid_quantity(self) -> None: result = check_stock.invoke({"product_name": "souris", "required_quantity": -1}) assert "positif" in result.lower() def test_product_not_found(self) -> None: result = check_stock.invoke({"product_name": "xyz", "required_quantity": 1}) assert "introuvable" in result.lower()