Re-sign visual-match thumbnails on every detail fetch

Match rows stored a signed URL minted when the scan ran, so it aged out (or
used the pre-stable signing scheme) and the modal showed broken tiles even
after legacy signatures were fixed. Rows now carry item_id/j_id and the
detail serializer mints a fresh thumbnail URL per request; matches whose
item no longer exists are dropped.
This commit is contained in:
2026-09-23 21:33:20 -05:00
parent c73a81a5f4
commit a62195ffce
3 changed files with 84 additions and 0 deletions
@@ -287,6 +287,37 @@ class StagedUploadWorkflowTests(TestCase):
self.assertEqual(body["visual_matches"], [])
self.assertEqual(body["status"], TempUpload.STATUS_PENDING)
def test_detail_resigns_stored_visual_match_urls(self):
"""Stored matches carry only the item reference; URLs are re-minted.
Embedding the signed URL meant it expired (or used an older signing
scheme) and the modal showed alt text instead of thumbnails.
"""
client = self.api_client(self.uploader)
self.seed_library_item(client, "seed-resign")
item = MediaItem.objects.get()
temp = self.make_temp(self.uploader, "resign")
TempUpload.objects.filter(pk=temp.pk).update(
visual_matches=[
{
"item_id": item.id,
"j_id": f"J-{item.id}",
"filename": "seed-resign.png",
"similarity": 96.5,
"thumbnail_url": "/api/files/J-x/thumbnail/?sig=stale",
},
{"item_id": 999999, "j_id": "J-999999", "filename": "gone.png"},
]
)
body = client.get(f"/api/uploads/{temp.id}/").json()
self.assertEqual(len(body["visual_matches"]), 1)
match = body["visual_matches"][0]
self.assertEqual(match["j_id"], f"J-{item.id}")
self.assertEqual(match["similarity"], 96.5)
self.assertNotIn("stale", match["thumbnail_url"])
self.assertIn("/thumbnail/", match["thumbnail_url"])
self.assertIn(f"v={item.md5}", match["thumbnail_url"])
def test_visual_match_phase_rejects_completed_uploads(self):
client = self.api_client(self.uploader)
temp = self.make_temp(