This commit is contained in:
2026-06-22 08:55:57 +08:00
parent dbb87823e8
commit 6ade6e8fa9
325 changed files with 41131 additions and 855 deletions
+166
View File
@@ -138,6 +138,18 @@ func (f *fakeProjectSvc) Unarchive(ctx context.Context, c project.Caller, slug s
p.ArchivedAt = nil
return nil
}
func (f *fakeProjectSvc) ListMembers(_ context.Context, _ project.Caller, _ string) ([]*project.Member, error) {
return nil, nil
}
func (f *fakeProjectSvc) AddMember(_ context.Context, _ project.Caller, _ string, _ project.AddMemberInput) (*project.Member, error) {
return nil, nil
}
func (f *fakeProjectSvc) RemoveMember(_ context.Context, _ project.Caller, _ string, _ uuid.UUID) error {
return nil
}
func (f *fakeProjectSvc) MemberRole(_ context.Context, _, _ uuid.UUID) (project.Role, error) {
return project.RoleNone, nil
}
// fakeRequirementSvc implements project.RequirementService.
type fakeRequirementSvc struct {
@@ -203,16 +215,63 @@ func (f *fakeRequirementSvc) Reopen(_ context.Context, _ project.Caller, slug st
return nil
}
// fakeArtifactSvc implements project.ArtifactService.
type fakeArtifactSvc struct {
items []*project.Artifact
}
func (f *fakeArtifactSvc) Create(_ context.Context, c project.Caller, _ string, reqNumber int, in project.CreateArtifactInput) (*project.Artifact, error) {
a := &project.Artifact{
ID: uuid.New(), Phase: in.Phase, Version: len(f.items) + 1,
Content: in.Content, Note: in.Note, CreatedBy: c.UserID,
CreatedAt: time.Now(), Verdict: project.VerdictNone,
}
f.items = append(f.items, a)
return a, nil
}
func (f *fakeArtifactSvc) List(_ context.Context, _ project.Caller, _ string, _ int, phase project.Phase) ([]*project.Artifact, error) {
out := make([]*project.Artifact, 0, len(f.items))
for _, a := range f.items {
if phase == "" || a.Phase == phase {
out = append(out, a)
}
}
return out, nil
}
func (f *fakeArtifactSvc) GetByVersion(_ context.Context, _ project.Caller, _ string, _ int, phase project.Phase, version int) (*project.Artifact, error) {
for _, a := range f.items {
if a.Phase == phase && a.Version == version {
return a, nil
}
}
return nil, errs.New(errs.CodeNotFound, "artifact")
}
func (f *fakeArtifactSvc) Approve(_ context.Context, _ project.Caller, _ string, _ int, phase project.Phase, version int, verdict project.Verdict) (*project.Artifact, error) {
for _, a := range f.items {
if a.Phase == phase && a.Version == version {
a.Verdict = verdict
return a, nil
}
}
return nil, errs.New(errs.CodeNotFound, "artifact")
}
// fakeIssueSvc implements project.IssueService.
type fakeIssueSvc struct {
items []*project.Issue
deps []*project.Dependency
}
func (f *fakeIssueSvc) Create(_ context.Context, _ project.Caller, _ string, in project.CreateIssueInput) (*project.Issue, error) {
prio := 0
if in.Priority != nil {
prio = *in.Priority
}
i := &project.Issue{
ID: uuid.New(), Number: len(f.items) + 1,
Title: in.Title, Description: in.Description,
Status: project.StatusOpen,
Priority: prio,
CreatedAt: time.Now(),
}
f.items = append(f.items, i)
@@ -266,6 +325,80 @@ func (f *fakeIssueSvc) Reopen(_ context.Context, _ project.Caller, slug string,
i.Status = project.StatusOpen
return nil
}
func (f *fakeIssueSvc) CreateSubtask(ctx context.Context, c project.Caller, slug string, parentNumber int, in project.CreateIssueInput) (*project.Issue, error) {
parent, err := f.Get(ctx, c, slug, parentNumber)
if err != nil {
return nil, err
}
i, err := f.Create(ctx, c, slug, in)
if err != nil {
return nil, err
}
pid := parent.ID
i.ParentID = &pid
return i, nil
}
func (f *fakeIssueSvc) AddDependency(ctx context.Context, c project.Caller, slug string, in project.AddDependencyInput) (*project.Dependency, error) {
if in.BlockedNumber == in.BlockerNumber {
return nil, errs.New(errs.CodeInvalidInput, "issue 不能依赖自身")
}
blocked, err := f.Get(ctx, c, slug, in.BlockedNumber)
if err != nil {
return nil, err
}
blocker, err := f.Get(ctx, c, slug, in.BlockerNumber)
if err != nil {
return nil, err
}
d := &project.Dependency{ID: uuid.New(), BlockedID: blocked.ID, BlockerID: blocker.ID, CreatedAt: time.Now()}
f.deps = append(f.deps, d)
return d, nil
}
func (f *fakeIssueSvc) RemoveDependency(ctx context.Context, c project.Caller, slug string, blockedNumber, blockerNumber int) error {
blocked, err := f.Get(ctx, c, slug, blockedNumber)
if err != nil {
return err
}
blocker, err := f.Get(ctx, c, slug, blockerNumber)
if err != nil {
return err
}
for idx, d := range f.deps {
if d.BlockedID == blocked.ID && d.BlockerID == blocker.ID {
f.deps = append(f.deps[:idx], f.deps[idx+1:]...)
return nil
}
}
return errs.New(errs.CodeNotFound, "依赖不存在")
}
func (f *fakeIssueSvc) ListDependencies(ctx context.Context, c project.Caller, slug string, number int) ([]*project.Issue, []*project.Issue, error) {
iss, err := f.Get(ctx, c, slug, number)
if err != nil {
return nil, nil, err
}
byID := func(id uuid.UUID) *project.Issue {
for _, x := range f.items {
if x.ID == id {
return x
}
}
return nil
}
var blockedBy, blocks []*project.Issue
for _, d := range f.deps {
if d.BlockedID == iss.ID {
if b := byID(d.BlockerID); b != nil {
blockedBy = append(blockedBy, b)
}
}
if d.BlockerID == iss.ID {
if b := byID(d.BlockedID); b != nil {
blocks = append(blocks, b)
}
}
}
return blockedBy, blocks, nil
}
// fakeWorkspaceSvc implements workspace.WorkspaceService.
type fakeWorkspaceSvc struct {
@@ -359,6 +492,7 @@ func testDeps() ServerDeps {
Projects: newFakeProjectSvc(proj),
Reqs: &fakeRequirementSvc{},
Issues: &fakeIssueSvc{},
Artifacts: &fakeArtifactSvc{},
Workspaces: &fakeWorkspaceSvc{},
}
}
@@ -607,6 +741,38 @@ func TestSetRequirementPhase(t *testing.T) {
assert.Equal(t, "auditing", out.Requirement.Phase)
}
func TestApproveRequirementArtifact(t *testing.T) {
deps := testDeps()
_, err := callTool(ctxWithAuth(Scope{}), deps, "add_requirement_artifact", map[string]any{
"project_slug": "test-project", "number": float64(1), "phase": "auditing", "content": "# audit",
})
require.NoError(t, err)
result, err := callTool(ctxWithAuth(Scope{}), deps, "approve_requirement_artifact", map[string]any{
"project_slug": "test-project", "number": float64(1), "phase": "auditing",
"version": float64(1), "verdict": "pass",
})
require.NoError(t, err)
var out artifactCreateOut
unmarshalStructured(t, result, &out)
assert.True(t, out.OK)
assert.Equal(t, "pass", out.Artifact.Verdict)
assert.True(t, out.Artifact.Approved)
}
func TestApproveRequirementArtifact_ScopeGated(t *testing.T) {
deps := testDeps()
// scope 不含 approve_requirement_artifact → CheckToolFromCtx 拒绝(IsError)。
result, err := callTool(ctxWithAuth(Scope{Tools: []string{"add_requirement_artifact"}}), deps,
"approve_requirement_artifact", map[string]any{
"project_slug": "test-project", "number": float64(1), "phase": "auditing",
"version": float64(1), "verdict": "pass",
})
require.NoError(t, err)
assert.True(t, result.IsError, "token scope without approve_requirement_artifact must deny")
}
func TestListRequirements(t *testing.T) {
deps := testDeps()