127 lines
4.2 KiB
Go
127 lines
4.2 KiB
Go
package workflows
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/workflow/engine"
|
|
wfmodels "github.com/kandev/kandev/internal/workflow/models"
|
|
)
|
|
|
|
// TestOfficeDefault_TriggersCompileThroughEngine verifies that every step in
|
|
// the embedded office-default template can be compiled into engine.StepSpec
|
|
// without panic and that the new event-driven triggers surface as compiled
|
|
// engine.Action lists. This pins the template's JSON shape against the
|
|
// engine's compile path so future schema drift is caught at build time.
|
|
func TestOfficeDefault_TriggersCompileThroughEngine(t *testing.T) {
|
|
tmpl := loadTemplateForTest(t, "office-default")
|
|
|
|
for _, def := range tmpl.Steps {
|
|
step := stepFromDefinition(def)
|
|
spec := engine.CompileStep(step)
|
|
|
|
// Every compiled spec must carry the seven new trigger keys, even
|
|
// if the slice is empty — Engine.HandleTrigger reads them by key.
|
|
for _, trig := range []engine.Trigger{
|
|
engine.TriggerOnComment,
|
|
engine.TriggerOnBlockerResolved,
|
|
engine.TriggerOnChildrenCompleted,
|
|
engine.TriggerOnApprovalResolved,
|
|
engine.TriggerOnHeartbeat,
|
|
engine.TriggerOnBudgetAlert,
|
|
engine.TriggerOnAgentError,
|
|
} {
|
|
if _, ok := spec.Events[trig]; !ok {
|
|
t.Errorf("step %q: compiled spec missing trigger %q", def.Name, trig)
|
|
}
|
|
}
|
|
}
|
|
|
|
work := findStep(tmpl.Steps, "Work")
|
|
if work == nil {
|
|
t.Fatal("Work step not present in template")
|
|
}
|
|
workSpec := engine.CompileStep(stepFromDefinition(*work))
|
|
commentActions := workSpec.Events[engine.TriggerOnComment]
|
|
if len(commentActions) != 1 {
|
|
t.Fatalf("Work.on_comment compiled to %d actions, want 1", len(commentActions))
|
|
}
|
|
if commentActions[0].Kind != engine.ActionQueueRun {
|
|
t.Errorf("Work.on_comment[0].Kind = %q, want queue_run", commentActions[0].Kind)
|
|
}
|
|
if commentActions[0].QueueRun == nil {
|
|
t.Fatal("Work.on_comment[0].QueueRun is nil")
|
|
}
|
|
if commentActions[0].QueueRun.Target != engine.TargetPrimary {
|
|
t.Errorf("Work.on_comment[0] target = %q, want primary", commentActions[0].QueueRun.Target)
|
|
}
|
|
|
|
review := findStep(tmpl.Steps, "Review")
|
|
if review == nil {
|
|
t.Fatal("Review step not present in template")
|
|
}
|
|
reviewSpec := engine.CompileStep(stepFromDefinition(*review))
|
|
enterActions := reviewSpec.Events[engine.TriggerOnEnter]
|
|
if len(enterActions) != 2 {
|
|
t.Fatalf("Review.on_enter compiled to %d actions, want 2", len(enterActions))
|
|
}
|
|
if enterActions[0].Kind != engine.ActionClearDecisions {
|
|
t.Errorf("Review.on_enter[0].Kind = %q, want clear_decisions", enterActions[0].Kind)
|
|
}
|
|
if enterActions[1].Kind != engine.ActionQueueRunForEachParticipant {
|
|
t.Errorf("Review.on_enter[1].Kind = %q, want queue_run_for_each_participant", enterActions[1].Kind)
|
|
}
|
|
if enterActions[1].QueueRunForEachParticipant == nil {
|
|
t.Fatal("Review.on_enter[1].QueueRunForEachParticipant is nil")
|
|
}
|
|
if enterActions[1].QueueRunForEachParticipant.Role != "reviewer" {
|
|
t.Errorf("Review.on_enter[1] role = %q, want reviewer", enterActions[1].QueueRunForEachParticipant.Role)
|
|
}
|
|
|
|
completeActions := reviewSpec.Events[engine.TriggerOnTurnComplete]
|
|
if len(completeActions) != 2 {
|
|
t.Fatalf("Review.on_turn_complete compiled to %d actions, want 2", len(completeActions))
|
|
}
|
|
for i, action := range completeActions {
|
|
if action.Guard == nil || action.Guard.WaitForQuorum == nil {
|
|
t.Errorf("Review.on_turn_complete[%d] missing wait_for_quorum guard", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func loadTemplateForTest(t *testing.T, id string) *wfmodels.WorkflowTemplate {
|
|
t.Helper()
|
|
templates, err := LoadTemplates()
|
|
if err != nil {
|
|
t.Fatalf("LoadTemplates: %v", err)
|
|
}
|
|
for _, tmpl := range templates {
|
|
if tmpl.ID == id {
|
|
return tmpl
|
|
}
|
|
}
|
|
t.Fatalf("template %q not found", id)
|
|
return nil
|
|
}
|
|
|
|
func findStep(steps []wfmodels.StepDefinition, name string) *wfmodels.StepDefinition {
|
|
for i := range steps {
|
|
if steps[i].Name == name {
|
|
return &steps[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// stepFromDefinition copies the StepDefinition fields used by engine.CompileStep
|
|
// onto a WorkflowStep. The compiler only reads Name, Position, Prompt, Events.
|
|
func stepFromDefinition(def wfmodels.StepDefinition) *wfmodels.WorkflowStep {
|
|
return &wfmodels.WorkflowStep{
|
|
ID: def.ID,
|
|
Name: def.Name,
|
|
Position: def.Position,
|
|
Prompt: def.Prompt,
|
|
Events: def.Events,
|
|
StageType: def.StageType,
|
|
}
|
|
}
|