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.
156 lines
4.5 KiB
Go
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)
|
|
}
|
|
}
|