package acp import ( "context" "os" "path/filepath" "runtime" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yan1h/agent-coding-workflow/internal/infra/crypto" ) // cfgRepoStub 只实现 ListConfigFiles;其余方法走嵌入的 nil 接口(调用即 panic)。 type cfgRepoStub struct { Repository files []*ConfigFile } func (s cfgRepoStub) ListConfigFiles(context.Context, uuid.UUID) ([]*ConfigFile, error) { return s.files, nil } func TestMaterializeAgentHome(t *testing.T) { t.Parallel() enc, err := crypto.NewEncryptor([]byte("0123456789abcdef0123456789abcdef")) require.NoError(t, err) kind := &AgentKind{ID: uuid.New(), ClientType: ClientCodex} content := []byte(`model = "gpt-5.5"`) encrypted, err := enc.Encrypt(content) require.NoError(t, err) root := t.TempDir() s := &Supervisor{ repo: cfgRepoStub{files: []*ConfigFile{{AgentKindID: kind.ID, RelPath: ".codex/config.toml", EncryptedContent: encrypted}}}, crypto: enc, cfg: SupervisorConfig{AgentHomesDir: root}, } home, err := s.materializeAgentHome(context.Background(), kind) require.NoError(t, err) assert.Equal(t, filepath.Join(root, kind.ID.String()), home) got, err := os.ReadFile(filepath.Join(home, ".codex", "config.toml")) require.NoError(t, err) assert.Equal(t, content, got) // 二次物化幂等(覆写) _, err = s.materializeAgentHome(context.Background(), kind) require.NoError(t, err) } func TestMaterializeAgentHome_RejectsEscape(t *testing.T) { t.Parallel() enc, err := crypto.NewEncryptor([]byte("0123456789abcdef0123456789abcdef")) require.NoError(t, err) encrypted, err := enc.Encrypt([]byte("x")) require.NoError(t, err) kind := &AgentKind{ID: uuid.New(), ClientType: ClientGeneric} s := &Supervisor{ repo: cfgRepoStub{files: []*ConfigFile{{AgentKindID: kind.ID, RelPath: "../escape.txt", EncryptedContent: encrypted}}}, crypto: enc, cfg: SupervisorConfig{AgentHomesDir: t.TempDir()}, } _, err = s.materializeAgentHome(context.Background(), kind) require.Error(t, err) assert.Contains(t, err.Error(), "escapes agent home") } func TestAgentHomeEnv_ByClientType(t *testing.T) { t.Parallel() home := filepath.Join(string(filepath.Separator), "data", "agent-homes", "x") env := agentHomeEnv(home, ClientClaudeCode) assert.Equal(t, home, env["HOME"]) assert.Equal(t, filepath.Join(home, ".claude"), env["CLAUDE_CONFIG_DIR"]) env = agentHomeEnv(home, ClientCodex) assert.Equal(t, filepath.Join(home, ".codex"), env["CODEX_HOME"]) env = agentHomeEnv(home, ClientGemini) assert.Equal(t, home, env["GEMINI_CLI_HOME"]) env = agentHomeEnv(home, ClientGeneric) assert.Equal(t, map[string]string{"HOME": home}, stripWinEnv(env)) if runtime.GOOS == "windows" { assert.Equal(t, home, agentHomeEnv(home, ClientGeneric)["USERPROFILE"]) } } // stripWinEnv 去掉平台相关的 USERPROFILE,便于跨平台断言。 func stripWinEnv(env map[string]string) map[string]string { out := map[string]string{} for k, v := range env { if k == "USERPROFILE" { continue } out[k] = v } return out }