Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion backend/app/database/face_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,13 @@ def db_get_images_by_cluster_id(

import json

metadata_dict = json.loads(metadata) if metadata else None
if metadata:
try:
metadata_dict = json.loads(metadata)
except json.JSONDecodeError:
metadata_dict = None
else:
metadata_dict = None
# Parse bbox JSON if it exists
bbox = None
if bbox_json:
Expand Down
15 changes: 14 additions & 1 deletion backend/app/routes/face_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,25 @@ def get_cluster_images(cluster_id: str):
images_data = db_get_images_by_cluster_id(cluster_id)

# Step 3: Convert to response models

# Step 3: Convert to response models
import json

def parse_metadata(metadata):
"""Parse metadata if stored as JSON string."""
if isinstance(metadata, str):
try:
metadata = json.loads(metadata)
except json.JSONDecodeError:
return None
return metadata if isinstance(metadata, dict) else None

images = [
ImageInCluster(
id=img["image_id"],
path=img["image_path"],
thumbnailPath=img["thumbnail_path"],
metadata=img["metadata"],
metadata=parse_metadata(img["metadata"]),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
face_id=img["face_id"],
confidence=img["confidence"],
bbox=img["bbox"],
Expand Down
Binary file added backend/test_db.sqlite3
Binary file not shown.
73 changes: 65 additions & 8 deletions backend/tests/test_face_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,75 @@ def test_get_cluster_images_empty(self, mock_get_cluster, mock_get_images):
assert data["data"]["total_images"] == 0
assert data["data"]["images"] == []

# ============================================================================
# Metadata JSON Parsing Tests (Fix for Issue #705)
# ============================================================================

@patch("app.routes.face_clusters.db_get_images_by_cluster_id")
@patch("app.routes.face_clusters.db_get_cluster_by_id")
def test_get_cluster_images_database_error(self, mock_get_cluster):
"""Test handling database errors during image retrieval."""
cluster_id = "cluster_123"
mock_get_cluster.side_effect = Exception("Database connection failed")
def test_metadata_string_is_parsed(self, mock_get_cluster, mock_get_images):
"""Metadata stored as JSON string should be parsed into a dict."""
mock_get_cluster.return_value = {"cluster_id": "cluster_123", "cluster_name": "John"}
mock_get_images.return_value = [{
"image_id": "img_1",
"image_path": "/path/to/image1.jpg",
"thumbnail_path": "/path/to/thumb1.jpg",
"metadata": '{"camera": "Canon EOS", "location": "NYC"}',
"face_id": 101,
"confidence": 0.95,
"bbox": {"x": 100, "y": 200, "width": 150, "height": 200},
}]

response = client.get(f"/face_clusters/{cluster_id}/images")
response = client.get("/face_clusters/cluster_123/images")
data = response.json()
image = data["data"]["images"][0]

assert response.status_code == 500
assert isinstance(image["metadata"], dict)
assert image["metadata"]["camera"] == "Canon EOS"

@patch("app.routes.face_clusters.db_get_images_by_cluster_id")
@patch("app.routes.face_clusters.db_get_cluster_by_id")
def test_metadata_dict_passes_through(self, mock_get_cluster, mock_get_images):
"""Metadata already as dict should stay unchanged."""
mock_get_cluster.return_value = {"cluster_id": "cluster_123", "cluster_name": "John"}
mock_get_images.return_value = [{
"image_id": "img_1",
"image_path": "/path/to/image1.jpg",
"thumbnail_path": "/path/to/thumb1.jpg",
"metadata": {"camera": "Canon EOS"},
"face_id": 101,
"confidence": 0.95,
"bbox": {"x": 100, "y": 200, "width": 150, "height": 200},
}]

response = client.get("/face_clusters/cluster_123/images")
data = response.json()
assert data["detail"]["success"] is False
assert data["detail"]["error"] == "Internal server error"
image = data["data"]["images"][0]

assert isinstance(image["metadata"], dict)
assert image["metadata"]["camera"] == "Canon EOS"

@patch("app.routes.face_clusters.db_get_images_by_cluster_id")
@patch("app.routes.face_clusters.db_get_cluster_by_id")
def test_invalid_metadata_returns_none(self, mock_get_cluster, mock_get_images):
"""Broken JSON metadata should return None without crashing."""
mock_get_cluster.return_value = {"cluster_id": "cluster_123", "cluster_name": "John"}
mock_get_images.return_value = [{
"image_id": "img_1",
"image_path": "/path/to/image1.jpg",
"thumbnail_path": "/path/to/thumb1.jpg",
"metadata": "{bad json!!!}",
"face_id": 101,
"confidence": 0.95,
"bbox": {"x": 100, "y": 200, "width": 150, "height": 200},
}]

response = client.get("/face_clusters/cluster_123/images")

assert response.status_code == 200
data = response.json()
assert data["data"]["images"][0]["metadata"] is None


# ============================================================================
# Additional Edge Case Tests
Expand Down
Loading