You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
||||
)
|
||||
|
||||
// fakeWorkspaceState is a controllable WorkspaceStateLookup for Done-gate tests.
|
||||
type fakeWorkspaceState struct {
|
||||
merged bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (f fakeWorkspaceState) PullRequestMerged(_ context.Context, _, _ uuid.UUID) (bool, error) {
|
||||
return f.merged, f.err
|
||||
}
|
||||
|
||||
// seedRequirement creates an open requirement under the seeded "demo" project.
|
||||
func seedRequirement(t *testing.T, repo *fakeRepo, owner uuid.UUID, ws *uuid.UUID) *Requirement {
|
||||
t.Helper()
|
||||
reqSvc, _, _ := newReqSvc(repo)
|
||||
r, err := reqSvc.Create(context.Background(), Caller{UserID: owner}, "demo", CreateRequirementInput{
|
||||
Title: "R", WorkspaceID: ws,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return r
|
||||
}
|
||||
|
||||
func requireGateFailed(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodePhaseGateFailed, ae.Code)
|
||||
}
|
||||
|
||||
func TestPhaseGate_Implementing_BlockedWithoutApprovedAuditing(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
r := seedRequirement(t, repo, owner, nil)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo}, nil)
|
||||
err := gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing)
|
||||
requireGateFailed(t, err)
|
||||
}
|
||||
|
||||
func TestPhaseGate_Implementing_AllowedAfterApproval(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
artSvc, _, _, _ := newArtifactSvc(repo)
|
||||
r := seedRequirement(t, repo, owner, nil)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
|
||||
require.NoError(t, err)
|
||||
_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictPass)
|
||||
require.NoError(t, err)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo}, nil)
|
||||
require.NoError(t, gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing))
|
||||
}
|
||||
|
||||
func TestPhaseGate_Implementing_BlockedWhenVerdictFail(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
artSvc, _, _, _ := newArtifactSvc(repo)
|
||||
r := seedRequirement(t, repo, owner, nil)
|
||||
caller := Caller{UserID: owner}
|
||||
|
||||
_, err := artSvc.Create(context.Background(), caller, "demo", r.Number, CreateArtifactInput{Phase: PhaseAuditing, Content: "report"})
|
||||
require.NoError(t, err)
|
||||
// verdict=fail 不写指针,gate 仍应阻止。
|
||||
_, err = artSvc.Approve(context.Background(), caller, "demo", r.Number, PhaseAuditing, 1, VerdictFail)
|
||||
require.NoError(t, err)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo}, nil)
|
||||
requireGateFailed(t, gate.Check(context.Background(), r, PhasePlanning, PhaseImplementing))
|
||||
}
|
||||
|
||||
func TestPhaseGate_Done_BlockedWhenNoWorkspace(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
r := seedRequirement(t, repo, owner, nil) // no workspace
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: true}}, nil)
|
||||
requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
|
||||
}
|
||||
|
||||
func TestPhaseGate_Done_BlockedWhenNotMerged(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
ws := uuid.New()
|
||||
r := seedRequirement(t, repo, owner, &ws)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: false}}, nil)
|
||||
requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
|
||||
}
|
||||
|
||||
func TestPhaseGate_Done_AllowedWhenMerged(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
ws := uuid.New()
|
||||
r := seedRequirement(t, repo, owner, &ws)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo, Workspace: fakeWorkspaceState{merged: true}}, nil)
|
||||
require.NoError(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
|
||||
}
|
||||
|
||||
func TestPhaseGate_Done_BlockedWhenWorkspaceLookupNil(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
ws := uuid.New()
|
||||
r := seedRequirement(t, repo, owner, &ws)
|
||||
|
||||
// Workspace 缺失 → 优雅降级为"未合并",阻止流转。
|
||||
gate := NewGateRunner(GateDeps{Repo: repo, Workspace: nil}, nil)
|
||||
requireGateFailed(t, gate.Check(context.Background(), r, PhaseReviewing, PhaseDone))
|
||||
}
|
||||
|
||||
func TestPhaseGate_OpenPhases_AlwaysAllowed(t *testing.T) {
|
||||
repo := newFakeRepo()
|
||||
owner := uuid.New()
|
||||
seedProject(t, repo, owner)
|
||||
r := seedRequirement(t, repo, owner, nil)
|
||||
|
||||
gate := NewGateRunner(GateDeps{Repo: repo}, nil)
|
||||
// planning/prototyping/auditing/reviewing 无 entry 网关 → 任意流转放行(回归 any-to-any)。
|
||||
for _, to := range []Phase{PhasePlanning, PhasePrototyping, PhaseAuditing, PhaseReviewing} {
|
||||
require.NoError(t, gate.Check(context.Background(), r, PhasePlanning, to), "to=%s", to)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user