mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-12 08:51:53 +00:00
test(cron): cover provider "custom" → providers.custom resolution
Add execution-time coverage that bare `provider="custom"` resolves a literal providers.custom endpoint (and still falls through when none exists), plus creation-time coverage that `_resolve_model_override` keeps a resolvable "custom" and only pins the main provider when it is unresolvable.
This commit is contained in:
parent
acd4f34e65
commit
f7a6d6a6a1
2 changed files with 121 additions and 0 deletions
|
|
@ -712,6 +712,76 @@ def test_named_custom_provider_uses_saved_credentials(monkeypatch):
|
|||
assert resolved["source"] == "custom_provider:Local"
|
||||
|
||||
|
||||
def test_bare_custom_resolves_providers_dict_entry_named_custom(monkeypatch):
|
||||
"""A request for bare ``provider="custom"`` must resolve a literal
|
||||
``providers.custom`` entry (e.g. a cliproxy endpoint) instead of falling
|
||||
through to the global default. Regression for cron jobs stored with
|
||||
``provider: "custom"`` failing with ``auth_unavailable: providers=codex``.
|
||||
"""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"load_config",
|
||||
lambda: {
|
||||
"providers": {
|
||||
"custom": {
|
||||
"api": "https://cliproxy.example.com/v1",
|
||||
"api_key": "cliproxy-key",
|
||||
"default_model": "gpt-5.4",
|
||||
"name": "CLIProxy",
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
# Reaching resolve_provider for bare custom with a matching entry means the
|
||||
# named-custom path was bypassed — that is the bug we are fixing.
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"resolve_provider",
|
||||
lambda *a, **k: (_ for _ in ()).throw(
|
||||
AssertionError(
|
||||
"resolve_provider must not be called; providers.custom should match"
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["base_url"] == "https://cliproxy.example.com/v1"
|
||||
assert resolved["api_key"] == "cliproxy-key"
|
||||
assert resolved["requested_provider"] == "custom"
|
||||
|
||||
|
||||
def test_bare_custom_without_named_entry_still_falls_through(monkeypatch):
|
||||
"""No literal providers.custom entry → bare custom keeps the legacy
|
||||
model.base_url trust-path behavior, unchanged by the fix."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {
|
||||
"provider": "openrouter",
|
||||
"base_url": "http://127.0.0.1:8082/v1",
|
||||
"default": "my-local-model",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"load_config",
|
||||
lambda: {"providers": {"some-other-proxy": {"api": "https://x.example/v1"}}},
|
||||
)
|
||||
monkeypatch.delenv("CUSTOM_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["base_url"] == "http://127.0.0.1:8082/v1"
|
||||
|
||||
|
||||
def test_named_custom_provider_uses_providers_dict_when_list_missing(monkeypatch):
|
||||
"""After v11→v12 migration deletes custom_providers, resolution should
|
||||
still find entries in the providers dict via get_compatible_custom_providers."""
|
||||
|
|
|
|||
|
|
@ -452,3 +452,54 @@ class TestUnifiedCronjobTool:
|
|||
assert updated["success"] is True
|
||||
stored = get_job(created["job_id"])
|
||||
assert stored["deliver"] == "telegram"
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# Per-job model/provider override resolution
|
||||
# =========================================================================
|
||||
|
||||
from tools.cronjob_tools import _resolve_model_override # noqa: E402
|
||||
|
||||
|
||||
class TestResolveModelOverride:
|
||||
"""`_resolve_model_override` must not silently hijack a job that meant to
|
||||
use a configured custom endpoint (e.g. ``providers.custom`` → cliproxy).
|
||||
Regression for cron jobs with ``provider: "custom"`` falling back to codex.
|
||||
"""
|
||||
|
||||
def test_keeps_bare_custom_when_a_named_entry_exists(self, monkeypatch):
|
||||
import hermes_cli.runtime_provider as rp_mod
|
||||
|
||||
monkeypatch.setattr(rp_mod, "has_named_custom_provider", lambda name: True)
|
||||
provider, model = _resolve_model_override(
|
||||
{"provider": "custom", "model": "gpt-5.4"}
|
||||
)
|
||||
assert provider == "custom"
|
||||
assert model == "gpt-5.4"
|
||||
|
||||
def test_pins_main_provider_when_bare_custom_unresolvable(self, monkeypatch):
|
||||
import hermes_cli.config as cfg_mod
|
||||
import hermes_cli.runtime_provider as rp_mod
|
||||
|
||||
monkeypatch.setattr(rp_mod, "has_named_custom_provider", lambda name: False)
|
||||
monkeypatch.setattr(
|
||||
cfg_mod, "load_config", lambda: {"model": {"provider": "openai-codex"}}
|
||||
)
|
||||
provider, model = _resolve_model_override(
|
||||
{"provider": "custom", "model": "gpt-5.4"}
|
||||
)
|
||||
# No matching custom entry → fall back to pinning the main provider.
|
||||
assert provider == "openai-codex"
|
||||
assert model == "gpt-5.4"
|
||||
|
||||
def test_keeps_explicit_custom_name_unchanged(self, monkeypatch):
|
||||
import hermes_cli.runtime_provider as rp_mod
|
||||
|
||||
# Even if the resolver claims no entry, the canonical "custom:<name>"
|
||||
# form is never stripped or pinned.
|
||||
monkeypatch.setattr(rp_mod, "has_named_custom_provider", lambda name: False)
|
||||
provider, model = _resolve_model_override(
|
||||
{"provider": "custom:cliproxy", "model": "gpt-5.4"}
|
||||
)
|
||||
assert provider == "custom:cliproxy"
|
||||
assert model == "gpt-5.4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue