fix: update backend tests to match actual API - all 41 tests passing
This commit is contained in:
@@ -1,67 +1,62 @@
|
||||
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, mock_gemini):
|
||||
def test_gemini_extraction(self, test_client, user_token, mock_gemini):
|
||||
"""Test AI extraction using Gemini."""
|
||||
response = test_client.post(
|
||||
"/api/ai/extract",
|
||||
json={
|
||||
"image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"provider": "gemini"
|
||||
}
|
||||
"/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
|
||||
data = response.json()
|
||||
assert "name" in data
|
||||
assert data["name"] == "Test Item"
|
||||
assert data["part_number"] == "PN-12345"
|
||||
|
||||
def test_claude_extraction(self, test_client, mock_claude):
|
||||
def test_claude_extraction(self, test_client, user_token, mock_claude):
|
||||
"""Test AI extraction using Claude."""
|
||||
response = test_client.post(
|
||||
"/api/ai/extract",
|
||||
json={
|
||||
"image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"provider": "claude"
|
||||
}
|
||||
"/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
|
||||
data = response.json()
|
||||
assert data["name"] == "Test Item"
|
||||
|
||||
def test_extraction_box_mode(self, test_client, mock_gemini):
|
||||
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(
|
||||
"/api/ai/extract",
|
||||
json={
|
||||
"image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
||||
"provider": "gemini",
|
||||
"mode": "box"
|
||||
}
|
||||
"/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_provider(self, test_client):
|
||||
"""Test that invalid provider fails."""
|
||||
def test_extraction_invalid_file_type(self, test_client, user_token):
|
||||
"""Test that invalid file type is rejected."""
|
||||
response = test_client.post(
|
||||
"/api/ai/extract",
|
||||
json={
|
||||
"image_base64": "invalid",
|
||||
"provider": "invalid_provider"
|
||||
}
|
||||
"/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_400_BAD_REQUEST
|
||||
assert response.status_code == status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
|
||||
|
||||
def test_extraction_missing_image(self, test_client):
|
||||
"""Test that missing image fails."""
|
||||
def test_extraction_missing_file(self, test_client, user_token):
|
||||
"""Test that missing file returns 422."""
|
||||
response = test_client.post(
|
||||
"/api/ai/extract",
|
||||
json={"provider": "gemini"}
|
||||
"/items/extract-label",
|
||||
headers={"Authorization": f"Bearer {user_token}"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
@@ -69,17 +64,27 @@ class TestAIExtraction:
|
||||
class TestAIValidation:
|
||||
"""Test AI extraction validation (user confirmation before save)."""
|
||||
|
||||
def test_validate_extraction(self, test_client, test_db):
|
||||
"""Test that extracted data requires user validation."""
|
||||
def test_extraction_requires_auth(self, test_client):
|
||||
"""Test that extraction endpoint requires authentication."""
|
||||
response = test_client.post(
|
||||
"/api/ai/extract",
|
||||
json={
|
||||
"image_base64": "xyz",
|
||||
"provider": "gemini",
|
||||
"save": False
|
||||
}
|
||||
"/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
|
||||
data = response.json()
|
||||
assert "extracted_data" in data
|
||||
assert "user_confirmation_required" in data or data.get("save") == False
|
||||
|
||||
# Item count should be unchanged — extraction never auto-saves
|
||||
final_count = test_db.query(Item).count()
|
||||
assert initial_count == final_count
|
||||
|
||||
Reference in New Issue
Block a user