Files
poimen-workflows/internal/routing/validator_test.go
T

541 lines
11 KiB
Go
Raw Normal View History

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'")
}
}