Files
bot-bottle/tests
didericis-claude 3c0d2fb66f
lint / lint (push) Successful in 1m1s
test / unit (pull_request) Successful in 1m5s
tracker-policy-pr / check-pr (pull_request) Successful in 10s
test / integration-docker (pull_request) Failing after 1m30s
test / coverage (pull_request) Has been skipped
refactor(backend): base owns the gateway-attach flow, fail hard (0081 review)
Addresses review on #519 (@didericis 6143 + the fail-hard direction over the
codex skip).

Base owns the flow (6143 / ADR 0006). `attach_bottled_agents_to_gateway()` is
now a concrete method on `BottleBackend` that delegates to
`gateway_attach.reconcile_running_bottles`; backends override only three
primitives — `_gateway_attach_resources()`, `_running_bottles()`,
`_attach_bottle_to_gateway()`. The shared control flow + error policy live in
one place so a backend can't drift onto a bespoke loop or a silent skip. Keeps
the reconcile flow + `GatewayAttachResources` out of `backend/base.py` (the
size guardrail) in a dedicated `backend/gateway_attach.py`. New ADR 0006 records
the "shared behaviour in the base backend, subclasses override primitives"
theme.

Fail hard, never skip (the maintainer's direction over the codex review's
skip). Any attach failure now aborts bring-up instead of being logged and
skipped: a bottle that silently can't reach the fresh gateway (its egress just
starts failing TLS) is worse than a loud failure. Every bottle is attempted and
the failures are raised together (aggregate `InfraLaunchError`) so one bring-up
surfaces the full blast radius. Resource gathering (the CA fetch) also fails
hard. PRD 0081 goal + design updated to match (reversed from the earlier
"tolerate per-bottle failures" draft).

Bumps the base.py guardrail cap 580->600 for the new contract surface (the
delegator + 3 abstract primitives); the flow itself lives in gateway_attach.py.

Refs #516, #519.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 01:50:06 +00:00
..

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_sandbox_escape.py
    test_orphan_cleanup.py
    ...
  canaries/
    test_gitleaks_release.py        # opt-in upstream artifact check

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_orphan_cleanup.pynetwork_remove is 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.
  • test_orchestrator_docker_auth.py — drives the real control-plane container and verifies role-scoped authentication.
  • test_multitenant_isolation.py and test_sandbox_escape.py — exercise token/allowlist separation and end-to-end escape attempts.

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. The gitleaks canary downloads the exact release archive pinned by Dockerfile.gateway, verifies its architecture-specific checksum, and executes the binary.

BOT_BOTTLE_RUN_CANARIES=1 python -m scripts.unittest_gate \
  -t . -s tests/canaries -v --minimum-executed 1 --fail-on-skip

What's NOT covered

  • bot_bottle/ssh.py end-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

  1. Pick the directory: tests/unit/ for a pure unit test, tests/integration/ for one that needs a backend.

  2. Filename: test_<topic>.py.

  3. Boilerplate:

    import unittest
    
    from bot_bottle.<module> import <symbol>
    
    class TestThing(unittest.TestCase):
        def test_x(self):
            ...
    
    if __name__ == "__main__":
        unittest.main()
    
  4. Skip guards live in tests._backend and 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 backend BOT_BOTTLE_BACKEND selects and skips unless that backend is available (checking, e.g., Linux + /dev/kvm for 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-test unittest.skip lines.