You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user