You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
||||
)
|
||||
|
||||
// mkIssue 在 demo 项目创建一个游离 issue,返回其编号。
|
||||
func mkIssue(t *testing.T, svc IssueService, caller Caller, title string) int {
|
||||
t.Helper()
|
||||
i, err := svc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: title})
|
||||
require.NoError(t, err)
|
||||
return i.Number
|
||||
}
|
||||
|
||||
func TestCreateSubtask_SetsParentAndPriority(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
parentNum := mkIssue(t, svc, caller, "Parent")
|
||||
prio := 2
|
||||
sub, err := svc.CreateSubtask(context.Background(), caller, "demo", parentNum,
|
||||
CreateIssueInput{Title: "Child", Priority: &prio})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sub.ParentID)
|
||||
require.Equal(t, 2, sub.Priority)
|
||||
|
||||
// 父不存在 → NotFound
|
||||
_, err = svc.CreateSubtask(context.Background(), caller, "demo", 9999, CreateIssueInput{Title: "X"})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeNotFound, ae.Code)
|
||||
}
|
||||
|
||||
func TestCreateIssue_RejectsBadPriority(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
bad := 5
|
||||
_, err := svc.Create(context.Background(), caller, "demo", CreateIssueInput{Title: "X", Priority: &bad})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestAddDependency_RejectsSelf(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: a})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestAddDependency_RejectsDuplicate(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
b := mkIssue(t, svc, caller, "B")
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
require.NoError(t, err)
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestAddDependency_RejectsDirectCycle(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
b := mkIssue(t, svc, caller, "B")
|
||||
// A blocked-by B
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
require.NoError(t, err)
|
||||
// B blocked-by A → 直接环,应拒绝
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: b, BlockerNumber: a})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestAddDependency_RejectsTransitiveCycle(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
b := mkIssue(t, svc, caller, "B")
|
||||
c := mkIssue(t, svc, caller, "C")
|
||||
// A blocked-by B, B blocked-by C
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
require.NoError(t, err)
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: b, BlockerNumber: c})
|
||||
require.NoError(t, err)
|
||||
// C blocked-by A → A->B->C->A 传递环,应拒绝
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: c, BlockerNumber: a})
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeInvalidInput, ae.Code)
|
||||
}
|
||||
|
||||
func TestAddDependency_AllowsValidDAG(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
b := mkIssue(t, svc, caller, "B")
|
||||
c := mkIssue(t, svc, caller, "C")
|
||||
// A blocked-by B, A blocked-by C —— 合法 DAG(无环)
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
require.NoError(t, err)
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: c})
|
||||
require.NoError(t, err)
|
||||
|
||||
blockedBy, blocks, err := svc.ListDependencies(context.Background(), caller, "demo", a)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blockedBy, 2)
|
||||
require.Len(t, blocks, 0)
|
||||
}
|
||||
|
||||
func TestRemoveDependency(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
a := mkIssue(t, svc, caller, "A")
|
||||
b := mkIssue(t, svc, caller, "B")
|
||||
_, err := svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: a, BlockerNumber: b})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, svc.RemoveDependency(context.Background(), caller, "demo", a, b))
|
||||
// 再删 → NotFound
|
||||
err = svc.RemoveDependency(context.Background(), caller, "demo", a, b)
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeNotFound, ae.Code)
|
||||
|
||||
blockedBy, _, err := svc.ListDependencies(context.Background(), caller, "demo", a)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blockedBy, 0)
|
||||
}
|
||||
|
||||
func TestListReadyLeafSubtasks_OnlyUnblockedOpen(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
svc, _, _ := newIssueSvc(repo)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
parent := mkIssue(t, svc, caller, "Parent")
|
||||
prio1 := 1
|
||||
prio3 := 3
|
||||
s1, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S1", Priority: &prio1})
|
||||
require.NoError(t, err)
|
||||
s2, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S2", Priority: &prio3})
|
||||
require.NoError(t, err)
|
||||
blocker := mkIssue(t, svc, caller, "Blocker")
|
||||
s3, err := svc.CreateSubtask(context.Background(), caller, "demo", parent, CreateIssueInput{Title: "S3"})
|
||||
require.NoError(t, err)
|
||||
// S3 blocked-by Blocker(未关闭)→ 不就绪
|
||||
_, err = svc.AddDependency(context.Background(), caller, "demo",
|
||||
AddDependencyInput{BlockedNumber: s3.Number, BlockerNumber: blocker})
|
||||
require.NoError(t, err)
|
||||
|
||||
proj, err := repo.GetProjectBySlug(context.Background(), "demo")
|
||||
require.NoError(t, err)
|
||||
ready, err := repo.ListReadyLeafSubtasks(context.Background(), proj.ID, 10)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := map[uuid.UUID]bool{}
|
||||
for _, i := range ready {
|
||||
got[i.ID] = true
|
||||
}
|
||||
require.True(t, got[s1.ID])
|
||||
require.True(t, got[s2.ID])
|
||||
require.False(t, got[s3.ID], "S3 被未关闭 blocker 阻塞,不应就绪")
|
||||
}
|
||||
Reference in New Issue
Block a user