"""Tests for GET /api/commands -- exposes hermes-agent COMMAND_REGISTRY.""" import json import urllib.request import pytest from tests.conftest import TEST_BASE, requires_agent_modules def _get(path): """GET helper -- returns parsed JSON or raises HTTPError.""" with urllib.request.urlopen(TEST_BASE + path, timeout=10) as r: return json.loads(r.read()) @requires_agent_modules def test_commands_endpoint_returns_list(): """GET /api/commands returns a JSON object with a 'commands' list.""" body = _get('/api/commands') assert 'commands' in body assert isinstance(body['commands'], list) assert len(body['commands']) > 0 @requires_agent_modules def test_commands_endpoint_includes_help(): """The 'help' command must always be present (it's not cli_only).""" body = _get('/api/commands') names = {c['name'] for c in body['commands']} assert 'help' in names @requires_agent_modules def test_commands_endpoint_command_shape(): """Each command entry has the required fields.""" body = _get('/api/commands') cmd = next(c for c in body['commands'] if c['name'] == 'help') required = { 'name', 'description', 'category', 'aliases', 'args_hint', 'subcommands', 'cli_only', 'gateway_only', } assert set(cmd.keys()) >= required assert isinstance(cmd['aliases'], list) assert isinstance(cmd['subcommands'], list) assert isinstance(cmd['cli_only'], bool) assert isinstance(cmd['gateway_only'], bool) @requires_agent_modules def test_commands_endpoint_excludes_gateway_only_and_never_expose(): """gateway_only commands and the _NEVER_EXPOSE set are filtered out.""" body = _get('/api/commands') names = {c['name'] for c in body['commands']} # /sethome, /restart, /update are gateway_only; /commands is in _NEVER_EXPOSE for name in ('sethome', 'restart', 'update', 'commands'): assert name not in names, f"{name} must be excluded from /api/commands" @requires_agent_modules def test_commands_endpoint_keeps_new_with_reset_alias(): """The 'new' command stays exposed and carries its 'reset' alias.""" body = _get('/api/commands') new_cmd = next(c for c in body['commands'] if c['name'] == 'new') assert 'reset' in new_cmd['aliases'] def test_list_commands_returns_empty_for_empty_registry(): """list_commands(_registry=[]) returns [] -- the same path as when hermes_cli is missing (the empty-or-missing case).""" from api.commands import list_commands assert list_commands(_registry=[]) == [] def test_list_commands_degrades_when_agent_missing(monkeypatch): """If hermes_cli.commands is not importable, list_commands() returns [] via the ImportError path. Verified by stubbing sys.modules; test cleanup is handled by monkeypatch + the fact that we don't reload api.commands.""" import sys monkeypatch.setitem(sys.modules, 'hermes_cli.commands', None) # NOTE: we do NOT reload api.commands. The lazy import inside # list_commands() will re-attempt the import on each call and hit # the stubbed-None module, raising ImportError, taking the fallback path. from api.commands import list_commands assert list_commands() == []