from unittest.mock import AsyncMock, patch from fastapi.testclient import TestClient from app.main import app client = TestClient(app) # Bytes JPEG minimaux valides (header JFIF) FAKE_IMAGE = ( b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" b"\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t" b"\x08\n\x0c\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a" b"\x1f\x1e\x1d\x1a\x1c\x1c $.' \",#\x1c\x1c(7),01444\x1f'9=82<.342\x1e" b"\xff\xc0\x00\x0b\x08\x00\x01\x00\x01\x01\x01\x11\x00" b"\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00" b"\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b" b"\xff\xda\x00\x08\x01\x01\x00\x00?\x00\xf5\x00\xff\xd9" ) def test_identify_returns_plantnet_results(): """PlantNet disponible → retourne ses résultats avec source=plantnet.""" fake_results = [ { "species": "Solanum lycopersicum", "common_name": "Tomate", "confidence": 0.95, "image_url": "", } ] with ( patch("app.services.redis_cache.get", return_value=None), patch("app.services.redis_cache.set"), patch( "app.services.plantnet.identify", new_callable=AsyncMock, return_value=fake_results, ), ): resp = client.post( "/api/identify", files={"file": ("test.jpg", FAKE_IMAGE, "image/jpeg")}, ) assert resp.status_code == 200 data = resp.json() assert data["source"] == "plantnet" assert len(data["results"]) == 1 assert data["results"][0]["species"] == "Solanum lycopersicum" assert data["results"][0]["confidence"] == 0.95 def test_identify_falls_back_to_yolo_when_plantnet_fails(): """PlantNet indisponible (retourne []) → fallback YOLO avec source=yolo.""" fake_yolo = [ { "species": "Tomato healthy", "common_name": "Tomate", "confidence": 0.80, "image_url": "", } ] with ( patch("app.services.redis_cache.get", return_value=None), patch("app.services.redis_cache.set"), patch( "app.services.plantnet.identify", new_callable=AsyncMock, return_value=[], ), patch( "app.services.yolo_service.identify", new_callable=AsyncMock, return_value=fake_yolo, ), ): resp = client.post( "/api/identify", files={"file": ("test.jpg", FAKE_IMAGE, "image/jpeg")}, ) assert resp.status_code == 200 data = resp.json() assert data["source"] == "yolo" assert len(data["results"]) == 1 assert data["results"][0]["common_name"] == "Tomate" def test_identify_uses_cache(): """Cache Redis hit → retourne les données en cache sans appeler PlantNet.""" cached = [ { "species": "Rosa canina", "common_name": "Églantier", "confidence": 0.9, "image_url": "", } ] with patch("app.services.redis_cache.get", return_value=cached): resp = client.post( "/api/identify", files={"file": ("test.jpg", FAKE_IMAGE, "image/jpeg")}, ) assert resp.status_code == 200 data = resp.json() assert data["source"] == "cache" assert data["results"][0]["species"] == "Rosa canina" def test_identify_returns_empty_when_both_fail(): """PlantNet et YOLO indisponibles → retourne résultats vides.""" with ( patch("app.services.redis_cache.get", return_value=None), patch("app.services.redis_cache.set"), patch( "app.services.plantnet.identify", new_callable=AsyncMock, return_value=[], ), patch( "app.services.yolo_service.identify", new_callable=AsyncMock, return_value=[], ), ): resp = client.post( "/api/identify", files={"file": ("test.jpg", FAKE_IMAGE, "image/jpeg")}, ) assert resp.status_code == 200 data = resp.json() assert data["source"] == "yolo" assert data["results"] == []