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) }