Files
Agent-System/internal/workflow/runtime/orchestrator_test.go
T
WeiCHEN ad96f67442 refactor(workflowdef): make definition/runtime deps unidirectional
Keep registry key validation local to workflowdef with composition-root
injection, adapt FlatCatalog for projectworkflow, and drive the
orchestrator through LiveInstancePort views instead of projectinstance.
2026-09-22 21:44:57 +08:00

156 lines
4.5 KiB
Go

package runtime_test
import (
"context"
"testing"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/workflow/runtime"
"chat-agent-system/internal/workflowdef"
)
type liveInstanceAdapter struct {
svc *projectinstance.Service
}
func (a liveInstanceAdapter) Available() bool {
return a.svc != nil && a.svc.Available()
}
func (a liveInstanceAdapter) GetLiveInstanceDetail(ctx context.Context, projectID int64) (*runtime.LiveInstanceView, *runtime.VersionView, error) {
detail, version, err := a.svc.GetLiveInstanceDetail(ctx, projectID)
if err != nil {
return nil, nil, err
}
return mapLiveInstance(detail), mapVersionView(version), nil
}
func mapLiveInstance(detail *projectinstance.InstanceDetail) *runtime.LiveInstanceView {
if detail == nil {
return nil
}
view := &runtime.LiveInstanceView{
InstanceID: detail.Instance.ID,
ConfigSnapshot: runtime.WorkflowPlanView{
Stages: mapStagePlans(detail.Instance.ConfigSnapshot.Stages),
},
Stages: mapStageInstances(detail.Stages),
}
return view
}
func mapStagePlans(stages []projectinstance.StagePlan) []runtime.StagePlanView {
out := make([]runtime.StagePlanView, 0, len(stages))
for _, st := range stages {
sp := runtime.StagePlanView{StageKey: st.StageKey}
for _, dt := range st.DocTasks {
sp.DocTasks = append(sp.DocTasks, runtime.DocTaskPlanView{
TaskKey: dt.TaskKey,
Duties: runtime.DutyView{
AuthorUserID: dt.Duties.AuthorUserID,
ReviewerUserID: dt.Duties.ReviewerUserID,
ApproverUserID: dt.Duties.ApproverUserID,
},
})
}
out = append(out, sp)
}
return out
}
func mapStageInstances(stages []projectinstance.StageInstance) []runtime.StageInstanceView {
out := make([]runtime.StageInstanceView, 0, len(stages))
for _, st := range stages {
sv := runtime.StageInstanceView{StageKey: st.StageKey}
for _, dt := range st.DocTasks {
sv.DocTasks = append(sv.DocTasks, runtime.DocTaskInstanceView{
ID: dt.ID,
TaskKey: dt.TaskKey,
})
}
out = append(out, sv)
}
return out
}
func mapVersionView(version *workflowdef.Version) *runtime.VersionView {
if version == nil {
return nil
}
view := &runtime.VersionView{}
for _, st := range version.Definition.Stages {
sv := runtime.StageDefView{Key: st.Key}
for _, dt := range st.DocTasks {
dv := runtime.DocTaskDefView{
Key: dt.Key,
ExecutorKey: dt.ExecutorKey,
QualityGateKey: dt.QualityGateKey,
TemplateAssetKey: dt.TemplateAssetKey,
TemplateVersion: dt.TemplateVersion,
}
if dt.Template != nil {
dv.Template = &runtime.TemplateRefView{
AssetKey: dt.Template.AssetKey,
Version: dt.Template.Version,
}
}
sv.DocTasks = append(sv.DocTasks, dv)
}
view.Definition.Stages = append(view.Definition.Stages, sv)
}
return view
}
func TestResolveDocTask_NoneDutiesStayZero(t *testing.T) {
ctx := context.Background()
store := projectinstance.NewMemoryStore()
if err := projectinstance.SeedProjectMeta(store, 500, projectinstance.LifecyclePlanning, "", 1, 0); err != nil {
t.Fatal(err)
}
svc := projectinstance.NewService(store, projectinstance.OptionalDutyResolver(), nil, nil)
if _, err := svc.SaveWorkflowPlan(ctx, 500, 1, true, projectinstance.SavePlanInput{
Plan: projectinstance.OptionalDutyPlan(
projectinstance.DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3},
projectinstance.DocDuty{},
),
ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1,
}); err != nil {
t.Fatalf("save: %v", err)
}
preview, err := svc.ActivationPreview(ctx, 500, 1, true)
if err != nil {
t.Fatalf("preview: %v", err)
}
if _, err := svc.Activate(ctx, 500, 1, true, projectinstance.ActivateInput{
ConfirmationToken: preview.ConfirmationToken,
ExpectedProjectVersion: preview.ExpectedProjectVersion,
ExpectedPlanRevision: preview.ExpectedPlanRevision,
}); err != nil {
t.Fatalf("activate: %v", err)
}
detail, _, err := svc.GetLiveInstanceDetail(ctx, 500)
if err != nil {
t.Fatal(err)
}
var researchID int64
for _, st := range detail.Stages {
for _, dt := range st.DocTasks {
if dt.TaskKey == "research" {
researchID = dt.ID
}
}
}
if researchID == 0 {
t.Fatal("expected research doc task instance id")
}
res, err := runtime.NewOrchestrator(liveInstanceAdapter{svc: svc}).ResolveDocTask(ctx, 500, researchID)
if err != nil {
t.Fatalf("ResolveDocTask: %v", err)
}
if res.AuthorUserID != 0 || res.ReviewerUserID != 0 || res.ApproverUserID != 0 {
t.Fatalf("none duties must stay zero without creator fallback, got %+v", res)
}
}