"""Units for the Hugging Face extension and its CLI surface. `huggingface_hub` is an optional extra, so the missing-dependency path is asserted by FORCING its absence (`sys.modules[name] = None` makes `import` raise) rather than by trusting the ambient environment. It was trusted once: these tests passed on a bare install and inverted silently the moment the `ui` extra arrived, because gradio depends on huggingface-hub. Everything else runs against stubs: no token, no network, no Hub. """ from __future__ import annotations import sys from typing import Any import pytest from typer.testing import CliRunner from mindxtrain.cli.main import app from mindxtrain.hf import extension as ext runner = CliRunner() @pytest.fixture(autouse=True) def no_ambient_token(monkeypatch: pytest.MonkeyPatch) -> None: """A developer's real HF_TOKEN must not change what these tests assert.""" for name in ext._HF_ENV: monkeypatch.delenv(name, raising=False) @pytest.fixture def hub_absent(monkeypatch: pytest.MonkeyPatch) -> None: """Force `import huggingface_hub` to fail, whatever is installed. A `None` entry in `sys.modules` makes the import raise ImportError, so the not-installed contract is testable on a machine where the extra IS installed. """ monkeypatch.setitem(sys.modules, "huggingface_hub", None) # --- token resolution -------------------------------------------------------------------- def test_explicit_token_beats_the_environment(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HF_TOKEN", "from-env") assert ext._token("explicit") == "explicit" def test_environment_names_are_tried_in_order(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HUGGINGFACEHUB_API_TOKEN", "third") assert ext._token() == "third" monkeypatch.setenv("HF_TOKEN", "first") assert ext._token() == "first" def test_no_token_anywhere_is_none() -> None: assert ext._token() is None # --- the optional dependency is reported, never raised ----------------------------------- def test_missing_dependency_is_a_result_not_an_exception(hub_absent: None) -> None: """A missing optional extra is reported as a result; it must never raise at the caller.""" api, err = ext._api("a-token") assert api is None assert err["ok"] is False assert "--extra hf" in err["reason"] # the extra that actually installs it def test_pull_base_reports_the_missing_dependency(hub_absent: None) -> None: out = ext.pull_base("HuggingFaceTB/SmolLM2-135M") assert out["ok"] is False and "huggingface_hub" in out["reason"] def test_no_token_names_the_variables_to_set(monkeypatch: pytest.MonkeyPatch) -> None: """With the library present but no token, the error must say which env vars are read.""" monkeypatch.setattr(ext, "_api", ext._api) # keep the real function fake_hub = type("m", (), {"HfApi": lambda **kw: None}) monkeypatch.setitem(__import__("sys").modules, "huggingface_hub", fake_hub) api, err = ext._api() assert api is None assert "HF_TOKEN" in err["reason"] # --- tree_paths: the RepoFolder trap ----------------------------------------------------- # The filter matches on the CLASS NAME, so these stubs must carry the Hub's exact names — # `tree_paths` deliberately avoids importing the optional type just to check it. class RepoFile: def __init__(self, path: str) -> None: self.path = path class RepoFolder: def __init__(self, path: str) -> None: self.path = path def test_tree_paths_skips_folders() -> None: """Folders and files both carry `.path`; counting folders as files corrupts any scan.""" api = type("A", (), {"list_repo_tree": lambda self, r, **kw: [ RepoFile("gen1/adapter.safetensors"), RepoFolder("gen1"), RepoFile("README.md"), ]})() assert ext.tree_paths(api, "PYTHAI/x") == ["gen1/adapter.safetensors", "README.md"] def test_tree_paths_on_a_tree_of_only_folders_is_empty() -> None: """The bug this guards: a scan reporting "nothing on the Hub" while the repo is full.""" api = type("A", (), {"list_repo_tree": lambda self, r, **kw: [RepoFolder("gen1"), RepoFolder("gen2")]})() assert ext.tree_paths(api, "PYTHAI/x") == [] # --- guards that must not need a network ------------------------------------------------- def test_warm_refuses_a_config_without_a_model(tmp_path) -> None: # type: ignore[no-untyped-def] cfg = tmp_path / "run.yaml" cfg.write_text("train:\n epochs: 1\n", encoding="utf-8") out = ext.warm(cfg) assert out["ok"] is False assert "model.name" in out["reason"] or "huggingface_hub" in out["reason"] def test_publish_refuses_a_missing_run_dir(tmp_path) -> None: # type: ignore[no-untyped-def] out = ext.publish_generation(tmp_path / "nope", "PYTHAI/x") assert out["ok"] is False and "no run dir" in out["reason"] # --- the CLI surface --------------------------------------------------------------------- def test_hf_is_registered_as_a_command_group() -> None: result = runner.invoke(app, ["--help"]) assert "hf" in result.stdout def test_every_hf_verb_is_reachable() -> None: result = runner.invoke(app, ["hf", "--help"]) for verb in ("whoami", "pull", "warm", "publish", "lineage", "dataset", "space"): assert verb in result.stdout def test_failure_exits_nonzero_so_a_script_can_branch(hub_absent: None) -> None: """The ascent loop warms a base and checks $? — an `ok:false` that exits 0 would be a trap.""" result = runner.invoke(app, ["hf", "whoami"]) assert result.exit_code == 1 assert "huggingface_hub" in result.stdout def test_success_exits_zero_and_prints_the_result(monkeypatch: pytest.MonkeyPatch) -> None: import mindxtrain.hf as hf_pkg monkeypatch.setattr(hf_pkg, "account", lambda tok=None: {"ok": True, "name": "Gregory-L", "can_write": ["PYTHAI"]}) result = runner.invoke(app, ["hf", "whoami"]) assert result.exit_code == 0 assert "Gregory-L" in result.stdout def test_token_option_is_passed_through(monkeypatch: pytest.MonkeyPatch) -> None: seen: dict[str, Any] = {} import mindxtrain.hf as hf_pkg def fake_account(tok=None): # type: ignore[no-untyped-def] seen["token"] = tok return {"ok": True} monkeypatch.setattr(hf_pkg, "account", fake_account) assert runner.invoke(app, ["hf", "whoami", "--token", "abc"]).exit_code == 0 assert seen["token"] == "abc"