原型阶段

This commit is contained in:
2026-06-09 23:44:31 +08:00
parent ea1d0fb3bd
commit 01b3375395
22 changed files with 1077 additions and 27 deletions
+67 -3
View File
@@ -2,11 +2,14 @@ package project
import (
"context"
"sort"
"sync"
"testing"
"time"
"github.com/google/uuid"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5/pgconn"
"github.com/stretchr/testify/require"
"github.com/yan1h/agent-coding-workflow/internal/audit"
@@ -20,6 +23,11 @@ type fakeRepo struct {
projects map[uuid.UUID]*Project
requirements map[uuid.UUID]*Requirement
issues map[uuid.UUID]*Issue
prototypes map[uuid.UUID]*Prototype
// 置 true 时,下一次 CreatePrototype 返回一次真实的 PG 唯一约束冲突后自动复位,
// 用于覆盖 prototypeService.createWithRetry 的 version 竞争重试分支。
createPrototypeFailOnce bool
}
func newFakeRepo() *fakeRepo {
@@ -27,12 +35,14 @@ func newFakeRepo() *fakeRepo {
projects: map[uuid.UUID]*Project{},
requirements: map[uuid.UUID]*Requirement{},
issues: map[uuid.UUID]*Issue{},
prototypes: map[uuid.UUID]*Prototype{},
}
}
func (r *fakeRepo) cloneProject(p *Project) *Project { v := *p; return &v }
func (r *fakeRepo) cloneReq(p *Requirement) *Requirement { v := *p; return &v }
func (r *fakeRepo) cloneIssue(p *Issue) *Issue { v := *p; return &v }
func (r *fakeRepo) cloneProject(p *Project) *Project { v := *p; return &v }
func (r *fakeRepo) cloneReq(p *Requirement) *Requirement { v := *p; return &v }
func (r *fakeRepo) cloneIssue(p *Issue) *Issue { v := *p; return &v }
func (r *fakeRepo) clonePrototype(p *Prototype) *Prototype { v := *p; return &v }
func (r *fakeRepo) CreateProject(_ context.Context, p *Project) (*Project, error) {
r.mu.Lock()
@@ -359,6 +369,60 @@ func (r *fakeRepo) ReopenIssue(_ context.Context, id uuid.UUID) error {
return nil
}
func (r *fakeRepo) GetMaxPrototypeVersion(_ context.Context, requirementID uuid.UUID) (int, error) {
r.mu.Lock()
defer r.mu.Unlock()
maxVer := 0
for _, x := range r.prototypes {
if x.RequirementID == requirementID && x.Version > maxVer {
maxVer = x.Version
}
}
return maxVer, nil
}
func (r *fakeRepo) CreatePrototype(_ context.Context, in *Prototype) (*Prototype, error) {
r.mu.Lock()
defer r.mu.Unlock()
if r.createPrototypeFailOnce {
r.createPrototypeFailOnce = false
// 模拟真实 PG 唯一约束冲突,触发 IsUniqueViolation 重试分支。
return nil, &pgconn.PgError{Code: pgerrcode.UniqueViolation}
}
for _, x := range r.prototypes {
if x.RequirementID == in.RequirementID && x.Version == in.Version {
return nil, &pgconn.PgError{Code: pgerrcode.UniqueViolation}
}
}
in.CreatedAt = time.Now()
r.prototypes[in.ID] = r.clonePrototype(in)
return r.clonePrototype(in), nil
}
func (r *fakeRepo) ListPrototypesByRequirement(_ context.Context, requirementID uuid.UUID) ([]*Prototype, error) {
r.mu.Lock()
defer r.mu.Unlock()
out := make([]*Prototype, 0)
for _, x := range r.prototypes {
if x.RequirementID == requirementID {
out = append(out, r.clonePrototype(x))
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Version < out[j].Version })
return out, nil
}
func (r *fakeRepo) GetPrototypeByVersion(_ context.Context, requirementID uuid.UUID, version int) (*Prototype, error) {
r.mu.Lock()
defer r.mu.Unlock()
for _, x := range r.prototypes {
if x.RequirementID == requirementID && x.Version == version {
return r.clonePrototype(x), nil
}
}
return nil, errs.New(errs.CodeNotFound, "prototype 不存在")
}
// spyAudit 收集所有写入的 audit entry,方便断言。
type spyAudit struct {
mu sync.Mutex