mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-02 07:11:49 +00:00
Remove unused imports (F401) and duplicate/shadowed import redefinitions (F811) across the codebase using ruff's safe autofixes. No behavioral changes -- imports only. - ~1400 safe autofixes applied across 644 files (net -1072 lines) - __init__.py re-exports preserved (excluded from F401 removal so public re-export surfaces stay intact) - Re-exports that are imported or monkeypatched by tests but look unused in their defining module are kept with explicit # noqa: F401 (gateway/run.py load_dotenv; run_agent re-exports from agent.message_sanitization, agent.context_compressor, agent.retry_utils, agent.prompt_builder, agent.process_bootstrap, agent.codex_responses_adapter) - Unsafe F841 (unused-variable) fixes deliberately skipped -- those can change behavior when the RHS has side effects - ruff lints remain disabled in pyproject.toml (only PLW1514 is selected); this is a one-time cleanup, not a config change Verification: - python -m compileall: clean - pytest --collect-only: all 27161 tests collect (zero import errors) - core entry points import clean (run_agent, model_tools, cli, toolsets, hermes_state, batch_runner, gateway) - static scan: every name any test imports directly from an edited module still resolves
130 lines
5.2 KiB
Python
130 lines
5.2 KiB
Python
"""Tests for non-ASCII credential detection and sanitization.
|
||
|
||
Covers the fix for issue #6843 — API keys containing Unicode lookalike
|
||
characters (e.g. ʋ U+028B instead of v) cause UnicodeEncodeError when
|
||
httpx tries to encode the Authorization header as ASCII.
|
||
"""
|
||
|
||
import os
|
||
|
||
|
||
from hermes_cli.config import _check_non_ascii_credential
|
||
|
||
|
||
class TestCheckNonAsciiCredential:
|
||
"""Tests for _check_non_ascii_credential()."""
|
||
|
||
def test_ascii_key_unchanged(self):
|
||
key = "sk-proj-" + "a" * 100
|
||
result = _check_non_ascii_credential("TEST_API_KEY", key)
|
||
assert result == key
|
||
|
||
def test_strips_unicode_v_lookalike(self, capsys):
|
||
"""The exact scenario from issue #6843: ʋ instead of v."""
|
||
key = "sk-proj-abc" + "ʋ" + "def" # \u028b
|
||
result = _check_non_ascii_credential("OPENROUTER_API_KEY", key)
|
||
assert result == "sk-proj-abcdef"
|
||
assert "ʋ" not in result
|
||
# Should print a warning
|
||
captured = capsys.readouterr()
|
||
assert "non-ASCII" in captured.err
|
||
|
||
def test_strips_multiple_non_ascii(self, capsys):
|
||
key = "sk-proj-aʋbécd"
|
||
result = _check_non_ascii_credential("OPENAI_API_KEY", key)
|
||
assert result == "sk-proj-abcd"
|
||
captured = capsys.readouterr()
|
||
assert "U+028B" in captured.err # reports the char
|
||
|
||
def test_empty_key(self):
|
||
result = _check_non_ascii_credential("TEST_KEY", "")
|
||
assert result == ""
|
||
|
||
def test_all_ascii_no_warning(self, capsys):
|
||
result = _check_non_ascii_credential("KEY", "all-ascii-value-123")
|
||
assert result == "all-ascii-value-123"
|
||
captured = capsys.readouterr()
|
||
assert captured.err == ""
|
||
|
||
|
||
class TestEnvLoaderSanitization:
|
||
"""Tests for _sanitize_loaded_credentials in env_loader."""
|
||
|
||
def test_strips_non_ascii_from_api_key(self, monkeypatch):
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
|
||
|
||
_WARNED_KEYS.discard("OPENROUTER_API_KEY")
|
||
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-proj-abcʋdef")
|
||
_sanitize_loaded_credentials()
|
||
assert os.environ["OPENROUTER_API_KEY"] == "sk-proj-abcdef"
|
||
|
||
def test_strips_non_ascii_from_token(self, monkeypatch):
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
|
||
|
||
_WARNED_KEYS.discard("DISCORD_BOT_TOKEN")
|
||
monkeypatch.setenv("DISCORD_BOT_TOKEN", "tokénvalue")
|
||
_sanitize_loaded_credentials()
|
||
assert os.environ["DISCORD_BOT_TOKEN"] == "toknvalue"
|
||
|
||
def test_ignores_non_credential_vars(self, monkeypatch):
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials
|
||
|
||
monkeypatch.setenv("MY_UNICODE_VAR", "héllo wörld")
|
||
_sanitize_loaded_credentials()
|
||
# Not a credential suffix — should be left alone
|
||
assert os.environ["MY_UNICODE_VAR"] == "héllo wörld"
|
||
|
||
def test_ascii_credentials_untouched(self, monkeypatch):
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials
|
||
|
||
monkeypatch.setenv("OPENAI_API_KEY", "sk-proj-allascii123")
|
||
_sanitize_loaded_credentials()
|
||
assert os.environ["OPENAI_API_KEY"] == "sk-proj-allascii123"
|
||
|
||
def test_warns_to_stderr_when_stripping(self, monkeypatch, capsys):
|
||
"""Silent stripping masks bad keys as opaque provider 400s (see #6843 fallout).
|
||
|
||
Users must be told when a copy-paste artifact was removed so they
|
||
can re-copy the key if authentication fails.
|
||
"""
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
|
||
|
||
_WARNED_KEYS.discard("GOOGLE_API_KEY")
|
||
monkeypatch.setenv("GOOGLE_API_KEY", "AIzaSy\u200babcdef") # ZWSP mid-key
|
||
_sanitize_loaded_credentials()
|
||
assert os.environ["GOOGLE_API_KEY"] == "AIzaSyabcdef"
|
||
|
||
captured = capsys.readouterr()
|
||
assert "GOOGLE_API_KEY" in captured.err
|
||
assert "U+200B" in captured.err
|
||
assert "re-copy" in captured.err.lower()
|
||
|
||
def test_warning_fires_only_once_per_key(self, monkeypatch, capsys):
|
||
"""Repeated loads (user env + project env) must not double-warn."""
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
|
||
|
||
_WARNED_KEYS.discard("GEMINI_API_KEY")
|
||
monkeypatch.setenv("GEMINI_API_KEY", "AIza\u028bbad")
|
||
_sanitize_loaded_credentials()
|
||
first = capsys.readouterr().err
|
||
|
||
monkeypatch.setenv("GEMINI_API_KEY", "AIza\u028bbad2")
|
||
_sanitize_loaded_credentials()
|
||
second = capsys.readouterr().err
|
||
|
||
assert "GEMINI_API_KEY" in first
|
||
assert second == "" # no repeat warning
|
||
|
||
def test_ascii_control_chars_not_stripped(self, monkeypatch, capsys):
|
||
"""ASCII control bytes (e.g. ESC 0x1B from terminal paste) are NOT non-ASCII.
|
||
|
||
This is intentional — they're valid ASCII for HTTP headers even if the
|
||
provider rejects them. Documents the scope of the sanitizer.
|
||
"""
|
||
from hermes_cli.env_loader import _sanitize_loaded_credentials, _WARNED_KEYS
|
||
|
||
_WARNED_KEYS.clear()
|
||
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant\x1bapi-key")
|
||
_sanitize_loaded_credentials()
|
||
assert os.environ["ANTHROPIC_API_KEY"] == "sk-ant\x1bapi-key"
|
||
assert capsys.readouterr().err == ""
|