The previous fix interpolated the real per-bottle identity token into the `warn()` message shown when `claude mcp add supervise` fails, so a registration failure would leak the credential into host terminal output and any collected launch logs (#476 review, PR #471 comment). Render a `<bottle-identity-token>` placeholder instead. The recovery hint still shows the required `--header x-bot-bottle-identity: …` shape, and the operator substitutes the value from inside the bottle (it rides in the agent's HTTPS_PROXY credentials) — so the token never reaches the host log. The token truthiness still gates whether the header is shown at all. Test updated to assert the header name and placeholder are present and the raw token is absent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tests
Plain-Python test suite using stdlib unittest. No external
dependencies. Unit tests run anywhere Python 3 is present; integration
tests run through the backend named by BOT_BOTTLE_BACKEND (default
docker) and skip cleanly when that backend isn't available on the host.
Layout
tests/
fixtures.py # JSON manifest builders (shared)
_backend.py # backend selection + skip guards (shared)
unit/
test_egress.py
test_egress_addon_core.py
test_manifest_egress.py
test_dlp_detectors.py
test_manifest_runtime.py
... # many others; see unit/ directory
integration/
test_gateway_image.py
test_dry_run_plan.py
test_orphan_cleanup.py
...
canaries/ # opt-in; see below (currently empty)
Classification falls out of the directory — no hand-maintained list to keep in sync.
Running
python -m unittest discover -t . -s tests/unit -v # unit only
python -m unittest discover -t . -s tests/integration -v # integration only
python -m unittest discover -t . -s tests -v # both (recursive)
python -m unittest tests.unit.test_manifest_egress # one file
Discovery is invoked with -t . (top-level dir = repo root) so the
bot_bottle package on sys.path resolves correctly.
What the integration tests cover
test_dry_run_plan.py—cli.py start --dry-run --format=jsonemits a structured plan that contains the resolved egress allowlist and the bottle's runtime, and creates zero Docker resources.test_orphan_cleanup.py—network_removeis idempotent against missing resources, so the EXIT trap can call it unconditionally.test_gateway_image.py— builds Dockerfile.gateway and probes that gitleaks / mitmdump / supervise are all reachable inside the gateway image.
Canaries
tests/canaries/ holds upstream-regression checks gated on
BOT_BOTTLE_RUN_CANARIES=1 and not part of the per-push suite.
They're invoked by the scheduled canaries workflow. Currently
no canaries are defined.
BOT_BOTTLE_RUN_CANARIES=1 python -m unittest discover -t . -s tests/canaries -v
What's NOT covered
bot_bottle/ssh.pyend-to-end (would need a fake SSH host inside the container).- A live SSH-through-git-gate tunnel against a real Tailscale-style IP.
- DLP false-positive measurements.
- TLS handling / cert pinning behavior.
Adding a test
-
Pick the directory:
tests/unit/for a pure unit test,tests/integration/for one that needs a backend. -
Filename:
test_<topic>.py. -
Boilerplate:
import unittest from bot_bottle.<module> import <symbol> class TestThing(unittest.TestCase): def test_x(self): ... if __name__ == "__main__": unittest.main() -
Skip guards live in
tests._backendand gate on the backend's own readiness check,bot_bottle.backend.has_backend— the same probe behind./cli.py backend status:- Backend-agnostic tests (go through
get_bottle_backend()) decorate the class with@skip_unless_selected_backend_available()— the test runs against whichever backendBOT_BOTTLE_BACKENDselects and skips unless that backend is available (checking, e.g., Linux +/dev/kvmfor Firecracker rather than unrelated Docker availability). - Backend-specific tests (exercise
DockerBroker,DockerGateway,backend.docker.*, …) decorate with@skip_unless_backend("docker")so they no-op under a run targeting a different backend.
Each CI integration job runs
./cli.py backend status --backend=<name>as a preflight, which prints a clear per-check summary and exits non-zero when the backend is missing — so absent infrastructure fails the job instead of hiding among per-testunittest.skiplines. - Backend-agnostic tests (go through