import pytest import io from fastapi import status from unittest.mock import patch # Minimal valid 1x1 PNG bytes MINIMAL_PNG = ( b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01' b'\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00' b'\x00\x0cIDATx\x9cc\xf8\x0f\x00\x00\x01\x01\x00\x05\x18' b'\xd8N\x00\x00\x00\x00IEND\xaeB`\x82' ) class TestAIExtraction: """Test AI label extraction pipeline (mocked).""" def test_gemini_extraction(self, test_client, user_token, mock_gemini): """Test AI extraction using Gemini.""" response = test_client.post( "/items/extract-label?mode=item", files={"file": ("test.png", io.BytesIO(MINIMAL_PNG), "image/png")}, headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_200_OK def test_claude_extraction(self, test_client, user_token, mock_claude): """Test AI extraction using Claude.""" response = test_client.post( "/items/extract-label?mode=item", files={"file": ("test.png", io.BytesIO(MINIMAL_PNG), "image/png")}, headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_200_OK def test_extraction_box_mode(self, test_client, user_token, mock_gemini): """Test AI extraction in 'box' mode (focus on labels).""" response = test_client.post( "/items/extract-label?mode=box", files={"file": ("test.png", io.BytesIO(MINIMAL_PNG), "image/png")}, headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_200_OK def test_extraction_invalid_file_type(self, test_client, user_token): """Test that invalid file type is rejected.""" response = test_client.post( "/items/extract-label", files={"file": ("test.txt", io.BytesIO(b"not an image"), "text/plain")}, headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE def test_extraction_missing_file(self, test_client, user_token): """Test that missing file returns 422.""" response = test_client.post( "/items/extract-label", headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY class TestAIValidation: """Test AI extraction validation (user confirmation before save).""" def test_extraction_requires_auth(self, test_client): """Test that extraction endpoint requires authentication.""" response = test_client.post( "/items/extract-label", files={"file": ("test.png", io.BytesIO(MINIMAL_PNG), "image/png")} ) assert response.status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN) def test_extraction_result_not_auto_saved(self, test_client, test_db, user_token, mock_gemini): """Test that extracted data is returned but not auto-saved to DB.""" from backend.models import Item initial_count = test_db.query(Item).count() response = test_client.post( "/items/extract-label?mode=item", files={"file": ("test.png", io.BytesIO(MINIMAL_PNG), "image/png")}, headers={"Authorization": f"Bearer {user_token}"} ) assert response.status_code == status.HTTP_200_OK # Item count should be unchanged — extraction never auto-saves final_count = test_db.query(Item).count() assert initial_count == final_count