| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- package workflow
- import (
- "testing"
- )
- func TestParseWorkflowDefinition(t *testing.T) {
- tests := []struct {
- name string
- jsonDef string
- wantErr bool
- }{
- {
- name: "valid simple workflow",
- jsonDef: `{
- "nodes": {
- "start": {
- "type": "task",
- "title": "Start",
- "content": "Start task",
- "dependsOn": []
- },
- "end": {
- "type": "task",
- "title": "End",
- "content": "End task",
- "dependsOn": ["start"]
- }
- }
- }`,
- wantErr: false,
- },
- {
- name: "valid parallel workflow",
- jsonDef: `{
- "nodes": {
- "start": {
- "type": "task",
- "title": "Start",
- "content": "Start task",
- "dependsOn": []
- },
- "parallel1": {
- "type": "task",
- "title": "Parallel 1",
- "content": "Parallel task 1",
- "dependsOn": ["start"]
- },
- "parallel2": {
- "type": "task",
- "title": "Parallel 2",
- "content": "Parallel task 2",
- "dependsOn": ["start"]
- },
- "join": {
- "type": "join",
- "title": "Join",
- "content": "Join parallel branches",
- "dependsOn": ["parallel1", "parallel2"]
- }
- }
- }`,
- wantErr: false,
- },
- {
- name: "invalid JSON",
- jsonDef: `{invalid}`,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- def, err := ParseWorkflowDefinition(tt.jsonDef)
- if (err != nil) != tt.wantErr {
- t.Errorf("ParseWorkflowDefinition() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if !tt.wantErr && def == nil {
- t.Error("ParseWorkflowDefinition() returned nil definition")
- }
- })
- }
- }
- func TestParseAndValidate(t *testing.T) {
- tests := []struct {
- name string
- jsonDef string
- wantErr bool
- }{
- {
- name: "empty definition",
- jsonDef: `{}`,
- wantErr: true,
- },
- {
- name: "node missing type",
- jsonDef: `{
- "nodes": {
- "start": {
- "title": "Start"
- }
- }
- }`,
- wantErr: true,
- },
- {
- name: "invalid node type",
- jsonDef: `{
- "nodes": {
- "start": {
- "type": "invalid",
- "title": "Start"
- }
- }
- }`,
- wantErr: true,
- },
- {
- name: "dependency on non-existent node",
- jsonDef: `{
- "nodes": {
- "start": {
- "type": "task",
- "title": "Start",
- "dependsOn": ["nonexistent"]
- }
- }
- }`,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- def, err := ParseWorkflowDefinition(tt.jsonDef)
- if err != nil {
- if !tt.wantErr {
- t.Errorf("ParseWorkflowDefinition() unexpected error = %v", err)
- }
- return
- }
- // Parse succeeded, now validate
- err = def.Validate()
- if (err != nil) != tt.wantErr {
- t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
- }
- }
- func TestValidate(t *testing.T) {
- tests := []struct {
- name string
- jsonDef string
- wantErr bool
- }{
- {
- name: "valid workflow",
- jsonDef: `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "end": {"type": "task", "title": "End", "dependsOn": ["start"]}
- }
- }`,
- wantErr: false,
- },
- {
- name: "cycle detection",
- jsonDef: `{
- "nodes": {
- "a": {"type": "task", "title": "A", "dependsOn": ["b"]},
- "b": {"type": "task", "title": "B", "dependsOn": ["a"]}
- }
- }`,
- wantErr: true,
- },
- {
- name: "self-referential cycle",
- jsonDef: `{
- "nodes": {
- "a": {"type": "task", "title": "A", "dependsOn": ["a"]}
- }
- }`,
- wantErr: true,
- },
- {
- name: "complex cycle",
- jsonDef: `{
- "nodes": {
- "a": {"type": "task", "title": "A", "dependsOn": []},
- "b": {"type": "task", "title": "B", "dependsOn": ["a"]},
- "c": {"type": "task", "title": "C", "dependsOn": ["b"]},
- "d": {"type": "task", "title": "D", "dependsOn": ["c"]},
- "e": {"type": "task", "title": "E", "dependsOn": ["d", "b"]}
- }
- }`,
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- def, err := ParseWorkflowDefinition(tt.jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- err = def.Validate()
- if (err != nil) != tt.wantErr {
- t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
- }
- })
- }
- }
- func TestGetRootNodes(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "middle": {"type": "task", "title": "Middle", "dependsOn": ["start"]},
- "end": {"type": "task", "title": "End", "dependsOn": ["middle"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- roots := def.GetRootNodes()
- if len(roots) != 1 {
- t.Errorf("GetRootNodes() returned %d roots, want 1", len(roots))
- }
- if len(roots) > 0 && roots[0] != "start" {
- t.Errorf("GetRootNodes() returned %v, want [start]", roots)
- }
- }
- func TestGetDependentNodes(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "branch1": {"type": "task", "title": "Branch 1", "dependsOn": ["start"]},
- "branch2": {"type": "task", "title": "Branch 2", "dependsOn": ["start"]},
- "end": {"type": "task", "title": "End", "dependsOn": ["branch1", "branch2"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- dependents := def.GetDependentNodes("start")
- if len(dependents) != 2 {
- t.Errorf("GetDependentNodes(start) returned %d dependents, want 2", len(dependents))
- }
- dependents = def.GetDependentNodes("branch1")
- if len(dependents) != 1 {
- t.Errorf("GetDependentNodes(branch1) returned %d dependents, want 1", len(dependents))
- }
- }
- func TestGetReadyNodes(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "middle": {"type": "task", "title": "Middle", "dependsOn": ["start"]},
- "end": {"type": "task", "title": "End", "dependsOn": ["middle"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- // Initially, only start should be ready
- ready := def.GetReadyNodes(map[string]bool{})
- if len(ready) != 1 || ready[0] != "start" {
- t.Errorf("GetReadyNodes({}) = %v, want [start]", ready)
- }
- // After start is executed, middle should be ready
- ready = def.GetReadyNodes(map[string]bool{"start": true})
- if len(ready) != 1 || ready[0] != "middle" {
- t.Errorf("GetReadyNodes({start:true}) = %v, want [middle]", ready)
- }
- // After middle is executed, end should be ready
- ready = def.GetReadyNodes(map[string]bool{"start": true, "middle": true})
- if len(ready) != 1 || ready[0] != "end" {
- t.Errorf("GetReadyNodes({start:true, middle:true}) = %v, want [end]", ready)
- }
- }
- func TestParallelNodeTypes(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "parallel": {"type": "parallel", "title": "Parallel", "dependsOn": ["start"]},
- "join": {"type": "join", "title": "Join", "dependsOn": ["parallel"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- if err := def.Validate(); err != nil {
- t.Errorf("Validate() error = %v, want nil", err)
- }
- // Verify node types
- if def.Nodes["parallel"].Type != NodeTypeParallel {
- t.Errorf("parallel node type = %v, want %v", def.Nodes["parallel"].Type, NodeTypeParallel)
- }
- if def.Nodes["join"].Type != NodeTypeJoin {
- t.Errorf("join node type = %v, want %v", def.Nodes["join"].Type, NodeTypeJoin)
- }
- }
- func TestConditionNodeType(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "start": {"type": "task", "title": "Start", "dependsOn": []},
- "condition": {"type": "condition", "title": "Condition", "dependsOn": ["start"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- if err := def.Validate(); err != nil {
- t.Errorf("Validate() error = %v, want nil", err)
- }
- if def.Nodes["condition"].Type != NodeTypeCondition {
- t.Errorf("condition node type = %v, want %v", def.Nodes["condition"].Type, NodeTypeCondition)
- }
- }
- func TestTriggerNodeType(t *testing.T) {
- jsonDef := `{
- "nodes": {
- "trigger": {"type": "trigger", "title": "Trigger", "dependsOn": []},
- "task": {"type": "task", "title": "Task", "dependsOn": ["trigger"]}
- }
- }`
- def, err := ParseWorkflowDefinition(jsonDef)
- if err != nil {
- t.Fatalf("ParseWorkflowDefinition() error = %v", err)
- }
- if err := def.Validate(); err != nil {
- t.Errorf("Validate() error = %v, want nil", err)
- }
- if def.Nodes["trigger"].Type != NodeTypeTrigger {
- t.Errorf("trigger node type = %v, want %v", def.Nodes["trigger"].Type, NodeTypeTrigger)
- }
- }
|