Files
ai-agent/sidecar/test_claude_args.py
Gabriel Vidal cf01bc00f4 refactor(sidecar): split sidecar.py into focused modules
config / pids / validate / claude_accounts / launch / fork / routes_runs /
routes_accounts, mounted by a thin sidecar.py (same `sidecar:app`, same 24
routes, same startup hook). Mutable state keeps one owner module
(pids._procs, claude_accounts._account_status_cache). Includes the
_claude_args builder from 1de9426 and its test; the Dockerfile's explicit
COPY list gains the new modules.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 23:55:47 +02:00

72 lines
2.9 KiB
Python

"""Every claude launch path builds on _claude_args: the --settings payload
(stop guard hook + inbox opt-in) and the headless flags can't be missed by
one of spawn / resume / fork — the spawn path once was."""
import json
import re
import claude_accounts
import fork
import launch
import routes_runs
from config import CLAUDE_BIN, DISALLOWED_TOOLS, PERMISSION_MODE, STOP_GUARD_TIMEOUT_S
from launch import _claude_args
SID = "11111111-2222-3333-4444-555555555555"
def _settings(args):
assert args.count("--settings") == 1
return json.loads(args[args.index("--settings") + 1])
def test_spawn_and_resume_shapes_share_the_flags():
spawn = _claude_args("hi", "--session-id", SID)
resume = _claude_args("hi", "--resume", SID)
assert spawn[:5] == [CLAUDE_BIN, "-p", "hi", "--session-id", SID]
assert resume[:5] == [CLAUDE_BIN, "-p", "hi", "--resume", SID]
for a in (spawn, resume):
assert a[a.index("--output-format") + 1] == "json"
assert a[a.index("--permission-mode") + 1] == PERMISSION_MODE
if DISALLOWED_TOOLS:
assert " ".join(DISALLOWED_TOOLS) in a
s = _settings(a)
assert s["crossSessionInbound"] == "accept"
def test_settings_carry_the_stop_guard_hook():
s = _settings(_claude_args("hi", "--session-id", SID))
hooks = s["hooks"]["Stop"][0]["hooks"]
assert len(hooks) == 1
cmd = hooks[0]["command"]
assert cmd.endswith("stop_guard.py") and "python" in cmd
assert hooks[0]["timeout"] == STOP_GUARD_TIMEOUT_S
assert hooks[0]["type"] == "command"
def test_stop_guard_can_be_switched_off(monkeypatch):
# _claude_settings reads the flag from launch's own globals (it is bound
# there at import), so that is the module to patch — not config.
monkeypatch.setattr(launch, "STOP_GUARD_ENABLED", False)
s = _settings(_claude_args("hi", "--session-id", SID))
assert "hooks" not in s and s["crossSessionInbound"] == "accept"
def test_every_launch_site_uses_the_builder():
# The builder lives in launch.py; the three launch sites are spawn and
# resume (routes_runs.py) and fork (fork.py).
src = "".join(open(m.__file__, encoding="utf-8").read()
for m in (launch, routes_runs, fork))
# the only literal `CLAUDE_BIN, "-p"` is the builder's own
assert len(re.findall(r'CLAUDE_BIN, "-p"', src)) == 1
assert src.count("_claude_args(prompt, ") == 3 # spawn, resume, fork
def test_run_env_exports_the_wait_ceiling(monkeypatch):
monkeypatch.setattr(claude_accounts, "BG_WAIT_CEILING_MS", "7200000")
env = claude_accounts._run_env(SID, None)
assert env["CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS"] == "7200000"
monkeypatch.setattr(claude_accounts, "BG_WAIT_CEILING_MS", "")
monkeypatch.delenv("CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS", raising=False)
assert "CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS" not in claude_accounts._run_env(SID, None)