Files
hermes-agent/tests/test_cli_tool_availability.py
Alexander Whitestone 4a3068b3b5
Some checks failed
Contributor Attribution Check / check-attribution (pull_request) Successful in 39s
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 44s
Tests / e2e (pull_request) Successful in 2m53s
Tests / test (pull_request) Failing after 1h28m32s
test: add regression tests for issue #834 KeyError fix
2026-04-16 02:12:36 +00:00

136 lines
5.3 KiB
Python

"""
Regression test for issue #834: KeyError 'missing_vars' in CLI startup.
Verifies that:
1. check_tool_availability() returns dicts with 'env_vars' key
2. _show_tool_availability_warnings() handles the correct key names
3. No KeyError occurs when toolsets are unavailable
"""
import json
import sys
import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
# Ensure project root on path
sys.path.insert(0, str(Path(__file__).parent.parent))
from tools.registry import registry
class TestCheckToolAvailabilityKeys:
"""Verify check_tool_availability returns the expected dict keys."""
def test_unavailable_has_env_vars_key(self):
"""Unavailable toolsets must have 'env_vars', not 'missing_vars'."""
available, unavailable = registry.check_tool_availability(quiet=True)
for item in unavailable:
assert "env_vars" in item, (
f"Toolset '{item.get('name')}' missing 'env_vars' key. "
f"Keys present: {list(item.keys())}"
)
assert "name" in item, f"Missing 'name' key in: {item}"
assert "tools" in item, f"Missing 'tools' key in: {item}"
# This was the bug: cli.py accessed 'missing_vars' which doesn't exist
assert "missing_vars" not in item, (
f"Toolset '{item.get('name')}' has legacy 'missing_vars' key — "
f"should be 'env_vars'"
)
def test_unavailable_env_vars_is_list(self):
"""The 'env_vars' value should always be a list."""
_, unavailable = registry.check_tool_availability(quiet=True)
for item in unavailable:
assert isinstance(item.get("env_vars"), list), (
f"env_vars should be list, got {type(item.get('env_vars'))}"
)
def test_available_is_list_of_strings(self):
"""Available toolsets should be a list of toolset name strings."""
available, _ = registry.check_tool_availability(quiet=True)
assert isinstance(available, list)
for ts in available:
assert isinstance(ts, str), f"Toolset name should be string, got {type(ts)}"
class TestShowToolAvailabilityWarningsLogic:
"""Test the logic of _show_tool_availability_warnings without CLI overhead."""
def test_filter_logic_with_env_vars(self):
"""The filter logic from cli.py should work with 'env_vars' key."""
# Simulate what check_tool_availability returns
unavailable = [
{"name": "browser", "env_vars": ["BROWSERBASE_API_KEY"], "tools": ["browser_navigate"]},
{"name": "web", "env_vars": ["FIRECRAWL_API_KEY"], "tools": ["web_search"]},
{"name": "no_deps", "env_vars": [], "tools": ["some_tool"]},
]
# This is the fixed logic from cli.py L3614
api_key_missing = [u for u in unavailable if u.get("env_vars")]
assert len(api_key_missing) == 2
assert api_key_missing[0]["name"] == "browser"
assert api_key_missing[1]["name"] == "web"
def test_filter_logic_with_empty_env_vars(self):
"""Toolsets with empty env_vars should be filtered out."""
unavailable = [
{"name": "system_tool", "env_vars": [], "tools": ["terminal"]},
]
api_key_missing = [u for u in unavailable if u.get("env_vars")]
assert len(api_key_missing) == 0
def test_display_logic_uses_env_vars(self):
"""The display loop should access 'env_vars', not 'missing_vars'."""
item = {
"name": "browser",
"env_vars": ["BROWSERBASE_API_KEY", "BROWSER_PROJECT_ID"],
"tools": ["browser_navigate", "browser_click", "browser_snapshot"],
}
# This is the fixed display logic from cli.py L3620-3623
tools_str = ", ".join(item["tools"][:2])
if len(item["tools"]) > 2:
tools_str += f", +{len(item['tools'])-2} more"
vars_str = ", ".join(item["env_vars"])
assert tools_str == "browser_navigate, browser_click, +1 more"
assert vars_str == "BROWSERBASE_API_KEY, BROWSER_PROJECT_ID"
def test_old_key_would_crash(self):
"""Demonstrate that accessing 'missing_vars' would raise KeyError."""
item = {"name": "test", "env_vars": ["KEY"], "tools": ["tool"]}
with pytest.raises(KeyError):
_ = item["missing_vars"]
class TestRegistryConsistency:
"""Verify registry internal consistency."""
def test_all_toolsets_have_required_keys(self):
"""Every toolset snapshot should have name, env_vars, tools."""
available, unavailable = registry.check_tool_availability(quiet=True)
all_toolsets = available + [u["name"] for u in unavailable]
assert len(all_toolsets) > 0, "No toolsets found at all"
for item in unavailable:
for key in ("name", "env_vars", "tools"):
assert key in item, f"Missing '{key}' in unavailable toolset: {item}"
def test_no_toolset_in_both_lists(self):
"""A toolset shouldn't appear in both available and unavailable."""
available, unavailable = registry.check_tool_availability(quiet=True)
unavailable_names = {u["name"] for u in unavailable}
overlap = set(available) & unavailable_names
assert len(overlap) == 0, f"Toolsets in both lists: {overlap}"
if __name__ == "__main__":
pytest.main([__file__, "-v"])