diff --git a/test/kohakuhub/api/repo/routers/test_crud.py b/test/kohakuhub/api/repo/routers/test_crud.py index a245227..a71d86d 100644 --- a/test/kohakuhub/api/repo/routers/test_crud.py +++ b/test/kohakuhub/api/repo/routers/test_crud.py @@ -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", diff --git a/test/kohakuhub/api/test_huggingface_hub_deep.py b/test/kohakuhub/api/test_huggingface_hub_deep.py index 0118e75..c3b2a7f 100644 --- a/test/kohakuhub/api/test_huggingface_hub_deep.py +++ b/test/kohakuhub/api/test_huggingface_hub_deep.py @@ -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 ):