feat: complete label management (name->id resolution, update/remove)
Resolves the long-standing problem that label tools passed names while Gitea's API requires numeric label ids. - gitea_client: add _resolve_label_ids() helper; create_issue and add_labels now resolve label names to ids (case-insensitive) and raise a clear "Unknown label(s)" error instead of a generic 500. - New tools: remove_labels (by name) and update_label (located by current name). - Register both write tools and document the name-based label contract. - Tests: resolver mapping + unknown-label error, add_labels id translation, update_label and remove_labels handlers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -118,7 +118,7 @@ async def test_public_methods_delegate_to_request_and_normalize() -> None:
|
||||
if endpoint == "/api/v1/repos/acme/demo/pulls/2":
|
||||
return {"number": 2}
|
||||
if endpoint == "/api/v1/repos/acme/demo/labels":
|
||||
return [{"name": "bug"}]
|
||||
return [{"id": 1, "name": "bug"}]
|
||||
if endpoint == "/api/v1/repos/acme/demo/tags":
|
||||
return [{"name": "v1"}]
|
||||
if endpoint == "/api/v1/repos/acme/demo/releases":
|
||||
@@ -246,6 +246,51 @@ async def test_list_user_repositories_unknown_user_returns_empty() -> None:
|
||||
assert await client.list_user_repositories("ghost") == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_label_ids_maps_names_case_insensitively() -> None:
|
||||
"""Label names are resolved to ids regardless of case."""
|
||||
client = GiteaClient(token="user-token")
|
||||
|
||||
async def fake_request(method: str, endpoint: str, **kwargs):
|
||||
return [{"id": 3, "name": "Bug"}, {"id": 9, "name": "wontfix"}]
|
||||
|
||||
client._request = AsyncMock(side_effect=fake_request) # type: ignore[method-assign]
|
||||
ids = await client._resolve_label_ids("o", "r", ["bug", "WONTFIX"], correlation_id="c")
|
||||
assert ids == [3, 9]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_label_ids_rejects_unknown_label() -> None:
|
||||
"""An unknown label name raises a clear error instead of a silent failure."""
|
||||
client = GiteaClient(token="user-token")
|
||||
|
||||
async def fake_request(method: str, endpoint: str, **kwargs):
|
||||
return [{"id": 3, "name": "bug"}]
|
||||
|
||||
client._request = AsyncMock(side_effect=fake_request) # type: ignore[method-assign]
|
||||
with pytest.raises(GiteaError, match="Unknown label"):
|
||||
await client._resolve_label_ids("o", "r", ["ghost"], correlation_id="c")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_labels_resolves_names_to_ids() -> None:
|
||||
"""add_labels translates names to ids before POSTing to Gitea."""
|
||||
client = GiteaClient(token="user-token")
|
||||
captured: dict = {}
|
||||
|
||||
async def fake_request(method: str, endpoint: str, **kwargs):
|
||||
if endpoint.endswith("/labels") and method == "GET":
|
||||
return [{"id": 42, "name": "bug"}]
|
||||
if endpoint.endswith("/issues/1/labels") and method == "POST":
|
||||
captured["body"] = kwargs.get("json_body")
|
||||
return {"labels": [{"name": "bug"}]}
|
||||
return {}
|
||||
|
||||
client._request = AsyncMock(side_effect=fake_request) # type: ignore[method-assign]
|
||||
await client.add_labels("o", "r", 1, ["bug"])
|
||||
assert captured["body"] == {"labels": [42]}
|
||||
|
||||
|
||||
def test_git_refs_allow_slash_containing_refs() -> None:
|
||||
"""Legitimate refs that contain '/' validate successfully."""
|
||||
tree = FileTreeArgs(owner="o", repo="r", ref="feature/foo")
|
||||
|
||||
Reference in New Issue
Block a user