ACP / MCP

This commit is contained in:
2026-06-12 11:44:19 +08:00
parent 2a9661a6ef
commit 4ea4adfd8e
47 changed files with 2558 additions and 240 deletions
+156
View File
@@ -0,0 +1,156 @@
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)
}