You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
||||
"github.com/yan1h/agent-coding-workflow/internal/project"
|
||||
)
|
||||
|
||||
// fakeApplier: 内存版 DecompositionApplier,记录建出的 issue 与边。
|
||||
type fakeApplier struct {
|
||||
nextNum int
|
||||
created []string // titles in order
|
||||
edges [][2]int // (blockedNumber, blockerNumber)
|
||||
byNum map[int]*project.Issue // number → issue
|
||||
}
|
||||
|
||||
func newFakeApplier() *fakeApplier {
|
||||
return &fakeApplier{byNum: map[int]*project.Issue{}}
|
||||
}
|
||||
|
||||
func (f *fakeApplier) mk(in project.CreateIssueInput, parent *int) *project.Issue {
|
||||
f.nextNum++
|
||||
prio := 0
|
||||
if in.Priority != nil {
|
||||
prio = *in.Priority
|
||||
}
|
||||
var pid *uuid.UUID
|
||||
if parent != nil {
|
||||
id := uuid.New()
|
||||
pid = &id
|
||||
}
|
||||
iss := &project.Issue{ID: uuid.New(), Number: f.nextNum, Title: in.Title, Priority: prio, ParentID: pid}
|
||||
f.byNum[iss.Number] = iss
|
||||
f.created = append(f.created, in.Title)
|
||||
return iss
|
||||
}
|
||||
|
||||
func (f *fakeApplier) Create(_ context.Context, _ project.Caller, _ string, in project.CreateIssueInput) (*project.Issue, error) {
|
||||
return f.mk(in, nil), nil
|
||||
}
|
||||
func (f *fakeApplier) CreateSubtask(_ context.Context, _ project.Caller, _ string, parentNumber int, in project.CreateIssueInput) (*project.Issue, error) {
|
||||
return f.mk(in, &parentNumber), nil
|
||||
}
|
||||
func (f *fakeApplier) AddDependency(_ context.Context, _ project.Caller, _ string, in project.AddDependencyInput) (*project.Dependency, error) {
|
||||
f.edges = append(f.edges, [2]int{in.BlockedNumber, in.BlockerNumber})
|
||||
return &project.Dependency{ID: uuid.New()}, nil
|
||||
}
|
||||
|
||||
func TestApplyDecomposition_RejectsCycleBeforeAnyWrite(t *testing.T) {
|
||||
f := newFakeApplier()
|
||||
d := Decomposition{
|
||||
Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}, {Ref: "b", Title: "B"}},
|
||||
Edges: []EdgeSpec{
|
||||
{BlockedRef: "a", BlockerRef: "b"},
|
||||
{BlockedRef: "b", BlockerRef: "a"}, // 环
|
||||
},
|
||||
}
|
||||
_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
require.Empty(t, f.created, "校验失败前不应有任何写入")
|
||||
require.Empty(t, f.edges)
|
||||
}
|
||||
|
||||
func TestApplyDecomposition_ValidDAGCreatesIssuesThenEdges(t *testing.T) {
|
||||
f := newFakeApplier()
|
||||
parent := 100
|
||||
f.byNum[parent] = &project.Issue{Number: parent}
|
||||
d := Decomposition{
|
||||
ParentNumber: &parent,
|
||||
Subtasks: []SubtaskSpec{
|
||||
{Ref: "a", Title: "A", Priority: 2},
|
||||
{Ref: "b", Title: "B"},
|
||||
{Ref: "c", Title: "C"},
|
||||
},
|
||||
Edges: []EdgeSpec{
|
||||
{BlockedRef: "a", BlockerRef: "b"},
|
||||
{BlockedRef: "a", BlockerRef: "c"},
|
||||
},
|
||||
}
|
||||
res, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, f.created, 3)
|
||||
require.Len(t, f.edges, 2)
|
||||
// refs 映射到真实 number
|
||||
require.Contains(t, res.RefToNumber, "a")
|
||||
require.Contains(t, res.RefToNumber, "b")
|
||||
require.Contains(t, res.RefToNumber, "c")
|
||||
// 边以真实 number 落库
|
||||
na, nb := res.RefToNumber["a"], res.RefToNumber["b"]
|
||||
require.Contains(t, f.edges, [2]int{na, nb})
|
||||
}
|
||||
|
||||
func TestApplyDecomposition_RejectsEmpty(t *testing.T) {
|
||||
f := newFakeApplier()
|
||||
_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", Decomposition{})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestApplyDecomposition_RejectsUnknownEdgeRef(t *testing.T) {
|
||||
f := newFakeApplier()
|
||||
d := Decomposition{
|
||||
Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}},
|
||||
Edges: []EdgeSpec{{BlockedRef: "a", BlockerRef: "zzz"}},
|
||||
}
|
||||
_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
require.Empty(t, f.created)
|
||||
}
|
||||
|
||||
func TestApplyDecomposition_RejectsDuplicateRef(t *testing.T) {
|
||||
f := newFakeApplier()
|
||||
d := Decomposition{
|
||||
Subtasks: []SubtaskSpec{{Ref: "a", Title: "A"}, {Ref: "a", Title: "A2"}},
|
||||
}
|
||||
_, err := ApplyDecomposition(context.Background(), f, project.Caller{}, "demo", d)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
Reference in New Issue
Block a user