package routing import ( "testing" ) func TestValidateValidWorkflowSpec(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test-workflow", Input: map[string]interface{}{ "repo": "https://github.com/test/repo", }, States: []State{ { Name: "Clone", Type: StateTypeTask, Resource: "CloneRepoActivity", Parameters: map[string]interface{}{ "repo": "${input.repo}", }, Timeout: "5m", Next: "Analyze", }, { Name: "Analyze", Type: StateTypeTask, Resource: "AnalyzeCodeActivity", Parameters: map[string]interface{}{ "path": "${Clone.output.path}", }, Timeout: "10m", End: true, }, }, } result := validator.ValidateWorkflowSpec(spec) if !result.Valid { t.Errorf("Spec should be valid. Errors: %v", result.Errors) } } func TestValidateWorkflowSpecNilName(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "", States: []State{ { Name: "Test", Type: StateTypeTask, Resource: "CloneRepoActivity", }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with empty name should be invalid") } if len(result.Errors) == 0 { t.Error("Should have validation errors") } } func TestValidateWorkflowSpecNoStates(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{}, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with no states should be invalid") } } func TestValidateInvalidStateTransition(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "A", Type: StateTypeTask, Resource: "CloneRepoActivity", Next: "NonExistent", }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with invalid state transition should be invalid") } } func TestValidateDuplicateStateName(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "A", Type: StateTypeTask, Resource: "CloneRepoActivity", }, { Name: "A", // Duplicate! Type: StateTypeTask, Resource: "CloneRepoActivity", }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with duplicate state names should be invalid") } } func TestValidateUnknownActivity(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "A", Type: StateTypeTask, Resource: "UnknownActivity", }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with unknown activity should be invalid") } } func TestValidatePassState(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "Success", Type: StateTypePass, Result: map[string]interface{}{"status": "ok"}, End: true, }, }, } result := validator.ValidateWorkflowSpec(spec) if !result.Valid { t.Errorf("Pass state spec should be valid: %v", result.Errors) } } func TestValidatePassStateNoResult(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "Success", Type: StateTypePass, End: true, }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Pass state without result should be invalid") } } func TestValidateFailState(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "Fail", Type: StateTypeFail, Error: "TestError", Cause: "For testing", }, }, } result := validator.ValidateWorkflowSpec(spec) if !result.Valid { t.Errorf("Fail state spec should be valid: %v", result.Errors) } } func TestValidateInvalidDuration(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &WorkflowSpec{ Name: "test", States: []State{ { Name: "A", Type: StateTypeTask, Resource: "CloneRepoActivity", Timeout: "invalid", }, }, } result := validator.ValidateWorkflowSpec(spec) if result.Valid { t.Error("Spec with invalid timeout should be invalid") } } func TestValidateCronWorkflowSpec(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &CronWorkflowSpec{ Name: "daily-scan", Type: "CronWorkflow", Schedule: "0 2 * * *", Timezone: "UTC", MaxConcurrent: 1, States: []State{ { Name: "Scan", Type: StateTypeTask, Resource: "SecurityScanActivity", End: true, }, }, } result := validator.ValidateCronWorkflowSpec(spec) if !result.Valid { t.Errorf("Valid cron spec should pass: %v", result.Errors) } } func TestValidateCronWorkflowSpecInvalidTimezone(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &CronWorkflowSpec{ Name: "daily-scan", Type: "CronWorkflow", Schedule: "0 2 * * *", Timezone: "InvalidTimezone", MaxConcurrent: 1, States: []State{ { Name: "Scan", Type: StateTypeTask, Resource: "SecurityScanActivity", End: true, }, }, } result := validator.ValidateCronWorkflowSpec(spec) if result.Valid { t.Error("Cron spec with invalid timezone should be invalid") } } func TestValidateCronWorkflowSpecInvalidSchedule(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &CronWorkflowSpec{ Name: "daily-scan", Type: "CronWorkflow", Schedule: "invalid cron", Timezone: "UTC", MaxConcurrent: 1, States: []State{ { Name: "Scan", Type: StateTypeTask, Resource: "SecurityScanActivity", End: true, }, }, } result := validator.ValidateCronWorkflowSpec(spec) if result.Valid { t.Error("Cron spec with invalid schedule should be invalid") } } func TestValidateCronWorkflowSpecInvalidType(t *testing.T) { kbPath := getKBPath() if kbPath == "" { t.Skip("Knowledge base not found") } kb, err := LoadKnowledgeBase(kbPath) if err != nil { t.Fatalf("Failed to load KB: %v", err) } validator := NewValidator(kb) spec := &CronWorkflowSpec{ Name: "daily-scan", Type: "WrongType", Schedule: "0 2 * * *", Timezone: "UTC", MaxConcurrent: 1, States: []State{ { Name: "Scan", Type: StateTypeTask, Resource: "SecurityScanActivity", End: true, }, }, } result := validator.ValidateCronWorkflowSpec(spec) if result.Valid { t.Error("Cron spec with wrong type should be invalid") } } func TestValidateDuration(t *testing.T) { validDurations := []string{"1s", "5m", "1h", "100ms"} for _, dur := range validDurations { if err := validateDuration(dur); err != nil { t.Errorf("Duration %s should be valid: %v", dur, err) } } invalidDurations := []string{"invalid", "5x", ""} for _, dur := range invalidDurations { if err := validateDuration(dur); err == nil { t.Errorf("Duration %s should be invalid", dur) } } } func TestValidateCronExpression(t *testing.T) { validCrons := []string{ "0 2 * * *", // 2 AM daily "*/5 * * * *", // Every 5 minutes "0 0 1 * *", // First of month "0 12 * * 1", // Noon on Mondays "30 15 * * *", // 3:30 PM daily } for _, cron := range validCrons { if err := validateCronExpression(cron); err != nil { t.Errorf("Cron %s should be valid: %v", cron, err) } } invalidCrons := []string{ "invalid", // Too few fields "0 2 * * * *", // Too many fields "60 * * * *", // Invalid minute "* 25 * * *", // Invalid hour } for _, cron := range invalidCrons { if err := validateCronExpression(cron); err == nil { t.Errorf("Cron %s should be invalid", cron) } } } func TestValidationResultString(t *testing.T) { result := &ValidationResult{ Valid: true, Errors: []ValidationError{}, } str := result.String() if str != "Valid ✓" { t.Errorf("Valid result string should be 'Valid ✓', got: %s", str) } result.Valid = false result.Errors = []ValidationError{ { Path: "spec.name", Message: "name is required", }, } str = result.String() if !contains(str, "Invalid") { t.Error("Invalid result string should contain 'Invalid'") } }