- Extends GET /api/v1/tags with sort=count_desc and min_count query params - New TagsComponent at /tags (public, no auth guard) shows all tags sorted by image count - Clicking a tag navigates to /?tags=<name> for a pre-filtered library view - LibraryComponent reads ?tags= query param on init to support deep-linking from tag browser - Library header gains a "Browse tags" link to /tags for discoverability - All 15 TDD tasks complete; ruff, ng lint, and ng build clean Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""
|
|
T041 — GET /api/v1/images?tags=cat,funny → only images with both tags
|
|
T042 — same query excludes images with only one matching tag
|
|
"""
|
|
import io
|
|
|
|
import pytest
|
|
|
|
|
|
def _minimal_gif() -> bytes:
|
|
return (
|
|
b"GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00"
|
|
b"!\xf9\x04\x00\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01"
|
|
b"\x00\x00\x02\x02D\x01\x00;"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_and_filter_returns_only_matching_images(client):
|
|
data = _minimal_gif()
|
|
|
|
# Image with both tags
|
|
r_both = await client.post(
|
|
"/api/v1/images",
|
|
files={"file": ("both.gif", io.BytesIO(data), "image/gif")},
|
|
data={"tags": "andcat,andfunny"},
|
|
)
|
|
both_id = r_both.json()["id"]
|
|
|
|
# Image with only one of the two tags — use different content to avoid dedup
|
|
r_one = await client.post(
|
|
"/api/v1/images",
|
|
files={"file": ("one.gif", io.BytesIO(data + b"\x00"), "image/gif")},
|
|
data={"tags": "andcat"},
|
|
)
|
|
|
|
response = await client.get("/api/v1/images?tags=andcat,andfunny")
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
ids = [item["id"] for item in body["items"]]
|
|
assert both_id in ids
|
|
assert r_one.json()["id"] not in ids
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_filter_excludes_partial_tag_match(client):
|
|
data = _minimal_gif()
|
|
|
|
# Image with only "exclcat"
|
|
r_partial = await client.post(
|
|
"/api/v1/images",
|
|
files={"file": ("partial.gif", io.BytesIO(data + b"\x01"), "image/gif")},
|
|
data={"tags": "exclcat"},
|
|
)
|
|
|
|
# Filter requires both exclcat and exclother
|
|
response = await client.get("/api/v1/images?tags=exclcat,exclother")
|
|
assert response.status_code == 200
|
|
body = response.json()
|
|
ids = [item["id"] for item in body["items"]]
|
|
assert r_partial.json()["id"] not in ids
|