hermes-agent/tests/hermes_cli/test_dingtalk_auth.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

169 lines
6.4 KiB
Python

"""Unit tests for hermes_cli/dingtalk_auth.py (QR device-flow registration)."""
from __future__ import annotations
import sys
from unittest.mock import MagicMock, patch
import pytest
# ---------------------------------------------------------------------------
# API layer — _api_post + error mapping
# ---------------------------------------------------------------------------
class TestApiPost:
def test_raises_on_network_error(self):
import requests
from hermes_cli.dingtalk_auth import _api_post, RegistrationError
with patch("hermes_cli.dingtalk_auth.requests.post",
side_effect=requests.ConnectionError("nope")):
with pytest.raises(RegistrationError, match="Network error"):
_api_post("/app/registration/init", {"source": "hermes"})
def test_raises_on_nonzero_errcode(self):
from hermes_cli.dingtalk_auth import _api_post, RegistrationError
mock_resp = MagicMock()
mock_resp.raise_for_status = MagicMock()
mock_resp.json.return_value = {"errcode": 42, "errmsg": "boom"}
with patch("hermes_cli.dingtalk_auth.requests.post", return_value=mock_resp):
with pytest.raises(RegistrationError, match=r"boom \(errcode=42\)"):
_api_post("/app/registration/init", {"source": "hermes"})
# ---------------------------------------------------------------------------
# begin_registration — 2-step nonce → device_code chain
# ---------------------------------------------------------------------------
class TestBeginRegistration:
def test_chains_init_then_begin(self):
from hermes_cli.dingtalk_auth import begin_registration
responses = [
{"errcode": 0, "nonce": "nonce123"},
{
"errcode": 0,
"device_code": "dev-xyz",
"verification_uri_complete": "https://open-dev.dingtalk.com/openapp/registration/openClaw?user_code=ABCD",
"expires_in": 7200,
"interval": 2,
},
]
with patch("hermes_cli.dingtalk_auth._api_post", side_effect=responses):
result = begin_registration()
assert result["device_code"] == "dev-xyz"
assert "verification_uri_complete" in result
assert result["interval"] == 2
assert result["expires_in"] == 7200
def test_missing_nonce_raises(self):
from hermes_cli.dingtalk_auth import begin_registration, RegistrationError
with patch("hermes_cli.dingtalk_auth._api_post",
return_value={"errcode": 0, "nonce": ""}):
with pytest.raises(RegistrationError, match="missing nonce"):
begin_registration()
# ---------------------------------------------------------------------------
# wait_for_registration_success — polling loop
# ---------------------------------------------------------------------------
class TestWaitForSuccess:
def test_returns_credentials_on_success(self):
from hermes_cli.dingtalk_auth import wait_for_registration_success
responses = [
{"status": "WAITING"},
{"status": "WAITING"},
{"status": "SUCCESS", "client_id": "cid-1", "client_secret": "sec-1"},
]
with patch("hermes_cli.dingtalk_auth.poll_registration", side_effect=responses), \
patch("hermes_cli.dingtalk_auth.time.sleep"):
cid, secret = wait_for_registration_success(
device_code="dev", interval=0, expires_in=60
)
assert cid == "cid-1"
assert secret == "sec-1"
def test_success_without_credentials_raises(self):
from hermes_cli.dingtalk_auth import wait_for_registration_success, RegistrationError
with patch("hermes_cli.dingtalk_auth.poll_registration",
return_value={"status": "SUCCESS", "client_id": "", "client_secret": ""}), \
patch("hermes_cli.dingtalk_auth.time.sleep"):
with pytest.raises(RegistrationError, match="credentials are missing"):
wait_for_registration_success(
device_code="dev", interval=0, expires_in=60
)
def test_invokes_waiting_callback(self):
from hermes_cli.dingtalk_auth import wait_for_registration_success
callback = MagicMock()
responses = [
{"status": "WAITING"},
{"status": "WAITING"},
{"status": "SUCCESS", "client_id": "cid", "client_secret": "sec"},
]
with patch("hermes_cli.dingtalk_auth.poll_registration", side_effect=responses), \
patch("hermes_cli.dingtalk_auth.time.sleep"):
wait_for_registration_success(
device_code="dev", interval=0, expires_in=60, on_waiting=callback
)
assert callback.call_count == 2
# ---------------------------------------------------------------------------
# QR rendering — terminal output
# ---------------------------------------------------------------------------
class TestRenderQR:
def test_returns_false_when_qrcode_missing(self, monkeypatch):
from hermes_cli import dingtalk_auth
# Simulate qrcode import failure
monkeypatch.setitem(sys.modules, "qrcode", None)
assert dingtalk_auth.render_qr_to_terminal("https://example.com") is False
def test_prints_when_qrcode_available(self, capsys):
"""End-to-end: render a real QR and verify SOMETHING got printed."""
try:
import qrcode # noqa: F401
except ImportError:
pytest.skip("qrcode library not available")
from hermes_cli.dingtalk_auth import render_qr_to_terminal
result = render_qr_to_terminal("https://example.com/test")
captured = capsys.readouterr()
assert result is True
assert len(captured.out) > 100 # rendered matrix is non-trivial
# ---------------------------------------------------------------------------
# Configuration — env var overrides
# ---------------------------------------------------------------------------
class TestConfigOverrides:
def test_base_url_default(self, monkeypatch):
monkeypatch.delenv("DINGTALK_REGISTRATION_BASE_URL", raising=False)
# Force module reload to pick up current env
import importlib
import hermes_cli.dingtalk_auth as mod
importlib.reload(mod)
assert mod.REGISTRATION_BASE_URL == "https://oapi.dingtalk.com"