fix(plugin): bind adapter to real Hermes gateway API; add contract-test gate
release / contract-test (push) Successful in 54s
release / test-and-release (push) Successful in 11s

- Resolve the Hermes gateway API lazily via get_adapter_class() so the adapter
  always inherits the real gateway.platforms.base.BasePlatformAdapter instead
  of the ImportError fallback stub (root cause of the AttributeError:
  'set_message_handler' crash at gateway startup).
- Pass Platform('meshtastic') enum to super().__init__ and align
  connect(*, is_reconnect=False) -> bool with the gateway contract.
- Split pure formatting utils into formatting.py; remove the stub fallback.
- Add tests/contract (L0-L3): real-base binding, ABC instantiation, gateway
  run.py wiring replay, signature and event-shape conformance, run against
  hermes-agent pinned to the deployed image source (v2026.8.31).
- Add scripts/contract-test.sh and a contract-test CI job; release publish
  now requires contract tests to pass.
- Untrack stray __pycache__ artifacts.
This commit is contained in:
2026-09-07 20:50:21 -07:00
parent 2d9106b864
commit 310c0244df
12 changed files with 712 additions and 256 deletions
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Gateway contract tests for hermes-meshtastic.
#
# Runs the plugin's contract suite (tests/contract/) against the REAL Hermes
# gateway API, pinned to the same upstream commit the deployed container image
# is built from (NousResearch/hermes-agent @ v2026.8.31 — the image tag used in
# k8s-flux apps/base/coder/hermes-agent).
#
# Why: the adapter used to be developed by guessing the gateway API and only
# failing at gateway startup after a release (AttributeError: ... no attribute
# 'set_message_handler'). These tests replay the gateway's adapter bootstrap
# (registry registration -> factory -> set_* wiring) so API drift fails here,
# in seconds, without deploying.
#
# Requires: uv (https://docs.astral.sh/uv/) and network on first run.
#
# Usage: scripts/contract-test.sh
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLUGIN_DIR="$REPO_ROOT/plugin"
VENV="$PLUGIN_DIR/.venv-contract"
HERMES_SRC="$PLUGIN_DIR/.hermes-src"
HERMES_TAG="${HERMES_TAG:-v2026.8.31}" # must match k8s-flux hermes image tag
HERMES_COMMIT="${HERMES_COMMIT:-29112bef099274229cadff79cdff7bf7b99c4b77}"
if ! command -v uv >/dev/null 2>&1; then
echo "error: uv is required (https://docs.astral.sh/uv/)" >&2
exit 1
fi
if [ ! -d "$HERMES_SRC/.git" ]; then
echo "== cloning hermes-agent @ $HERMES_TAG (deployed image source) =="
git clone --depth 1 --branch "$HERMES_TAG" \
https://github.com/NousResearch/hermes-agent.git "$HERMES_SRC"
fi
actual="$(git -C "$HERMES_SRC" rev-parse HEAD)"
if [ "$actual" != "$HERMES_COMMIT" ]; then
echo "error: $HERMES_SRC is at $actual, expected $HERMES_COMMIT ($HERMES_TAG)." >&2
echo " Refresh deliberately: HERMES_TAG=<new tag> scripts/contract-test.sh" >&2
exit 1
fi
if [ ! -x "$VENV/bin/python" ]; then
echo "== creating contract venv (python 3.13, matching the Hermes image) =="
uv venv --python 3.13 "$VENV"
fi
uv pip install --python "$VENV/bin/python" -q -e "$HERMES_SRC"
uv pip install --python "$VENV/bin/python" -q pytest
echo "== running contract tests (HERMES_CONTRACT_REQUIRED=1) =="
(
cd "$PLUGIN_DIR"
HERMES_CONTRACT_REQUIRED=1 "$VENV/bin/python" -m pytest tests/contract -q "$@"
)
echo "== running standalone unit tests (no gateway needed) =="
(
cd "$PLUGIN_DIR"
"$VENV/bin/python" -m pytest tests/test_adapter.py -q
)