package acp_test import ( "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yan1h/agent-coding-workflow/internal/acp" "github.com/yan1h/agent-coding-workflow/internal/infra/errs" ) func newConfigFileTestSvc(t *testing.T) (acp.AgentKindService, *acp.AgentKind, acp.Caller) { t.Helper() repo := newFakeAgentKindRepo() svc := acp.NewAgentKindService(repo, testEncryptor(t), &agentKindRecordingRecorder{}) admin := acp.Caller{UserID: uuid.New(), IsAdmin: true} k, err := svc.Create(context.Background(), admin, acp.CreateAgentKindInput{ Name: "claude", DisplayName: "Claude", BinaryPath: "/x", ClientType: acp.ClientClaudeCode, Enabled: true, }) require.NoError(t, err) return svc, k, admin } func TestConfigFiles_UpsertListDelete_Roundtrip(t *testing.T) { t.Parallel() ctx := context.Background() svc, k, admin := newConfigFileTestSvc(t) content := `{"model":"claude-sonnet-4-6"}` out, err := svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/settings.json", content) require.NoError(t, err) assert.Equal(t, ".claude/settings.json", out.RelPath) assert.Equal(t, content, out.Content) list, err := svc.ListConfigFiles(ctx, admin, k.ID) require.NoError(t, err) require.Len(t, list, 1) assert.Equal(t, content, list[0].Content) // 解密往返 require.NoError(t, svc.DeleteConfigFile(ctx, admin, k.ID, ".claude/settings.json")) list, err = svc.ListConfigFiles(ctx, admin, k.ID) require.NoError(t, err) assert.Empty(t, list) } func TestConfigFiles_AdminOnly(t *testing.T) { t.Parallel() ctx := context.Background() svc, k, _ := newConfigFileTestSvc(t) user := acp.Caller{UserID: uuid.New(), IsAdmin: false} _, err := svc.ListConfigFiles(ctx, user, k.ID) ae, ok := errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeForbidden, ae.Code) _, err = svc.UpsertConfigFile(ctx, user, k.ID, "a.json", "{}") ae, ok = errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeForbidden, ae.Code) } func TestConfigFiles_RelPathValidation(t *testing.T) { t.Parallel() ctx := context.Background() svc, k, admin := newConfigFileTestSvc(t) bad := []string{ "", "/etc/passwd", "../escape.json", "a/../../b.json", "C:/windows/x.json", "..", ".", } for _, p := range bad { _, err := svc.UpsertConfigFile(ctx, admin, k.ID, p, "{}") ae, ok := errs.As(err) require.True(t, ok, "rel_path %q should be rejected", p) assert.Equal(t, errs.CodeInvalidInput, ae.Code, "rel_path %q", p) } // 反斜杠归一为正斜杠;./ 前缀被 Clean 掉 out, err := svc.UpsertConfigFile(ctx, admin, k.ID, `.codex\config.toml`, `model = "gpt-5.5"`) require.NoError(t, err) assert.Equal(t, ".codex/config.toml", out.RelPath) out, err = svc.UpsertConfigFile(ctx, admin, k.ID, "./a/b.json", "{}") require.NoError(t, err) assert.Equal(t, "a/b.json", out.RelPath) } func TestConfigFiles_SyntaxValidation(t *testing.T) { t.Parallel() ctx := context.Background() svc, k, admin := newConfigFileTestSvc(t) _, err := svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/settings.json", "{not json") ae, ok := errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeInvalidInput, ae.Code) _, err = svc.UpsertConfigFile(ctx, admin, k.ID, ".codex/config.toml", "= broken toml") ae, ok = errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeInvalidInput, ae.Code) // 未识别扩展名不校验 _, err = svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/CLAUDE.md", "# 任意文本") require.NoError(t, err) } func TestConfigFiles_DeleteNotFound(t *testing.T) { t.Parallel() ctx := context.Background() svc, k, admin := newConfigFileTestSvc(t) err := svc.DeleteConfigFile(ctx, admin, k.ID, "missing.json") ae, ok := errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeNotFound, ae.Code) } func TestAgentKindService_ClientType(t *testing.T) { t.Parallel() ctx := context.Background() repo := newFakeAgentKindRepo() svc := acp.NewAgentKindService(repo, testEncryptor(t), nil) admin := acp.Caller{UserID: uuid.New(), IsAdmin: true} // 缺省 → generic k, err := svc.Create(ctx, admin, acp.CreateAgentKindInput{ Name: "k1", DisplayName: "K1", BinaryPath: "/x", Enabled: true, }) require.NoError(t, err) assert.Equal(t, acp.ClientGeneric, k.ClientType) // 非法值拒绝 _, err = svc.Create(ctx, admin, acp.CreateAgentKindInput{ Name: "k2", DisplayName: "K2", BinaryPath: "/x", ClientType: "bogus", Enabled: true, }) ae, ok := errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeInvalidInput, ae.Code) // 更新 client_type ct := acp.ClientCodex updated, err := svc.Update(ctx, admin, k.ID, acp.UpdateAgentKindInput{ClientType: &ct}) require.NoError(t, err) assert.Equal(t, acp.ClientCodex, updated.ClientType) bad := acp.ClientType("nope") _, err = svc.Update(ctx, admin, k.ID, acp.UpdateAgentKindInput{ClientType: &bad}) ae, ok = errs.As(err) require.True(t, ok) assert.Equal(t, errs.CodeInvalidInput, ae.Code) }