CI / lint (push) Successful in 2s
CI / extension-version (push) Successful in 2s
Build images / sign-extension (push) Successful in 3s
Build images / build-agent (push) Successful in 6s
CI / frontend-build (push) Successful in 21s
CI / backend-lint-and-test (push) Successful in 33s
Build images / build-web (push) Successful in 1m2s
Build images / smoke-web (push) Skipped
Build images / build-ml (push) Successful in 1m54s
Build images / promote (push) Skipped
CI / integration (push) Successful in 2m20s
Operator: "the filters in the latest feed, drop down but don't have values". Two separate causes: - Platform: PostsFilterBar built its items from `platformsStore.platforms`. The platforms store has never had that property; it exposes `list` and `byKey`. The read returned undefined, `|| []` turned that into an empty list, and nothing failed. ArtistsView had copied the same read, so the Browse → Artists platform filter was empty too. Both now read `list` and show platform names rather than raw keys. - Artist: the autocomplete searched the server only after something was typed (autocomplete returns [] for an empty query by design, which its tests pin). Opening the dropdown therefore showed an empty menu. PostsFilterBar now loads every artist once from a new lightweight `GET /api/artists/names` (id, name, slug; alphabetical; no joins) and filters client-side, so the list is there on open. A deep-linked artist_id now also shows the artist's real name instead of "Artist #id". Guard: frontend/test/storeUsage.spec.js scans src for `platformsStore.<name>` and fails on any name the store doesn't define, since the frontend CI has no type-checker to catch this. A positive control shows the shipped `platformsStore.platforms` read is flagged, and a vacuity check confirms the scan really walks the tree. tests/test_api_artists_create.py covers /names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
88 lines
3.2 KiB
Python
88 lines
3.2 KiB
Python
import pytest
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_artist_returns_201(client):
|
|
resp = await client.post("/api/artists", json={"name": "Alice"})
|
|
assert resp.status_code == 201
|
|
body = await resp.get_json()
|
|
assert body["name"] == "Alice"
|
|
assert body["slug"] == "alice"
|
|
assert body["created"] is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_artist_idempotent(client):
|
|
a = await client.post("/api/artists", json={"name": "Bob"})
|
|
b = await client.post("/api/artists", json={"name": "Bob"})
|
|
assert (await a.get_json())["created"] is True
|
|
body_b = await b.get_json()
|
|
assert body_b["created"] is False
|
|
assert body_b["id"] == (await a.get_json())["id"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_artist_rejects_empty(client):
|
|
resp = await client.post("/api/artists", json={"name": " "})
|
|
assert resp.status_code == 400
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_autocomplete_returns_matches(client):
|
|
await client.post("/api/artists", json={"name": "Alice"})
|
|
await client.post("/api/artists", json={"name": "Alice Cooper"})
|
|
await client.post("/api/artists", json={"name": "Bob"})
|
|
resp = await client.get("/api/artists/autocomplete?q=alic")
|
|
assert resp.status_code == 200
|
|
names = [a["name"] for a in await resp.get_json()]
|
|
assert "Alice" in names
|
|
assert "Alice Cooper" in names
|
|
assert "Bob" not in names
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_autocomplete_empty_query(client):
|
|
resp = await client.get("/api/artists/autocomplete?q=")
|
|
assert resp.status_code == 200
|
|
assert await resp.get_json() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_patch_renames_display_name_only(client):
|
|
# #130: PATCH renames the display name; slug unchanged (paths safe).
|
|
created = await (await client.post("/api/artists", json={"name": "12345678"})).get_json()
|
|
resp = await client.patch(
|
|
f"/api/artists/{created['id']}", json={"name": "Kurotsuchi Machi"}
|
|
)
|
|
assert resp.status_code == 200
|
|
body = await resp.get_json()
|
|
assert body["name"] == "Kurotsuchi Machi"
|
|
assert body["slug"] == created["slug"] # slug frozen
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_patch_rename_validation(client):
|
|
created = await (await client.post("/api/artists", json={"name": "Nn"})).get_json()
|
|
assert (await client.patch(
|
|
f"/api/artists/{created['id']}", json={"name": " "}
|
|
)).status_code == 400
|
|
assert (await client.patch(
|
|
f"/api/artists/{created['id']}", json={})).status_code == 400
|
|
assert (await client.patch(
|
|
"/api/artists/999999", json={"name": "Ghost"})).status_code == 404
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_names_lists_every_artist_alphabetically(client):
|
|
"""The Latest feed's artist filter shows this list before anything is typed.
|
|
`autocomplete` stays empty for an empty query; this is the full list."""
|
|
for name in ("zed", "Alice", "bob"):
|
|
await client.post("/api/artists", json={"name": name})
|
|
resp = await client.get("/api/artists/names")
|
|
assert resp.status_code == 200
|
|
body = await resp.get_json()
|
|
assert [a["name"] for a in body] == ["Alice", "bob", "zed"]
|
|
assert set(body[0]) == {"id", "name", "slug"}
|