mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(auth): fall back to global auth.json in _load_provider_state
In profile mode, _load_provider_state previously returned None when a provider was absent from the profile's auth.json — even if the user had authenticated at the global root. This broke runtime credential resolvers that read state directly (resolve_nous_access_token, resolve_nous_runtime_credentials), causing profiles without their own nous login to fail with 'Hermes is not logged into Nous Portal' despite a valid global session. Push the existing read-only global fallback (already used by get_provider_auth_state and read_credential_pool) into _load_provider_state so every caller benefits, and simplify get_provider_auth_state into a thin wrapper. Writes still target the profile only — profile state continues to shadow global state on the next read after a per-profile login. Behavior in classic (non-profile) mode is unchanged because _load_global_auth_store returns an empty dict. Adds 5 tests covering the new contract on _load_provider_state directly. Existing 770 auth/credential/nous tests still pass.
This commit is contained in:
parent
dd0d5d5a82
commit
414a5bc924
2 changed files with 123 additions and 15 deletions
|
|
@ -275,6 +275,98 @@ def test_provider_auth_state_returns_none_when_neither_has_it(profile_env):
|
|||
assert get_provider_auth_state("nous") is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _load_provider_state — internal global fallback (issue #18594 follow-up)
|
||||
#
|
||||
# Several runtime helpers (notably ``resolve_nous_runtime_credentials`` and
|
||||
# ``resolve_nous_access_token``) call ``_load_provider_state`` directly with
|
||||
# a profile-loaded auth store rather than going through
|
||||
# ``get_provider_auth_state``. Without the fallback wired into
|
||||
# ``_load_provider_state`` itself, those helpers raise ``"Hermes is not
|
||||
# logged into Nous Portal"`` even though the user has a valid global Nous
|
||||
# login. These tests pin the per-provider shadowing into the helper.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_load_provider_state_falls_back_to_global(profile_env):
|
||||
"""When the loaded profile store has no provider entry, fall back to global."""
|
||||
from hermes_cli.auth import _load_auth_store, _load_provider_state
|
||||
|
||||
_write(profile_env["global"] / "auth.json", _make_auth_store(providers={
|
||||
"nous": {"access_token": "global-nous-token", "refresh_token": "rt"},
|
||||
}))
|
||||
_write(profile_env["profile"] / "auth.json", _make_auth_store(providers={}))
|
||||
|
||||
auth_store = _load_auth_store()
|
||||
state = _load_provider_state(auth_store, "nous")
|
||||
assert state is not None
|
||||
assert state["access_token"] == "global-nous-token"
|
||||
|
||||
|
||||
def test_load_provider_state_profile_wins_over_global(profile_env):
|
||||
from hermes_cli.auth import _load_auth_store, _load_provider_state
|
||||
|
||||
_write(profile_env["global"] / "auth.json", _make_auth_store(providers={
|
||||
"nous": {"access_token": "global-token"},
|
||||
}))
|
||||
_write(profile_env["profile"] / "auth.json", _make_auth_store(providers={
|
||||
"nous": {"access_token": "profile-token"},
|
||||
}))
|
||||
|
||||
auth_store = _load_auth_store()
|
||||
state = _load_provider_state(auth_store, "nous")
|
||||
assert state is not None
|
||||
assert state["access_token"] == "profile-token"
|
||||
|
||||
|
||||
def test_load_provider_state_returns_none_when_neither_has_it(profile_env):
|
||||
from hermes_cli.auth import _load_auth_store, _load_provider_state
|
||||
|
||||
_write(profile_env["global"] / "auth.json", _make_auth_store(providers={}))
|
||||
_write(profile_env["profile"] / "auth.json", _make_auth_store(providers={}))
|
||||
|
||||
auth_store = _load_auth_store()
|
||||
assert _load_provider_state(auth_store, "nous") is None
|
||||
|
||||
|
||||
def test_load_provider_state_classic_mode_no_fallback(tmp_path, monkeypatch):
|
||||
"""In classic mode there is no global to fall back to; behavior is unchanged."""
|
||||
fake_home = tmp_path / "home"
|
||||
fake_home.mkdir()
|
||||
monkeypatch.setattr(Path, "home", lambda: fake_home)
|
||||
hermes_home = tmp_path / "classic"
|
||||
hermes_home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
_write(hermes_home / "auth.json", _make_auth_store(providers={
|
||||
"nous": {"access_token": "classic-token"},
|
||||
}))
|
||||
|
||||
from hermes_cli.auth import _load_auth_store, _load_provider_state
|
||||
|
||||
auth_store = _load_auth_store()
|
||||
state = _load_provider_state(auth_store, "nous")
|
||||
assert state is not None
|
||||
assert state["access_token"] == "classic-token"
|
||||
# Absent providers still return None.
|
||||
assert _load_provider_state(auth_store, "anthropic") is None
|
||||
|
||||
|
||||
def test_load_provider_state_malformed_global_does_not_break_profile(profile_env):
|
||||
"""A corrupt global auth.json must not break profile reads."""
|
||||
(profile_env["global"] / "auth.json").write_text("{not valid json")
|
||||
_write(profile_env["profile"] / "auth.json", _make_auth_store(providers={
|
||||
"nous": {"access_token": "profile-token"},
|
||||
}))
|
||||
|
||||
from hermes_cli.auth import _load_auth_store, _load_provider_state
|
||||
|
||||
auth_store = _load_auth_store()
|
||||
state = _load_provider_state(auth_store, "nous")
|
||||
assert state is not None
|
||||
assert state["access_token"] == "profile-token"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Classic mode — no fallback path should ever trigger
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue