hermes-agent/tests/hermes_cli/test_slack_cli.py
Teknium 39975613b1
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over
the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker,
dashboard, conformance, monitoring, secret_sources, hermes_state,
providers). Same rubric as wave 1 (AGENTS.md test policy); security,
alternation/caching invariants, issue-number regressions, and E2E kept.

Real test-quality fixes found and rooted out along the way:
- tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls
  (DEFAULT_CONFIG smart-approval leaked in) — pinned approval
  mode=manual via autouse fixture: 17.4s → 0.4s.
- test_model_switch_custom_providers.py / test_user_providers_model_switch.py
  silently probed live provider catalogs (~2s/test) — stubbed
  cached_provider_model_ids/provider_model_ids/fetch_api_models.
- test_telegram_noise_filter.py: 15-platform copy-paste matrix over
  shared gateway.run logic → 3 representative platforms (55s → 3.9s).
- test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on
  MagicMock agents — interrupt.side_effect now clears _running_agents
  (22s → 1.0s).
- test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x
  (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps
  patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait
  5s → 0.5s.
- test_telegram_init_deadline.py: loop-block margin restored to 1.0s
  with rationale comment — the watchdog-dump assertion needs the loop
  blocked well past deadline+grace under parallel load (flaked once in
  the 40-worker verification run at a 0.2s margin).

Verification: full hermetic suite via scripts/run_tests.sh —
2,438 files, 21,718 tests passed, 0 failed, 293.9s wall.
Suite totals vs original baseline: 46,820 → 19,757 test functions
(−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s.
2026-07-29 13:39:40 -07:00

155 lines
5.1 KiB
Python

"""Tests for Slack CLI helpers."""
import argparse
import json
import subprocess
import sys
from pathlib import Path
import pytest
from hermes_cli.slack_cli import _build_full_manifest, slack_manifest_command
from hermes_cli.subcommands.slack import build_slack_parser
def _parse_slack_args(argv):
"""Build the real `hermes slack` parser and parse argv against it."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="command")
build_slack_parser(subparsers, cmd_slack=lambda _args: 0)
return parser.parse_args(argv)
def _run_console_entrypoint(*argv: str) -> subprocess.CompletedProcess[str]:
"""Run the packaged console-script contract in a fresh interpreter."""
return subprocess.run(
[
sys.executable,
"-c",
"from hermes_cli.main import main; raise SystemExit(main())",
*argv,
],
cwd=Path(__file__).resolve().parents[2],
capture_output=True,
text=True,
timeout=30,
check=False,
)
def test_slack_dispatcher_propagates_manifest_failure(monkeypatch):
from hermes_cli import main as main_module
from hermes_cli import slack_cli
monkeypatch.setattr(slack_cli, "slack_manifest_command", lambda _args: 2)
with pytest.raises(SystemExit) as exc_info:
main_module.cmd_slack(argparse.Namespace(slack_command="manifest"))
assert exc_info.value.code == 2
class TestSlackManifestConsoleExitStatus:
"""The packaged CLI must expose manifest validation failures to shells."""
def test_too_short_long_description_exits_two(self):
result = _run_console_entrypoint(
"slack", "manifest", "--long-description", "x" * 174
)
assert result.returncode == 2
assert result.stdout == ""
assert "at least 175 characters" in result.stderr
def test_missing_long_description_file_exits_two(self, tmp_path):
missing = tmp_path / "missing.md"
result = _run_console_entrypoint(
"slack", "manifest", "--long-description-file", str(missing)
)
assert result.returncode == 2
assert result.stdout == ""
assert "cannot read long description" in result.stderr
class TestSlackManifestArgparse:
"""Slack manifest messaging-experience flags wire through argparse."""
def test_long_description_file_preserves_newlines(self, tmp_path, capsys):
content = ("x" * 175) + "\r\n" + ("y" * 175) + "\r"
source = tmp_path / "AGENTS.md"
source.write_bytes(content.encode("utf-8"))
args = _parse_slack_args(
["slack", "manifest", "--long-description-file", str(source)]
)
assert slack_manifest_command(args) == 0
manifest = json.loads(capsys.readouterr().out)
assert manifest["display_information"]["long_description"] == content
def test_long_description_file_reports_tilde_expansion_errors(
self, monkeypatch, capsys
):
source = "~hermes-user-that-does-not-exist-20260716/AGENTS.md"
def fail_expanduser(_path):
raise RuntimeError("home directory unavailable")
monkeypatch.setattr(Path, "expanduser", fail_expanduser)
args = _parse_slack_args(
["slack", "manifest", "--long-description-file", source]
)
assert slack_manifest_command(args) == 2
captured = capsys.readouterr()
assert captured.out == ""
assert "cannot read long description" in captured.err
assert source in captured.err
class TestSlackFullManifest:
"""Generated full Slack app manifest used by `hermes slack manifest`."""
def test_assistant_features_remain_enabled(self):
manifest = _build_full_manifest("Hermes", "Your Hermes agent on Slack")
assert "assistant_view" in manifest["features"]
assert "agent_view" not in manifest["features"]
assert "assistant:write" in manifest["oauth_config"]["scopes"]["bot"]
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
assert "assistant_thread_started" in bot_events
def test_no_assistant_preserves_core_surface(self):
"""Dropping assistant mode must NOT strip the regular messaging surface."""
manifest = _build_full_manifest(
"Hermes", "Your Hermes agent on Slack", include_assistant=False
)
# Flat DM still needs the Messages tab writable.
assert manifest["features"]["app_home"]["messages_tab_enabled"] is True
# Slash commands and Socket Mode are independent of assistant mode.
assert manifest["features"]["slash_commands"]
assert manifest["settings"]["socket_mode_enabled"] is True
# Channel + DM scopes/events survive so the bot still works everywhere.
bot_scopes = manifest["oauth_config"]["scopes"]["bot"]
for scope in ("commands", "channels:history", "groups:read", "im:history"):
assert scope in bot_scopes
bot_events = manifest["settings"]["event_subscriptions"]["bot_events"]
for event in ("message.im", "message.channels", "message.groups", "app_mention"):
assert event in bot_events