d32f36bb2b
Two bugs surfaced by a review of the previous commit: - host_control_plane_token() resolves its path via the ambient BOT_BOTTLE_ROOT env var, not the host_root kwarg passed to OrchestratorService (that kwarg only controls the DB bind-mount destination). The test's isolation claim was false as a result: running it read/wrote the developer's real ~/.bot-bottle/control-plane-token instead of the throwaway temp dir — confirmed directly on disk. Fixed by pointing the env var at the same temp dir for the test's duration and restoring it via addCleanup. - ensure_running() creates a per-bottle Docker network but stop() only ever removes containers, never the network — every run of this test leaked one bridge network permanently (found and removed 5 from prior runs via `docker network ls`). Fixed with an explicit `docker network rm` in addCleanup. Verified: re-ran the suite twice: 5/5 pass both times, the real ~/.bot-bottle/control-plane-token timestamp is unchanged across both runs (proving isolation), and `docker network ls` shows zero leaked bot-bottle-net-itest-* networks afterward. pyright clean, pylint 10.00/10. Remaining findings from the same review (missing GITEA_ACTIONS skip guard, root-owned bind-mount cleanup on native Linux, no setUpClass, reinvented OrchestratorClient, gateway_name should be a constructor param rather than a subclassed private-method override) are left for a follow-up — each is a real, separate design/scope call, not a mechanical fix like these two. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tests
Plain-Python test suite using stdlib unittest. No external
dependencies. Unit tests run anywhere Python 3 is present; integration
tests need Docker and skip cleanly otherwise.
Layout
tests/
fixtures.py # JSON manifest builders (shared)
_docker.py # docker-availability skip helper (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 Docker. - 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() - For Docker-dependent tests, decorate the class with
@skip_unless_docker()fromtests._docker.