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
136 lines
5.3 KiB
Python
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"])
|