mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-09 08:21:50 +00:00
fix(tests): catch up 25 stale tests after recent merges (#28626)
Sweep of all CI failures on origin/main, grouped by drift source: Telegram allowlist gate (db50af910added user-authz to _should_process_message): - Hardcoded "[Telegram]" prefix in the logger.warning so the call no longer dereferences self.name → self.platform, which test fixtures built via object.__new__ never set. - test_telegram_format / test_allowed_channels_widening fixtures stub _is_callback_user_authorized → True so the new gate doesn't reject guest-mode / allowed-channels test messages. - test_telegram_approval_buttons::test_update_prompt_callback_not_affected sets TELEGRAM_ALLOWED_USERS="*" so the fail-closed default doesn't reject the callback before it writes .update_response. Approval surface (6d495d9e7renamed status,214b95392detached stdin): - test_no_callback_returns_approval_required: status is now "pending_approval" (was "approval_required"). - test_close_stdin_allows_eof_driven_process_to_finish: switch to use_pty=True; non-PTY now uses stdin=DEVNULL. Mattermost (send() now resolves root_id via _api_get first): - test_send_with_thread_reply mocks _session.get with a thread-root response so the new resolver doesn't TypeError on a bare AsyncMock. Kanban (d8ad431derename,f55d94a1ereview column, _kanban_worker_skill_available): - _safe_int → _to_epoch in the two test_kanban_db tests. - Spawn-skills tests (×3) monkey-patch _kanban_worker_skill_available to True since the isolated kanban_home fixture has no devops/kanban-worker tree. - test_gateway_dispatcher_disables_corrupt_board: connect count 3 → 5 (review-column probe now also runs per tick). Aux-config severity at_or_above (a94ddd807): - test_diagnostics_endpoint_severity_filter expects warning filter to include error+critical now (was exact-match). Anthropic error handling (conversation loop extracted from run_agent): - _no_backoff_wait fixture patches BOTH run_agent.jittered_backoff AND agent.conversation_loop.jittered_backoff. The latter is the actual call site; without the second patch tests burn ~2s per retry and hit the 30s SIGALRM timeout on CI. Other test pollution / drift: - test_auto_does_not_select_copilot_from_github_token: patch agent.bedrock_adapter.has_aws_credentials → False so boto3's credential chain can't auto-pick Bedrock from developer ~/.aws. - test_setup_openclaw_migration: patch hermes_cli.gateway.get_env_value in addition to setup_mod.get_env_value — _platform_status reads through the gateway module's binding. - test_gateway_prefix: COMPONENT_PREFIXES["gateway"] now includes "hermes_plugins" too. - test_recommended_update_command_defaults_to_hermes_update: also short-circuit get_managed_update_command in case a stray ~/.hermes/.managed marker is present. - test_user_id_is_not_explicit: _parse_target_ref now returns is_explicit=False for Slack U.../W... IDs (chat.postMessage rejects them — a DM must be opened first via conversations.open).
This commit is contained in:
parent
12c39830f0
commit
a0bd11d022
14 changed files with 118 additions and 20 deletions
|
|
@ -314,6 +314,16 @@ class TestResolveProvider:
|
|||
assert resolve_provider("auto") == "openrouter"
|
||||
|
||||
def test_auto_does_not_select_copilot_from_github_token(self, monkeypatch):
|
||||
# AWS Bedrock auto-detection (via boto3's credential chain) runs at
|
||||
# the tail of resolve_provider("auto") and will silently pick up
|
||||
# ~/.aws/credentials on developer machines that aren't blanked by
|
||||
# the hermetic conftest. Force-disable it so this test exercises
|
||||
# the specific "GitHub token alone shouldn't auto-pick copilot"
|
||||
# behavior, not the Bedrock fallback.
|
||||
monkeypatch.setattr(
|
||||
"agent.bedrock_adapter.has_aws_credentials",
|
||||
lambda env=None: False,
|
||||
)
|
||||
monkeypatch.setenv("GITHUB_TOKEN", "gh-test-token")
|
||||
with pytest.raises(AuthError, match="No inference provider configured"):
|
||||
resolve_provider("auto")
|
||||
|
|
|
|||
|
|
@ -2700,6 +2700,12 @@ def test_default_spawn_auto_loads_kanban_worker_skill(kanban_home, monkeypatch):
|
|||
We intercept Popen to capture the argv without actually spawning a
|
||||
hermes subprocess (which would hang trying to call an LLM).
|
||||
"""
|
||||
# Pretend the bundled kanban-worker skill resolves for this isolated
|
||||
# HERMES_HOME — the fixture creates an empty tmpdir without the
|
||||
# devops/kanban-worker tree, and _default_spawn gates the --skills
|
||||
# flag on actual resolvability.
|
||||
monkeypatch.setattr(kb, "_kanban_worker_skill_available", lambda _h: True)
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeProc:
|
||||
|
|
@ -2969,6 +2975,7 @@ def test_create_task_skills_lists_all_toolset_typos(kanban_home):
|
|||
def test_default_spawn_appends_per_task_skills(kanban_home, monkeypatch):
|
||||
"""Dispatcher argv must carry one `--skills X` pair per task skill,
|
||||
in addition to the built-in kanban-worker."""
|
||||
monkeypatch.setattr(kb, "_kanban_worker_skill_available", lambda _h: True)
|
||||
captured = {}
|
||||
|
||||
class FakeProc:
|
||||
|
|
@ -3018,6 +3025,7 @@ def test_default_spawn_appends_per_task_skills(kanban_home, monkeypatch):
|
|||
|
||||
def test_default_spawn_dedupes_kanban_worker_from_task_skills(kanban_home, monkeypatch):
|
||||
"""If a task explicitly lists 'kanban-worker', we don't double-pass it."""
|
||||
monkeypatch.setattr(kb, "_kanban_worker_skill_available", lambda _h: True)
|
||||
captured = {}
|
||||
|
||||
class FakeProc:
|
||||
|
|
@ -3665,9 +3673,13 @@ def test_gateway_dispatcher_disables_corrupt_board_without_traceback(
|
|||
assert sum("not a valid SQLite database" in msg for msg in messages) == 1
|
||||
assert not any("tick failed on board" in msg for msg in messages)
|
||||
assert not any(record.exc_info for record in caplog.records)
|
||||
# First tick connect + two ready-queue probes. The second dispatch tick
|
||||
# skips connect because the corrupt board fingerprint is disabled.
|
||||
assert calls["connect"] == 3
|
||||
# First tick connect (dispatch) + two probes per `_has_ready_work` call
|
||||
# (ready then review, both via _kb.connect). The second dispatch tick
|
||||
# skips the dispatch connect because the corrupt board fingerprint is
|
||||
# disabled, but the ready/review probes still each connect. PR f55d94a1e
|
||||
# added the review-column probe alongside the existing ready-column
|
||||
# probe, bumping this from 3 → 5.
|
||||
assert calls["connect"] == 5
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -2237,24 +2237,25 @@ def _make_task(**overrides) -> "kb.Task":
|
|||
|
||||
def test_safe_int_accepts_int_and_int_string():
|
||||
"""Sanity: well-typed values pass through."""
|
||||
assert kb._safe_int(0) == 0
|
||||
assert kb._safe_int(1700000000) == 1700000000
|
||||
assert kb._safe_int("1700000000") == 1700000000
|
||||
# PR d8ad431de renamed _safe_int → _to_epoch (now also handles ISO-8601).
|
||||
assert kb._to_epoch(0) == 0
|
||||
assert kb._to_epoch(1700000000) == 1700000000
|
||||
assert kb._to_epoch("1700000000") == 1700000000
|
||||
|
||||
|
||||
def test_safe_int_returns_none_on_corrupt_inputs():
|
||||
"""All the failure modes that used to crash task_age."""
|
||||
# None — common when the column was never written
|
||||
assert kb._safe_int(None) is None
|
||||
assert kb._to_epoch(None) is None
|
||||
# Unsubstituted format string — the literal case the PR title cites
|
||||
assert kb._safe_int("%s") is None
|
||||
assert kb._to_epoch("%s") is None
|
||||
# Arbitrary non-numeric strings
|
||||
assert kb._safe_int("abc") is None
|
||||
assert kb._safe_int("") is None
|
||||
assert kb._to_epoch("abc") is None
|
||||
assert kb._to_epoch("") is None
|
||||
# Float-ish strings: int("1.5") raises ValueError too — caller wants None.
|
||||
assert kb._safe_int("1.5") is None
|
||||
assert kb._to_epoch("1.5") is None
|
||||
# Random object — covered by TypeError branch
|
||||
assert kb._safe_int(object()) is None
|
||||
assert kb._to_epoch(object()) is None
|
||||
|
||||
|
||||
def test_task_age_handles_corrupt_created_at():
|
||||
|
|
|
|||
|
|
@ -29,7 +29,13 @@ def test_format_managed_message_homebrew(monkeypatch):
|
|||
def test_recommended_update_command_defaults_to_hermes_update(monkeypatch):
|
||||
monkeypatch.delenv("HERMES_MANAGED", raising=False)
|
||||
|
||||
with patch("hermes_cli.config.detect_install_method", return_value="git"):
|
||||
# Also short-circuit the .managed marker path — CI runners may have an
|
||||
# ambient ~/.hermes/.managed if a prior test left HERMES_HOME pointing
|
||||
# somewhere with that marker, which would make get_managed_update_command()
|
||||
# return "Update your Nix flake input ..." instead of falling through to
|
||||
# detect_install_method().
|
||||
with patch("hermes_cli.config.get_managed_update_command", return_value=None), \
|
||||
patch("hermes_cli.config.detect_install_method", return_value="git"):
|
||||
assert recommended_update_command() == "hermes update"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -404,7 +404,14 @@ class TestGetSectionConfigSummary:
|
|||
assert result == "max turns: 120"
|
||||
|
||||
def test_gateway_returns_none_without_tokens(self):
|
||||
with patch.object(setup_mod, "get_env_value", return_value=""):
|
||||
# _platform_status reads via hermes_cli.gateway.get_env_value, not
|
||||
# setup_mod.get_env_value, so patch BOTH. Without the second patch,
|
||||
# any environment-variable token (or one leaked in by a sibling
|
||||
# test on the same xdist worker) makes the gateway section report
|
||||
# platforms-configured and the test sees a non-None summary.
|
||||
import hermes_cli.gateway as gateway_mod
|
||||
with patch.object(setup_mod, "get_env_value", return_value=""), \
|
||||
patch.object(gateway_mod, "get_env_value", return_value=""):
|
||||
result = setup_mod._get_section_config_summary({}, "gateway")
|
||||
assert result is None
|
||||
|
||||
|
|
@ -625,6 +632,13 @@ class TestSetupWizardSkipsConfiguredSections:
|
|||
|
||||
reloaded_config = {"model": "openai/gpt-4"}
|
||||
|
||||
# _platform_status (called by the gateway summary path) reads env
|
||||
# vars via hermes_cli.gateway.get_env_value, NOT setup_mod's. Patch
|
||||
# both so xdist sibling tests can't leak a TELEGRAM_BOT_TOKEN /
|
||||
# WHATSAPP_* / etc. through and trick the wizard into thinking the
|
||||
# gateway section is already configured (which would skip it).
|
||||
import hermes_cli.gateway as gateway_mod
|
||||
|
||||
with (
|
||||
patch.object(setup_mod, "ensure_hermes_home"),
|
||||
patch.object(
|
||||
|
|
@ -633,6 +647,7 @@ class TestSetupWizardSkipsConfiguredSections:
|
|||
),
|
||||
patch.object(setup_mod, "get_hermes_home", return_value=tmp_path),
|
||||
patch.object(setup_mod, "get_env_value", side_effect=env_side),
|
||||
patch.object(gateway_mod, "get_env_value", side_effect=env_side),
|
||||
patch.object(setup_mod, "is_interactive_stdin", return_value=True),
|
||||
patch("hermes_cli.auth.get_active_provider", return_value=None),
|
||||
patch("builtins.input", return_value=""),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue