完善 MCP 工具

This commit is contained in:
2026-06-11 08:21:09 +08:00
parent c0f15050d0
commit 1415d3b43b
20 changed files with 3722 additions and 61 deletions
+102
View File
@@ -0,0 +1,102 @@
package chat
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
)
// tplFakeRepo embeds fakeRepo and overrides prompt template methods.
type tplFakeRepo struct {
*fakeRepo
templates map[uuid.UUID]*PromptTemplate
}
func newTplFakeRepo() *tplFakeRepo {
return &tplFakeRepo{fakeRepo: newFakeRepo(), templates: map[uuid.UUID]*PromptTemplate{}}
}
func (r *tplFakeRepo) GetPromptTemplate(_ context.Context, id uuid.UUID) (*PromptTemplate, error) {
return r.templates[id], nil // 未命中返回 (nil, nil),与真实 repo 语义一致
}
func (r *tplFakeRepo) UpdatePromptTemplate(_ context.Context, id uuid.UUID, name, content string) error {
t, ok := r.templates[id]
if !ok {
return errs.New(errs.CodeChatTemplateNotFound, "template not found")
}
t.Name, t.Content = name, content
return nil
}
func (r *tplFakeRepo) DeletePromptTemplate(_ context.Context, id uuid.UUID) error {
delete(r.templates, id)
return nil
}
func newTplSvc(repo *tplFakeRepo) TemplateService {
return NewTemplateService(repo, noopAuditor{}, discardLogger())
}
func addTplFixture(repo *tplFakeRepo, scope TemplateScope, owner *uuid.UUID) *PromptTemplate {
t := &PromptTemplate{ID: uuid.New(), Name: "tpl", Content: "c", Scope: scope, OwnerID: owner}
repo.templates[t.ID] = t
return t
}
func requireForbidden(t *testing.T, err error) {
t.Helper()
ae, ok := errs.As(err)
require.True(t, ok)
assert.Equal(t, errs.CodeForbidden, ae.Code)
}
func TestTemplateUpdate_SystemScopeRequiresAdmin(t *testing.T) {
repo := newTplFakeRepo()
svc := newTplSvc(repo)
sys := addTplFixture(repo, TemplateScopeSystem, nil)
uid := uuid.New()
// 非 admin → 拒绝
err := svc.Update(context.Background(), uid, false, sys.ID, "x", "y")
requireForbidden(t, err)
assert.Equal(t, "tpl", repo.templates[sys.ID].Name)
// admin → 允许
require.NoError(t, svc.Update(context.Background(), uid, true, sys.ID, "x", "y"))
assert.Equal(t, "x", repo.templates[sys.ID].Name)
}
func TestTemplateDelete_SystemScopeRequiresAdmin(t *testing.T) {
repo := newTplFakeRepo()
svc := newTplSvc(repo)
sys := addTplFixture(repo, TemplateScopeSystem, nil)
uid := uuid.New()
err := svc.Delete(context.Background(), uid, false, sys.ID)
requireForbidden(t, err)
assert.Contains(t, repo.templates, sys.ID)
require.NoError(t, svc.Delete(context.Background(), uid, true, sys.ID))
assert.NotContains(t, repo.templates, sys.ID)
}
func TestTemplateUpdateDelete_UserScopeOwnerOnly(t *testing.T) {
repo := newTplFakeRepo()
svc := newTplSvc(repo)
owner := uuid.New()
tpl := addTplFixture(repo, TemplateScopeUser, &owner)
stranger := uuid.New()
// 非 owner(即使 admin)→ 拒绝,保持既有 owner-only 语义
requireForbidden(t, svc.Update(context.Background(), stranger, true, tpl.ID, "x", "y"))
requireForbidden(t, svc.Delete(context.Background(), stranger, true, tpl.ID))
// owner → 允许
require.NoError(t, svc.Update(context.Background(), owner, false, tpl.ID, "x", "y"))
require.NoError(t, svc.Delete(context.Background(), owner, false, tpl.ID))
assert.NotContains(t, repo.templates, tpl.ID)
}