feat(gallery): hidden-view review endpoints — list + keep + un-hide (#141 step 5)
GET /api/gallery/hidden-review lists unresolved presentation auto-hide flags (image + presentation tag + conflict tag/score), most-concerning first. POST .../keep resolves the flag (the tag stays). POST .../unhide removes the presentation tag (image returns to the gallery), records a TagSuggestionRejection so the head learns it misfired, and resolves the flag. Tests for list/keep/unhide. Frontend review strip (shown when Show-hidden is on) next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""Hidden-view review endpoints (#141): list flagged auto-hides + keep / un-hide."""
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from backend.app.models import (
|
||||
ImageRecord,
|
||||
PresentationReview,
|
||||
Tag,
|
||||
TagKind,
|
||||
TagSuggestionRejection,
|
||||
)
|
||||
from backend.app.models.tag import image_tag
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
async def _setup_flag(db):
|
||||
banner = (await db.execute(
|
||||
select(Tag).where(Tag.is_system.is_(True), Tag.name == "banner")
|
||||
)).scalar_one()
|
||||
content = Tag(name="looksreal", kind=TagKind.general)
|
||||
db.add(content)
|
||||
img = ImageRecord(
|
||||
path="/images/rv.jpg", sha256="rv" * 32, size_bytes=1, mime="image/jpeg",
|
||||
width=1, height=1, origin="imported_filesystem",
|
||||
integrity_status="unknown",
|
||||
)
|
||||
db.add(img)
|
||||
await db.flush()
|
||||
await db.execute(image_tag.insert().values(
|
||||
image_record_id=img.id, tag_id=banner.id, source="presentation_auto"))
|
||||
db.add(PresentationReview(
|
||||
image_record_id=img.id, tag_id=banner.id,
|
||||
conflict_tag_id=content.id, conflict_score=0.8,
|
||||
))
|
||||
await db.commit()
|
||||
return img, banner, content
|
||||
|
||||
|
||||
async def _source(db, image_id, tag_id):
|
||||
return (await db.execute(
|
||||
select(image_tag.c.source).where(
|
||||
image_tag.c.image_record_id == image_id,
|
||||
image_tag.c.tag_id == tag_id,
|
||||
)
|
||||
)).scalar_one_or_none()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hidden_review_lists_flag(client, db):
|
||||
img, banner, content = await _setup_flag(db)
|
||||
body = await (await client.get("/api/gallery/hidden-review")).get_json()
|
||||
assert len(body["items"]) == 1
|
||||
it = body["items"][0]
|
||||
assert it["image_id"] == img.id
|
||||
assert it["tag_id"] == banner.id
|
||||
assert it["conflict_tag_id"] == content.id
|
||||
assert it["conflict_name"] == "looksreal"
|
||||
assert it["conflict_score"] == 0.8
|
||||
assert it["thumbnail_url"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hidden_review_keep_resolves_but_keeps_tag(client, db):
|
||||
img, banner, _ = await _setup_flag(db)
|
||||
resp = await client.post(
|
||||
f"/api/gallery/hidden-review/{img.id}/{banner.id}/keep")
|
||||
assert resp.status_code == 204
|
||||
body = await (await client.get("/api/gallery/hidden-review")).get_json()
|
||||
assert body["items"] == [] # flag resolved
|
||||
assert await _source(db, img.id, banner.id) == "presentation_auto" # tag stays
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_hidden_review_unhide_removes_tag_and_records_rejection(client, db):
|
||||
img, banner, _ = await _setup_flag(db)
|
||||
resp = await client.post(
|
||||
f"/api/gallery/hidden-review/{img.id}/{banner.id}/unhide")
|
||||
assert resp.status_code == 204
|
||||
assert await _source(db, img.id, banner.id) is None # back in the gallery
|
||||
rej = (await db.execute(
|
||||
select(TagSuggestionRejection).where(
|
||||
TagSuggestionRejection.image_record_id == img.id,
|
||||
TagSuggestionRejection.tag_id == banner.id,
|
||||
)
|
||||
)).scalar_one_or_none()
|
||||
assert rej is not None # head learns it misfired
|
||||
body = await (await client.get("/api/gallery/hidden-review")).get_json()
|
||||
assert body["items"] == []
|
||||
Reference in New Issue
Block a user