fix(test): actually isolate the docker control-plane auth test's root + network
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>
This commit is contained in:
@@ -14,7 +14,9 @@ orchestrator or gateway.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
import urllib.error
|
import urllib.error
|
||||||
@@ -50,13 +52,42 @@ class _IsolatedOrchestratorService(OrchestratorService):
|
|||||||
class TestDockerControlPlaneAuthIntegration(unittest.TestCase):
|
class TestDockerControlPlaneAuthIntegration(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
suffix = secrets.token_hex(4)
|
suffix = secrets.token_hex(4)
|
||||||
self._tmp = tempfile.TemporaryDirectory()
|
self._tmp = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
|
||||||
self.addCleanup(self._tmp.cleanup)
|
self.addCleanup(self._tmp.cleanup)
|
||||||
|
|
||||||
|
# host_control_plane_token() — both the test's own call below and the
|
||||||
|
# one OrchestratorService makes internally to inject the container's
|
||||||
|
# env var — resolves its path via the *ambient* BOT_BOTTLE_ROOT env
|
||||||
|
# var, not the host_root kwarg passed to the constructor (that kwarg
|
||||||
|
# only controls the DB bind-mount destination). Without pointing the
|
||||||
|
# env var at the same throwaway dir, the "isolated" test reads/writes
|
||||||
|
# the developer's real ~/.bot-bottle/control-plane-token.
|
||||||
|
previous_root = os.environ.get("BOT_BOTTLE_ROOT")
|
||||||
|
|
||||||
|
def _restore_root() -> None:
|
||||||
|
if previous_root is None:
|
||||||
|
os.environ.pop("BOT_BOTTLE_ROOT", None)
|
||||||
|
else:
|
||||||
|
os.environ["BOT_BOTTLE_ROOT"] = previous_root
|
||||||
|
|
||||||
|
os.environ["BOT_BOTTLE_ROOT"] = self._tmp.name
|
||||||
|
self.addCleanup(_restore_root)
|
||||||
|
|
||||||
|
# ensure_running()/DockerGateway create this network but never remove
|
||||||
|
# it — stop() only removes containers — so every run would otherwise
|
||||||
|
# leak one bridge network permanently.
|
||||||
|
network = f"bot-bottle-net-itest-{suffix}"
|
||||||
|
self.addCleanup(
|
||||||
|
lambda: subprocess.run(
|
||||||
|
["docker", "network", "rm", network],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.svc = _IsolatedOrchestratorService(
|
self.svc = _IsolatedOrchestratorService(
|
||||||
orchestrator_name=f"bot-bottle-orch-itest-{suffix}",
|
orchestrator_name=f"bot-bottle-orch-itest-{suffix}",
|
||||||
gateway_name=f"bot-bottle-gw-itest-{suffix}",
|
gateway_name=f"bot-bottle-gw-itest-{suffix}",
|
||||||
network=f"bot-bottle-net-itest-{suffix}",
|
network=network,
|
||||||
port=20000 + secrets.randbelow(10000),
|
port=20000 + secrets.randbelow(10000),
|
||||||
host_root=Path(self._tmp.name),
|
host_root=Path(self._tmp.name),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user