Merge fallout (my resolution errors, all caught by CI): - hermes_cli/backup.py + gateway.py: `theirs` on those hunks re-imported clusters HEAD had already moved to backup_restore.py / kept in the facade. backup.py loses the 349-line duplicate (main's #110179 fix is ported into backup_restore._import_db_member); the systemd service-unit cluster returns to gateway.py (PM's _prepare_service_launcher / _pm_managed_node_dirs / _systemd_command have no home in main's extraction) with main's utf-8-sig read. gateway_service_unit.py is dropped. - gateway/run.py: main's plugin-update chore is not profile-scoped (the housekeeping ordering test pins the scope/drain sequence). - pyproject + 30 test files: `import yaml` -> `import hermes_yaml as yaml` (pm-clean has no pyyaml); gateway/config._bundled_platform_manifest_name reads through hermes_yaml. - tests re-seamed onto pm-clean's shape: residency admission (installed_engine), supervisor child env (binary is a constructor argument), update import guard (update_cmd_deps is gone; our probe already scrubs PYTHONPATH — both #115032 invariants pass), shallow-count git responses (stash path asks `status --porcelain -z`); dropped tests for retired code (_run_node_bootstrap/_ensure_tui_node, Windows resume demotion). - tests/tools/test_local_env_blocklist.py: restore the two helpers the suite-reduction commit dropped and the blocklist import. Real fixes: - pm: classify_uv_failure/ResolutionConflict move beside the uv runner (pm.environment, stdlib-only). pm.workspace imports tomllib at module level and cannot load on the 3.10 bootstrap python that streams uv output in the Docker arm64 image. - tools/browser_tool.warm_agent_browser_npx_cache: back as a permanent definition — it is on the frozen old-updater surface, and the revert-scheduled compat pointer does not count. - hermes_cli/memory_setup: the dashboard's pip row uses pm.environments. running_from_selected_environment for installed vs restart_required. - scripts/windows-build-deps.ps1: export DISTUTILS_USE_SDK/MSSdk so setuptools trusts the primed MSVC environment instead of asking vswhere (`env -i` test runner on win32-arm64 compiling ruamel-yaml-clib); run_tests.sh forwards them. - tests/pm/test_windows_build_deps.py: start the protocol test from a parent env without the toolchain variables the runner job already exports. - tests/conftest.py scrubs HERMES_BUNDLED_PLUGINS (Nix-wrapped hermes on the dev host); tests/home_io_guard.py treats sys.path site-packages under the real home as the interpreter's installation (PM-activated developer shell). - tests-js: four `curly` lint errors from main's new scripts.
59 lines
2.6 KiB
Python
59 lines
2.6 KiB
Python
"""Endpoint-less manual credentials resolve through the public runtime ladder."""
|
|
import pytest
|
|
import hermes_yaml as yaml
|
|
|
|
from hermes_cli.auth import PROVIDER_REGISTRY, write_credential_pool
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
from hermes_constants import get_hermes_home
|
|
|
|
|
|
KEY = "test-only-not-a-real-provider-key"
|
|
|
|
|
|
def _store_entry(provider, **fields):
|
|
write_credential_pool(provider, [{
|
|
"id": "test-no-endpoint", "source": "manual", "auth_type": "api_key",
|
|
"access_token": KEY, "priority": 0, **fields,
|
|
}])
|
|
|
|
|
|
@pytest.mark.parametrize("endpoint", [None, "", "default"])
|
|
@pytest.mark.parametrize("requested", ["opencode", "opencode-zen"])
|
|
def test_missing_pool_endpoint_uses_provider_default(endpoint, requested):
|
|
default = PROVIDER_REGISTRY["opencode-zen"].inference_base_url
|
|
fields = {} if endpoint is None else {"base_url": default if endpoint == "default" else endpoint}
|
|
_store_entry("opencode-zen", **fields)
|
|
runtime = resolve_runtime_provider(requested=requested, target_model="glm-5.3")
|
|
assert runtime["base_url"] == default
|
|
assert runtime["api_key"] == KEY
|
|
assert runtime["source"] == "manual"
|
|
assert runtime["api_mode"] == "chat_completions"
|
|
|
|
|
|
@pytest.mark.parametrize("entry_url", [None, "default", "https://entry.example/v1"])
|
|
@pytest.mark.parametrize("config_provider", ["deepseek", "openai"])
|
|
def test_pool_endpoint_preserves_entry_and_provider_config_boundaries(entry_url, config_provider):
|
|
default = PROVIDER_REGISTRY["deepseek"].inference_base_url
|
|
config_url = "https://config.example/v1"
|
|
(get_hermes_home() / "config.yaml").write_text(yaml.safe_dump({
|
|
"model": {"provider": config_provider, "base_url": config_url},
|
|
}), encoding="utf-8")
|
|
fields = {} if entry_url is None else {"base_url": default if entry_url == "default" else entry_url}
|
|
_store_entry("deepseek", **fields)
|
|
runtime = resolve_runtime_provider(requested="deepseek", target_model="deepseek-chat")
|
|
expected = entry_url if entry_url and entry_url != "default" else (
|
|
config_url if config_provider == "deepseek" else default
|
|
)
|
|
assert runtime["base_url"] == expected
|
|
assert runtime["api_key"] == KEY
|
|
assert runtime["source"] == "manual"
|
|
|
|
|
|
def test_missing_endpoint_still_finalizes_anthropic_wire():
|
|
_store_entry("opencode-zen")
|
|
runtime = resolve_runtime_provider(requested="opencode-zen", target_model="claude-sonnet-4-6")
|
|
default = PROVIDER_REGISTRY["opencode-zen"].inference_base_url
|
|
assert runtime["api_mode"] == "anthropic_messages"
|
|
assert runtime["base_url"] == default.removesuffix("/v1")
|
|
assert runtime["api_key"] == KEY
|