Files
bot-bottle/tests/unit/test_docker_provision_provider_auth.py
T
didericis-claude 0efc07ba67
test / unit (pull_request) Successful in 50s
test / integration (pull_request) Successful in 59s
test / unit (push) Successful in 43s
test / integration (push) Successful in 1m3s
refactor(backend): pass Bottle to provisioners instead of target string
Closes #178.

The backend provision functions now receive a Bottle handle with
exec / cp_in methods instead of a raw target string. Provisioner
modules use bottle.exec and bottle.cp_in in place of inlined
subprocess.run(["docker", "exec"/"cp", ...]) and direct
_smolvm.machine_cp / machine_exec calls. This decouples the
provisioners from backend-specific runtime primitives so future
refactors (e.g. the supervise rework) can swap the bottle's exec
implementation without touching every provisioner.

Each launch.py constructs the Bottle handle before calling
provision so it can be passed in; provision_prompt's return value
is wired back onto the bottle's prompt path attribute after the
fact.
2026-06-03 20:47:37 +00:00

174 lines
5.6 KiB
Python

"""Unit: docker provider auth marker provisioning."""
from __future__ import annotations
import unittest
from pathlib import Path
from unittest.mock import MagicMock
from bot_bottle.agent_provider import (
AgentProvisionDir,
AgentProvisionFile,
AgentProvisionPlan,
)
from bot_bottle.backend import Bottle, BottleSpec, ExecResult
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
from bot_bottle.backend.docker.provision import provider_auth as _provider_auth
from bot_bottle.egress import EgressPlan
from bot_bottle.git_gate import GitGatePlan
from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import PipelockProxyPlan
from bot_bottle.workspace import workspace_plan
def _plan(
*,
codex_auth_file: Path | None = None,
agent_provider_template: str = "codex",
) -> DockerBottlePlan:
manifest = Manifest.from_json_obj({
"bottles": {"dev": {"agent_provider": {"template": "codex"}}},
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
})
spec = BottleSpec(
manifest=manifest,
agent_name="demo",
copy_cwd=False,
user_cwd="/tmp/x",
)
return DockerBottlePlan(
spec=spec,
stage_dir=Path("/tmp/stage"),
slug="demo-abc12",
container_name="bot-bottle-demo-abc12",
container_name_pinned=False,
image="bot-bottle-codex:latest",
derived_image="",
runtime_image="bot-bottle-codex:latest",
dockerfile_path="",
env_file=Path("/tmp/agent.env"),
forwarded_env={},
prompt_file=Path("/tmp/prompt.txt"),
proxy_plan=PipelockProxyPlan(
yaml_path=Path("/tmp/pipelock.yaml"),
slug="demo-abc12",
),
git_gate_plan=GitGatePlan(
slug="demo-abc12",
entrypoint_script=Path("/tmp/git-gate-entrypoint.sh"),
hook_script=Path("/tmp/git-gate-hook"),
access_hook_script=Path("/tmp/git-gate-access-hook"),
upstreams=(),
),
egress_plan=EgressPlan(
slug="demo-abc12",
routes_path=Path("/tmp/routes.yaml"),
routes=(),
token_env_map={},
),
supervise_plan=None,
use_runsc=False,
agent_provision=_agent_provision(
agent_provider_template, codex_auth_file=codex_auth_file,
),
workspace_plan=workspace_plan(spec, guest_home="/home/node"),
)
def _agent_provision(
template: str, *, codex_auth_file: Path | None = None,
) -> AgentProvisionPlan:
if template != "codex":
return AgentProvisionPlan(
template=template,
command=template,
prompt_mode="append_file",
image="",
dockerfile="",
guest_env={},
)
files = [
AgentProvisionFile(
Path("/tmp/codex-config.toml"),
"/home/node/.codex/config.toml",
),
]
if codex_auth_file is not None:
files.append(AgentProvisionFile(
codex_auth_file,
"/home/node/.codex/auth.json",
))
return AgentProvisionPlan(
template="codex",
command="codex",
prompt_mode="read_prompt_file",
image="bot-bottle-codex:latest",
dockerfile="",
guest_env={},
dirs=(AgentProvisionDir("/home/node/.codex"),),
files=tuple(files),
)
def _make_bottle(name: str = "bot-bottle-demo-abc12") -> MagicMock:
bottle = MagicMock(spec=Bottle)
bottle.name = name
bottle.exec.return_value = ExecResult(returncode=0, stdout="", stderr="")
return bottle
class TestProvisionProviderAuth(unittest.TestCase):
def test_noop_for_non_codex_provider(self):
bottle = _make_bottle()
_provider_auth.provision_provider_auth(
_plan(agent_provider_template="claude"), bottle,
)
self.assertEqual(0, bottle.cp_in.call_count)
self.assertEqual(0, bottle.exec.call_count)
def test_codex_provider_trusts_launch_dir_without_auth_file(self):
bottle = _make_bottle()
_provider_auth.provision_provider_auth(_plan(), bottle)
scripts = [c.args[0] for c in bottle.exec.call_args_list]
self.assertTrue(
any("mkdir -p" in s and "/home/node/.codex" in s for s in scripts)
)
cp_calls = [c.args for c in bottle.cp_in.call_args_list]
self.assertIn(
("/tmp/codex-config.toml", "/home/node/.codex/config.toml"),
cp_calls,
)
self.assertTrue(
any("chown" in s and "/home/node/.codex/config.toml" in s for s in scripts)
)
self.assertTrue(
any("chmod" in s and "/home/node/.codex/config.toml" in s for s in scripts)
)
def test_copies_dummy_auth_json_to_codex_home(self):
bottle = _make_bottle()
_provider_auth.provision_provider_auth(
_plan(codex_auth_file=Path("/tmp/codex-auth.json")),
bottle,
)
cp_calls = [c.args for c in bottle.cp_in.call_args_list]
self.assertIn(
("/tmp/codex-config.toml", "/home/node/.codex/config.toml"),
cp_calls,
)
self.assertIn(
("/tmp/codex-auth.json", "/home/node/.codex/auth.json"),
cp_calls,
)
scripts = [c.args[0] for c in bottle.exec.call_args_list]
self.assertTrue(
any("chown" in s and "/home/node/.codex/auth.json" in s for s in scripts)
)
self.assertTrue(
any("chmod" in s and "/home/node/.codex/auth.json" in s for s in scripts)
)
if __name__ == "__main__":
unittest.main()