Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

137 lines
4.5 KiB
Go

package runtimeprep
import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func writeManagedClaudePointer(t *testing.T, stateDir string, executable string) {
t.Helper()
pointerPath := filepath.Join(stateDir, filepath.FromSlash(claudeCodeManagedPointerRelPath))
if err := os.MkdirAll(filepath.Dir(pointerPath), 0o755); err != nil {
t.Fatal(err)
}
content, err := json.Marshal(map[string]string{
"version": "2.1.201",
"executable": executable,
})
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(pointerPath, content, 0o644); err != nil {
t.Fatal(err)
}
}
func writeFakeExecutable(t *testing.T, path string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
}
func TestClaudeCodeExecutableEnvPrefersExplicitOverride(t *testing.T) {
stateDir := t.TempDir()
managed := filepath.Join(stateDir, "claude-managed")
writeFakeExecutable(t, managed)
writeManagedClaudePointer(t, stateDir, managed)
t.Setenv(claudeCodeExecutableEnvName, "/custom/claude")
env := ClaudeCodePreparer{StateDir: stateDir}.claudeCodeExecutableEnv()
if len(env) != 1 || env[0] != claudeCodeExecutableEnvName+"=/custom/claude" {
t.Fatalf("env = %v, want explicit override", env)
}
}
func TestClaudeCodeExecutableEnvUsesManagedPointer(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("executable-bit checks do not apply on windows")
}
stateDir := t.TempDir()
managed := filepath.Join(stateDir, "agent-providers", "claude-code", "versions", "2.1.201", "claude")
writeFakeExecutable(t, managed)
writeManagedClaudePointer(t, stateDir, managed)
t.Setenv(claudeCodeExecutableEnvName, "")
env := ClaudeCodePreparer{StateDir: stateDir}.claudeCodeExecutableEnv()
if len(env) != 1 || env[0] != claudeCodeFallbackExecutableEnvName+"="+managed {
t.Fatalf("env = %v, want managed fallback %s", env, managed)
}
}
func TestClaudeCodeExecutableEnvIgnoresDanglingPointer(t *testing.T) {
stateDir := t.TempDir()
writeManagedClaudePointer(t, stateDir, filepath.Join(stateDir, "missing-binary"))
t.Setenv(claudeCodeExecutableEnvName, "")
// Force PATH lookup to fail so the dangling pointer would be the only
// candidate; the result must then be empty.
t.Setenv("PATH", t.TempDir())
env := ClaudeCodePreparer{StateDir: stateDir}.claudeCodeExecutableEnv()
if len(env) != 0 {
t.Fatalf("env = %v, want empty for dangling pointer", env)
}
}
func TestClaudeCodeExecutableEnvFallsBackToPathClaude(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("PATH shim helper writes a shell script")
}
binDir := t.TempDir()
claudePath := filepath.Join(binDir, "claude")
writeFakeExecutable(t, claudePath)
t.Setenv(claudeCodeExecutableEnvName, "")
t.Setenv("PATH", binDir)
env := ClaudeCodePreparer{StateDir: t.TempDir()}.claudeCodeExecutableEnv()
if len(env) != 1 || !strings.HasSuffix(env[0], "="+claudePath) ||
!strings.HasPrefix(env[0], claudeCodeFallbackExecutableEnvName+"=") {
t.Fatalf("env = %v, want PATH fallback %s", env, claudePath)
}
}
func TestClaudePrepareExposesPersonalSkillsAsAdditionalDirectory(t *testing.T) {
personalSkillRoot := filepath.Join(t.TempDir(), "shared", "skills")
skillPath := filepath.Join(personalSkillRoot, "shared-skill", "SKILL.md")
writeSidecarTestFile(t, skillPath, "---\nname: shared-skill\n---\nshared\n")
preparer := newTestPreparer(t.TempDir())
preparer.RegisterProvider(ClaudeCodePreparer{PersonalSkillRoot: personalSkillRoot})
prepared, err := preparer.Prepare(t.Context(), PrepareInput{
WorkspaceID: "workspace-1",
AgentSessionID: "session-1",
AgentTargetID: "local:claude-code",
Provider: "claude-code",
Cwd: t.TempDir(),
})
if err != nil {
t.Fatal(err)
}
var directories []string
if err := json.Unmarshal([]byte(envValue(prepared.Env, claudeAdditionalDirectoriesEnv)), &directories); err != nil {
t.Fatalf("decode Claude additional directories: %v", err)
}
if len(directories) != 1 {
t.Fatalf("Claude additional directories = %#v, want one projection", directories)
}
projectedSkill := filepath.Join(directories[0], ".claude", "skills", "shared-skill", "SKILL.md")
projectedInfo, err := os.Stat(projectedSkill)
if err != nil {
t.Fatalf("projected Claude personal Skill missing: %v", err)
}
sourceInfo, err := os.Stat(skillPath)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(projectedInfo, sourceInfo) {
t.Fatal("Claude personal Skill projection is not backed by the stable source")
}
}