hermes-agent/tests/test_project_metadata.py
ethernet 0fce82164a Pluginify provider/platform/terminal backends
Move provider adapters (anthropic, bedrock, azure), platform adapters
(telegram, slack, discord, feishu, dingtalk, matrix), and terminal backends
(modal, daytona) out of core into plugins/ workspace members. Core references
them via the plugin registries (get_provider_namespace / get_provider_service /
get_tool_provider / get_credential_pool_hook) instead of direct imports.

- Provider/platform/terminal adapters relocated under plugins/; pyproject
  extras reference workspace members; nix variants aggregate per-platform extras.
- Anthropic credential discovery + OAuth-masquerade guard live in the plugin's
  credential_pool_hook; browser-open guarded by _can_open_graphical_browser.
- Vercel AI Gateway + Vercel Sandbox removed (upstream deletion); get_bedrock_model_ids
  removed (replaced by bedrock_model_ids_or_none + discover_bedrock_models).
- Terminal backends resolve ModalEnvironment / DaytonaEnvironment lazily from
  the plugin registry.
- uv.lock regenerated against the pluginified workspace.

Plugin test suites updated for the relocation: imports point at
hermes_agent_<plat>.adapter, caplog logger-name filters and monkeypatch targets
use the new module paths, and credential/rollback tests patch
registries.get_provider_service rather than the removed agent.*_adapter modules.

Verified: zero dead imports of relocated modules in core (import smoke test +
rename-map grep); nix develop succeeds; targeted plugin suites green
(bedrock, anthropic-auxiliary, matrix, dingtalk, feishu, credential_pool,
switch_model_rollback). Remaining full-suite failures are pre-existing on the
pre-merge tree (telegram setUpModule __code__) or environmental (voice/media/
PTY/network-dependent), not introduced here.
2026-05-29 09:28:00 -04:00

109 lines
4.1 KiB
Python

"""Regression tests for packaging metadata in pyproject.toml."""
from pathlib import Path
import tomllib
def _load_optional_dependencies():
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
with pyproject_path.open("rb") as handle:
project = tomllib.load(handle)["project"]
return project["optional-dependencies"]
def _load_package_data():
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
with pyproject_path.open("rb") as handle:
tool = tomllib.load(handle)["tool"]
return tool["setuptools"]["package-data"]
def test_matrix_extra_not_in_all():
"""The [matrix] extra pulls `mautrix[encryption]` -> `python-olm`,
which has Linux-only wheels and no native build path on Windows or
modern macOS (archived libolm, C++ errors with Clang 21+).
With matrix in [all], `uv sync --locked` on Windows tried to build
python-olm from sdist and failed on `make`. The [matrix] extra is
excluded from [all] — users opt in via `pip install hermes-agent[matrix]`.
"""
optional_dependencies = _load_optional_dependencies()
assert "matrix" in optional_dependencies, (
"[matrix] extra must still exist for explicit `pip install hermes-agent[matrix]`"
)
matrix_in_all = [
dep for dep in optional_dependencies["all"]
if "matrix" in dep
]
assert not matrix_in_all, (
f"matrix must not appear in [all] — it's an opt-in plugin. Found: "
f"{matrix_in_all}"
)
def test_plugin_extras_are_workspace_member_refs():
"""Every plugin extra in pyproject.toml should reference a workspace
member package (e.g. ``hermes-agent-anthropic``), not inline dep specs.
This ensures the single source of truth for plugin deps is the plugin's
own pyproject.toml, not the main package's extras.
"""
optional_dependencies = _load_optional_dependencies()
# Extras that are known plugin workspace members
plugin_extras = {
"anthropic", "bedrock", "azure-identity",
"discord", "exa", "firecrawl", "parallel",
"honcho", "hindsight",
"fal", "tts", "stt",
"daytona", "modal",
"telegram", "slack", "dingtalk", "feishu", "matrix",
"dashboard",
}
for extra in plugin_extras:
if extra not in optional_dependencies:
continue
specs = optional_dependencies[extra]
for spec in specs:
assert spec.startswith("hermes-agent-"), (
f"[{extra}] extra should reference a workspace member, "
f"not an inline dep spec. Got: {spec}"
)
def test_dingtalk_extra_includes_qrcode_for_qr_auth():
"""DingTalk's QR-code device-flow auth (hermes_cli/dingtalk_auth.py)
needs the qrcode package — verify it's in the dingtalk plugin's deps."""
pyproject_path = Path(__file__).resolve().parents[1] / "plugins" / "platforms" / "dingtalk" / "pyproject.toml"
with pyproject_path.open("rb") as handle:
project = tomllib.load(handle)["project"]
deps = project.get("dependencies", [])
assert any("qrcode" in d for d in deps), (
f"hermes-agent-dingtalk should depend on qrcode. deps: {deps}"
)
def test_feishu_extra_includes_qrcode_for_qr_login():
"""Feishu's QR login flow needs the qrcode package — verify it's in
the feishu plugin's deps."""
pyproject_path = Path(__file__).resolve().parents[1] / "plugins" / "platforms" / "feishu" / "pyproject.toml"
with pyproject_path.open("rb") as handle:
project = tomllib.load(handle)["project"]
deps = project.get("dependencies", [])
assert any("qrcode" in d for d in deps), (
f"hermes-agent-feishu should depend on qrcode. deps: {deps}"
)
def test_dashboard_plugin_manifests_and_assets_are_packaged():
"""Bundled dashboard plugins need their manifests and built assets in
wheel installs so /api/dashboard/plugins can discover them outside a
source checkout."""
package_data = _load_package_data()
plugin_data = package_data["plugins"]
assert "*/dashboard/manifest.json" in plugin_data
assert "*/dashboard/dist/*" in plugin_data
assert "*/dashboard/dist/**/*" in plugin_data