Test: der Claude-Code-Weg in der echten Sandbox, mit Stellvertreter (#712)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7a8852fe6b
commit
4940fdefb0
@@ -186,6 +186,65 @@ public sealed class McpSocketTests : IDisposable
|
||||
outcome.Text.ShouldContain("added: result.txt");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #712: The Claude Code path in the real sandbox, with a stand-in for <c>claude</c> —
|
||||
/// the real one does not run on the test machine, and a test has no key. The stand-in
|
||||
/// checks what Claude Code would be given (arguments, key, home, MCP configuration),
|
||||
/// reaches the API host through the proxy, calls the MCP server the way Claude Code
|
||||
/// would (the command from mcp.json) and answers in stream-json.
|
||||
/// </summary>
|
||||
[ForeignRunFact]
|
||||
public async Task The_claude_code_path_holds_together_in_the_real_sandbox()
|
||||
{
|
||||
await using var host = NewHost();
|
||||
await using var web = InstanceHttpHost.TryStart(host, NullLoggerFactory.Instance);
|
||||
web.ShouldNotBeNull();
|
||||
|
||||
var bin = Path.Combine(_root, "fake-claude");
|
||||
Directory.CreateDirectory(bin);
|
||||
var claude = Path.Combine(bin, "claude");
|
||||
File.WriteAllText(claude, string.Join("\n",
|
||||
"#!/bin/sh",
|
||||
"""case " $* " in *dangerously*) echo "bypass passed" >&2; exit 9;; esac""",
|
||||
"""[ -n "$ANTHROPIC_API_KEY" ] || { echo "no key" >&2; exit 8; }""",
|
||||
"""[ -w "$HOME/.claude/settings.json" ] || { echo "no home" >&2; exit 7; }""",
|
||||
"""prompt=$(cat)""",
|
||||
"""api=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 https://api.anthropic.com/ 2>&1)""",
|
||||
"""cmd=$(sed -n 's/.*"command":"\([^"]*\)".*/\1/p' /run/agent/mcp.json)""",
|
||||
"""tools=$(printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | CLAWD_MCP_TOKEN_FILE=/run/agent/mcp-token "$cmd" --mcp-bridge --socket /run/mcp.sock 2>&1 | tr -d '"')""",
|
||||
"""echo "changed by claude" > /work/answer.txt""",
|
||||
"""printf '%s\n' '{"type":"system","subtype":"init","model":"claude-test"}'""",
|
||||
"""printf '{"type":"result","subtype":"success","is_error":false,"num_turns":1,"result":"prompt=%s api=%s tools=%s","total_cost_usd":0.01,"usage":{"input_tokens":10,"output_tokens":5}}\n' "$prompt" "$api" "$tools" """,
|
||||
""));
|
||||
if (OperatingSystem.IsLinux())
|
||||
File.SetUnixFileMode(claude, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
|
||||
|
||||
var sandbox = (Core.Sandbox.BwrapSandbox)Core.Sandbox.BwrapSandbox.Detect(NullLogger.Instance);
|
||||
var helper = Core.Sandbox.SandboxHelper.For(ForeignRunFactAttribute.HelperPath!);
|
||||
sandbox.Helper = helper;
|
||||
var settings = new Core.Runtimes.ClaudeCodeSettings { Executable = claude, ApiKey = "sk-ant-fake" };
|
||||
var runner = new Core.Runtimes.ForeignRunner(sandbox, [new Core.Runtimes.ClaudeCodeRuntime(() => settings)], host.Mcp,
|
||||
InstanceEndpoint.McpSocketPath(host.InstancePath), helper, NullLogger.Instance);
|
||||
|
||||
var agentDir = Path.Combine(host.InstancePath, "Agents", "Agent-coder");
|
||||
Directory.CreateDirectory(Path.Combine(agentDir, "Workspace"));
|
||||
var agent = new AgentConfig
|
||||
{
|
||||
AgentId = "coder", AgentDir = agentDir, WorkspacePath = Path.Combine(agentDir, "Workspace"),
|
||||
Runtime = new Core.Runtimes.AgentRuntimeConfig { Kind = "claude-code", TimeoutMinutes = 2 }
|
||||
};
|
||||
|
||||
var outcome = await runner.RunAsync(new Core.Runtimes.ForeignRunRequest(agent, "run-cc", null, "brief", "fix it"), _ => { }, CancellationToken.None);
|
||||
|
||||
outcome.Status.ShouldBe(AgentRunStatus.Completed, outcome.Text);
|
||||
outcome.Text!.ShouldContain("prompt=fix it");
|
||||
outcome.Text.ShouldNotContain("api=000", customMessage: "the API host is reached through the proxy");
|
||||
outcome.Text.ShouldContain("name:Echo");
|
||||
outcome.Text.ShouldContain("added: answer.txt");
|
||||
outcome.Model.ShouldBe("claude-test");
|
||||
outcome.CostUsd.ShouldBe(0.01m);
|
||||
}
|
||||
|
||||
private sealed class EchoTool : IAgentTool
|
||||
{
|
||||
public string Name => "Echo";
|
||||
|
||||
Reference in New Issue
Block a user