hermes-agent/tests/agent/test_onboarding.py
Teknium 6b81590c55
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00

227 lines
7.1 KiB
Python

"""Tests for agent/onboarding.py — contextual first-touch hint helpers."""
from __future__ import annotations
import yaml
from agent.onboarding import (
BUSY_INPUT_FLAG,
OPENCLAW_RESIDUE_FLAG,
TOOL_PROGRESS_FLAG,
busy_input_hint_cli,
busy_input_hint_gateway,
detect_openclaw_residue,
is_seen,
mark_seen,
openclaw_residue_hint_cli,
tool_progress_hint_cli,
tool_progress_hint_gateway,
)
class TestIsSeen:
def test_empty_config_unseen(self):
assert is_seen({}, BUSY_INPUT_FLAG) is False
def test_seen_flag_true(self):
cfg = {"onboarding": {"seen": {BUSY_INPUT_FLAG: True}}}
assert is_seen(cfg, BUSY_INPUT_FLAG) is True
def test_seen_flag_falsy(self):
cfg = {"onboarding": {"seen": {BUSY_INPUT_FLAG: False}}}
assert is_seen(cfg, BUSY_INPUT_FLAG) is False
class TestMarkSeen:
def test_preserves_other_config(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
cfg_path.write_text(yaml.safe_dump({
"model": {"default": "claude-sonnet-4.6"},
"display": {"skin": "default"},
}))
assert mark_seen(cfg_path, BUSY_INPUT_FLAG) is True
loaded = yaml.safe_load(cfg_path.read_text())
assert loaded["model"]["default"] == "claude-sonnet-4.6"
assert loaded["display"]["skin"] == "default"
assert loaded["onboarding"]["seen"][BUSY_INPUT_FLAG] is True
def test_idempotent(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
mark_seen(cfg_path, BUSY_INPUT_FLAG)
first = cfg_path.read_text()
# Second call must be a no-op on-disk content (file may be touched,
# but the YAML contents should be identical).
mark_seen(cfg_path, BUSY_INPUT_FLAG)
second = cfg_path.read_text()
assert yaml.safe_load(first) == yaml.safe_load(second)
class TestHintMessages:
def test_busy_input_hint_cli_steer(self):
msg = busy_input_hint_cli("steer")
assert "/busy interrupt" in msg
assert "/busy queue" in msg
assert "steer" in msg.lower()
def test_hints_are_not_empty(self):
for hint in (
busy_input_hint_gateway("queue"),
busy_input_hint_gateway("interrupt"),
busy_input_hint_gateway("steer"),
busy_input_hint_cli("queue"),
busy_input_hint_cli("interrupt"),
busy_input_hint_cli("steer"),
tool_progress_hint_gateway(),
tool_progress_hint_cli(),
):
assert hint.strip()
class TestRoundTrip:
"""After mark_seen, is_seen on the re-loaded config must return True."""
def test_mark_then_is_seen(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
assert mark_seen(cfg_path, BUSY_INPUT_FLAG) is True
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, BUSY_INPUT_FLAG) is True
assert is_seen(loaded, TOOL_PROGRESS_FLAG) is False
def test_mark_both_flags_independently(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
mark_seen(cfg_path, BUSY_INPUT_FLAG)
mark_seen(cfg_path, TOOL_PROGRESS_FLAG)
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, BUSY_INPUT_FLAG) is True
assert is_seen(loaded, TOOL_PROGRESS_FLAG) is True
# ---------------------------------------------------------------------------
# OpenClaw residue banner
# ---------------------------------------------------------------------------
class TestDetectOpenclawResidue:
def test_returns_true_when_openclaw_dir_present(self, tmp_path):
(tmp_path / ".openclaw").mkdir()
assert detect_openclaw_residue(home=tmp_path) is True
def test_returns_false_when_path_is_a_file(self, tmp_path):
# A stray file named ``.openclaw`` is NOT a workspace — skip the banner.
(tmp_path / ".openclaw").write_text("oops")
assert detect_openclaw_residue(home=tmp_path) is False
class TestOpenclawResidueHint:
def test_hint_warns_cleanup_breaks_openclaw(self):
# Archiving the directory breaks OpenClaw for users still running it —
# the banner must flag that side effect.
msg = openclaw_residue_hint_cli().lower()
assert "openclaw will stop working" in msg or "stop working" in msg
def test_hint_not_empty(self):
assert openclaw_residue_hint_cli().strip()
class TestOpenclawResidueSeenFlag:
def test_flag_independent_of_other_flags(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
mark_seen(cfg_path, BUSY_INPUT_FLAG)
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, OPENCLAW_RESIDUE_FLAG) is False
def test_flag_round_trips(self, tmp_path):
cfg_path = tmp_path / "config.yaml"
assert mark_seen(cfg_path, OPENCLAW_RESIDUE_FLAG) is True
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, OPENCLAW_RESIDUE_FLAG) is True
class TestProfileBuildMode:
def test_default_is_ask(self):
from agent.onboarding import profile_build_mode
assert profile_build_mode({}) == "ask"
assert profile_build_mode({"onboarding": {}}) == "ask"
assert profile_build_mode({"onboarding": {"profile_build": "ask"}}) == "ask"
def test_non_mapping_config_safe(self):
from agent.onboarding import profile_build_mode
assert profile_build_mode("not a dict") == "ask" # type: ignore[arg-type]
assert profile_build_mode({"onboarding": "nope"}) == "ask"
class TestProfileBuildDirective:
def test_directive_is_opt_in_and_consent_gated(self):
from agent.onboarding import profile_build_directive
d = profile_build_directive()
# Must OFFER, not assume.
assert "OFFER" in d
# Must require consent before external lookups.
assert "consent" in d.lower()
# Must forbid silently reading connected accounts.
assert "silently" in d.lower()
# Must persist via the user-profile memory store.
assert 'target="user"' in d
# Must allow declining.
assert "decline" in d.lower()
def test_directive_mentions_first_message(self):
from agent.onboarding import profile_build_directive
assert "first message ever" in profile_build_directive()
class TestProfileBuildSeenFlag:
def test_flag_round_trips(self, tmp_path):
from agent.onboarding import PROFILE_BUILD_FLAG
cfg_path = tmp_path / "config.yaml"
assert mark_seen(cfg_path, PROFILE_BUILD_FLAG) is True
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, PROFILE_BUILD_FLAG) is True
def test_flag_independent_of_busy_input(self, tmp_path):
from agent.onboarding import PROFILE_BUILD_FLAG
cfg_path = tmp_path / "config.yaml"
mark_seen(cfg_path, BUSY_INPUT_FLAG)
loaded = yaml.safe_load(cfg_path.read_text())
assert is_seen(loaded, PROFILE_BUILD_FLAG) is False
class TestProfileBuildConfigDefault:
def test_default_config_carries_ask(self):
from hermes_cli.config import DEFAULT_CONFIG
assert DEFAULT_CONFIG["onboarding"]["profile_build"] == "ask"