test(hf-e2e): move visibility coverage tests under huggingface_hub flow

The previous coverage commit added raw-HTTP tests in test_crud.py for
the visibility="public" + 400 unknown-visibility branches. Per the HF
compatibility rule (AGENTS.md §5.1: prefer verification through the
real huggingface_hub Python client), reshape so the public branch is
exercised by HfApi.create_repo(repo_id, private=False) — v0 cells
take the explicit-private path, v1 cells take the
visibility="public" path, both observably collapse to a public repo.
The 400 branch keeps using raw HTTP because huggingface_hub validates
visibility client-side (RepoVisibility_T Literal) and refuses to
send unknown values; that branch is documented in the test docstring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
narugo1992
2026-05-06 21:12:15 +08:00
parent 7fc4f20a55
commit 6a4511f836
2 changed files with 66 additions and 35 deletions

View File

@@ -24,41 +24,6 @@ async def test_create_repository_and_reject_normalized_duplicate(owner_client):
assert body["repo_id"] == "owner/sandbox-repo"
async def test_create_repo_visibility_public_creates_public_repo(owner_client):
"""``visibility="public"`` (huggingface_hub>=1.x shape with
``private=False``) must round-trip as a public repo. Pairs with the
``visibility="private"`` path covered through the live hf-client e2e
test in ``test_huggingface_hub_deep.py`` — together they exercise
both branches of the visibility resolver in ``crud.py`` so the
patch-coverage gate stays clean."""
response = await owner_client.post(
"/api/repos/create",
json={"type": "model", "name": "visibility-public-repo", "visibility": "public"},
)
assert response.status_code == 200
assert response.json()["repo_id"] == "owner/visibility-public-repo"
info = await owner_client.get("/api/models/owner/visibility-public-repo")
assert info.status_code == 200
assert info.json()["private"] is False
async def test_create_repo_unknown_visibility_returns_400(owner_client):
"""The resolver must reject visibility values it cannot map to a
private bool. Without this guard a typo like ``"hidden"`` would be
silently treated as public — masking client-side bugs and breaking
HF API symmetry with ``update_repo_settings``."""
response = await owner_client.post(
"/api/repos/create",
json={"type": "model", "name": "visibility-bogus-repo", "visibility": "hidden"},
)
assert response.status_code == 400
body = response.json()
error_text = body.get("detail", {}).get("error", "") if isinstance(body.get("detail"), dict) else str(body)
assert "visibility" in error_text.lower()
assert "public" in error_text and "private" in error_text
async def test_admin_can_delete_empty_org_repository(admin_client, owner_client):
create_response = await owner_client.post(
"/api/repos/create",

View File

@@ -836,6 +836,72 @@ async def test_create_repo_private_flag_is_honored_across_hf_versions(
await _run(outsider_api.repo_info, repo_id)
async def test_create_repo_private_false_round_trips_as_public(
live_server_url, hf_api_token
):
"""``HfApi.create_repo(repo_id, private=False)`` must produce a
public repo on every client version.
The two on-the-wire shapes are also asymmetric on the public side:
* ``huggingface_hub<1`` sends ``{"private": false}`` directly,
hitting the explicit-``private`` branch of the backend resolver.
* ``huggingface_hub>=1.x`` resolves ``private=False`` into
``{"visibility": "public"}`` (see ``_resolve_repo_visibility`` in
``hf_api.py``), hitting the ``visibility=="public"`` branch
instead.
Routing through the real client on the matrix exercises *both*
branches across the CI matrix — v0 cells cover ``private``
resolution and v1 cells cover ``visibility`` resolution — so a
regression in either branch surfaces here.
"""
api = _api(live_server_url, hf_api_token)
repo_id = "owner/hf-deep-create-public"
await _run(api.create_repo, repo_id, private=False)
info = await _run(api.repo_info, repo_id)
assert info.private is False, (
"create_repo(private=False) produced a private repo — the "
"backend mis-resolved the public path. v0 sends 'private=False' "
"and v1 sends 'visibility=public'; both must collapse to public."
)
async def test_create_repo_rejects_unknown_visibility_value(
live_server_url, hf_api_token
):
"""The visibility resolver must reject values it cannot map to a
private bool with a 400 (and a stable error message).
The real ``huggingface_hub`` client validates ``visibility``
client-side (``RepoVisibility_T = Literal["public", "private",
"protected"]`` in ``hf_api.py``) and refuses to send a typo like
``"hidden"`` over the wire — so the only way to drive this
server-side branch is a direct HTTP request alongside the
hf-client e2e flow. Without this guard a typo would silently
produce a public repo, masking client-side bugs and breaking
symmetry with the same guard in ``update_repo_settings``.
"""
async with httpx.AsyncClient(timeout=10) as client:
response = await client.post(
f"{live_server_url}/api/repos/create",
json={
"type": "model",
"name": "hf-deep-create-bogus-visibility",
"visibility": "hidden",
},
headers={"Authorization": f"Bearer {hf_api_token}"},
)
assert response.status_code == 400, response.text
body = response.json()
detail = body.get("detail") if isinstance(body, dict) else None
error_text = detail.get("error", "") if isinstance(detail, dict) else str(body)
assert "visibility" in error_text.lower()
assert "public" in error_text and "private" in error_text
async def test_update_repo_settings_visibility_field_is_honored(
live_server_url, hf_api_token
):