package routing import ( "strings" "testing" ) func TestResolveInputField(t *testing.T) { input := map[string]interface{}{ "repo": "https://github.com/test/repo", "branch": "main", } resolver := NewJSONPathResolver(input, map[string]interface{}{}) // Test resolving input field value, err := resolver.Resolve("${input.repo}") if err != nil { t.Fatalf("Failed to resolve: %v", err) } if value != "https://github.com/test/repo" { t.Errorf("Expected 'https://github.com/test/repo', got %v", value) } } func TestResolveNestedField(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Clone": map[string]interface{}{ "output": map[string]interface{}{ "path": "/tmp/repo", "commit": "abc123", }, }, } resolver := NewJSONPathResolver(input, stepResults) // Test resolving nested field value, err := resolver.Resolve("${Clone.output.path}") if err != nil { t.Fatalf("Failed to resolve: %v", err) } if value != "/tmp/repo" { t.Errorf("Expected '/tmp/repo', got %v", value) } } func TestResolveDeepNesting(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Analyze": map[string]interface{}{ "output": map[string]interface{}{ "metrics": map[string]interface{}{ "quality": map[string]interface{}{ "score": 0.95, }, }, }, }, } resolver := NewJSONPathResolver(input, stepResults) value, err := resolver.Resolve("${Analyze.output.metrics.quality.score}") if err != nil { t.Fatalf("Failed to resolve: %v", err) } score, ok := value.(float64) if !ok { t.Fatalf("Expected float64, got %T", value) } if score != 0.95 { t.Errorf("Expected 0.95, got %v", score) } } func TestResolveNonTemplate(t *testing.T) { input := map[string]interface{}{} resolver := NewJSONPathResolver(input, map[string]interface{}{}) // Non-template strings should be returned as-is value, err := resolver.Resolve("plain string") if err != nil { t.Fatalf("Failed to resolve: %v", err) } if value != "plain string" { t.Errorf("Expected 'plain string', got %v", value) } } func TestResolveMissingStep(t *testing.T) { input := map[string]interface{}{} resolver := NewJSONPathResolver(input, map[string]interface{}{}) // Should error on missing step _, err := resolver.Resolve("${NonExistentStep.output.field}") if err == nil { t.Error("Expected error for missing step") } } func TestResolveMissingField(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Clone": map[string]interface{}{ "output": map[string]interface{}{ "path": "/tmp/repo", }, }, } resolver := NewJSONPathResolver(input, stepResults) // Should error on missing field _, err := resolver.Resolve("${Clone.output.nonexistent}") if err == nil { t.Error("Expected error for missing field") } } func TestResolveString(t *testing.T) { input := map[string]interface{}{ "repo": "https://github.com/test/repo", } stepResults := map[string]interface{}{ "Clone": map[string]interface{}{ "output": map[string]interface{}{ "path": "/tmp/repo", }, }, } resolver := NewJSONPathResolver(input, stepResults) // Resolve string with multiple expressions result, err := resolver.ResolveString("Repository at ${input.repo} cloned to ${Clone.output.path}") if err != nil { t.Fatalf("Failed to resolve string: %v", err) } expected := "Repository at https://github.com/test/repo cloned to /tmp/repo" if result != expected { t.Errorf("Expected '%s', got '%s'", expected, result) } } func TestResolveStringNoExpressions(t *testing.T) { input := map[string]interface{}{} resolver := NewJSONPathResolver(input, map[string]interface{}{}) // String without expressions should be returned unchanged result, err := resolver.ResolveString("plain string") if err != nil { t.Fatalf("Failed to resolve string: %v", err) } if result != "plain string" { t.Errorf("Expected 'plain string', got '%s'", result) } } func TestResolvePaths(t *testing.T) { input := map[string]interface{}{ "repo": "https://github.com/test/repo", } stepResults := map[string]interface{}{ "Clone": map[string]interface{}{ "output": map[string]interface{}{ "path": "/tmp/repo", }, }, } resolver := NewJSONPathResolver(input, stepResults) // Resolve a map with JSONPath values data := map[string]interface{}{ "repository": "${input.repo}", "path": "${Clone.output.path}", "literal": "just a string", } result, err := resolver.ResolvePaths(data) if err != nil { t.Fatalf("Failed to resolve paths: %v", err) } if result["repository"] != "https://github.com/test/repo" { t.Errorf("repository mismatch: %v", result["repository"]) } if result["path"] != "/tmp/repo" { t.Errorf("path mismatch: %v", result["path"]) } if result["literal"] != "just a string" { t.Errorf("literal mismatch: %v", result["literal"]) } } func TestResolveNestedMap(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Analyze": map[string]interface{}{ "output": map[string]interface{}{ "score": 0.95, }, }, } resolver := NewJSONPathResolver(input, stepResults) // Resolve nested map data := map[string]interface{}{ "analysis": map[string]interface{}{ "quality": "${Analyze.output.score}", }, } result, err := resolver.ResolvePaths(data) if err != nil { t.Fatalf("Failed to resolve nested map: %v", err) } analysisMap := result["analysis"].(map[string]interface{}) if analysisMap["quality"] != 0.95 { t.Errorf("Expected 0.95, got %v", analysisMap["quality"]) } } func TestResolveSlice(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Scan": map[string]interface{}{ "output": map[string]interface{}{ "vulnerabilities": []map[string]interface{}{ {"cve": "CVE-001"}, {"cve": "CVE-002"}, }, }, }, } resolver := NewJSONPathResolver(input, stepResults) // Resolve slice data := map[string]interface{}{ "issues": "${Scan.output.vulnerabilities}", } result, err := resolver.ResolvePaths(data) if err != nil { t.Fatalf("Failed to resolve slice: %v", err) } issues := result["issues"].([]map[string]interface{}) if len(issues) != 2 { t.Errorf("Expected 2 issues, got %d", len(issues)) } } func TestValidatePath(t *testing.T) { resolver := NewJSONPathResolver(map[string]interface{}{}, map[string]interface{}{}) // Valid paths validPaths := []string{ "input.repo", "Clone.output.path", "Analyze.output.metrics.quality.score", } for _, path := range validPaths { if err := resolver.ValidatePath(path); err != nil { t.Errorf("Path '%s' should be valid: %v", path, err) } } // Invalid paths invalidPaths := []string{ "", "singleword", } for _, path := range invalidPaths { if err := resolver.ValidatePath(path); err == nil { t.Errorf("Path '%s' should be invalid", path) } } } func TestGetAvailableSteps(t *testing.T) { stepResults := map[string]interface{}{ "Clone": map[string]interface{}{}, "Analyze": map[string]interface{}{}, "Scan": map[string]interface{}{}, } resolver := NewJSONPathResolver(map[string]interface{}{}, stepResults) steps := resolver.GetAvailableSteps() if len(steps) != 3 { t.Errorf("Expected 3 steps, got %d", len(steps)) } // Check all steps are present stepMap := make(map[string]bool) for _, step := range steps { stepMap[step] = true } if !stepMap["Clone"] || !stepMap["Analyze"] || !stepMap["Scan"] { t.Error("Missing expected steps") } } func TestGetInputFields(t *testing.T) { input := map[string]interface{}{ "repo": "test", "branch": "main", "path": "/tmp", } resolver := NewJSONPathResolver(input, map[string]interface{}{}) fields := resolver.GetInputFields() if len(fields) != 3 { t.Errorf("Expected 3 fields, got %d", len(fields)) } // Check all fields are present fieldMap := make(map[string]bool) for _, field := range fields { fieldMap[field] = true } if !fieldMap["repo"] || !fieldMap["branch"] || !fieldMap["path"] { t.Error("Missing expected input fields") } } func TestResolveWithStringMap(t *testing.T) { input := map[string]interface{}{} stepResults := map[string]interface{}{ "Config": map[string]string{ "url": "https://example.com", "port": "8080", }, } resolver := NewJSONPathResolver(input, stepResults) // Resolve from string map value, err := resolver.Resolve("${Config.url}") if err != nil { t.Fatalf("Failed to resolve: %v", err) } if value != "https://example.com" { t.Errorf("Expected 'https://example.com', got %v", value) } } func TestResolveComplexWorkflow(t *testing.T) { input := map[string]interface{}{ "repo": "https://github.com/test/repo", "branch": "feature/new", } stepResults := map[string]interface{}{ "Clone": map[string]interface{}{ "output": map[string]interface{}{ "path": "/tmp/repo", "commit": "abc123def456", }, }, "Analyze": map[string]interface{}{ "output": map[string]interface{}{ "quality": 0.92, "issues": []string{"issue1", "issue2"}, }, }, } resolver := NewJSONPathResolver(input, stepResults) // Complex workflow parameters params := map[string]interface{}{ "source_repo": "${input.repo}", "target_branch": "${input.branch}", "cloned_path": "${Clone.output.path}", "commit_hash": "${Clone.output.commit}", "quality_score": "${Analyze.output.quality}", "issues_found": "${Analyze.output.issues}", "report": "Quality score is ${Analyze.output.quality} for commit ${Clone.output.commit}", } resolved, err := resolver.ResolvePaths(params) if err != nil { t.Fatalf("Failed to resolve workflow: %v", err) } if resolved["source_repo"] != "https://github.com/test/repo" { t.Error("source_repo mismatch") } if resolved["target_branch"] != "feature/new" { t.Error("target_branch mismatch") } if resolved["cloned_path"] != "/tmp/repo" { t.Error("cloned_path mismatch") } if resolved["commit_hash"] != "abc123def456" { t.Error("commit_hash mismatch") } if resolved["quality_score"] != 0.92 { t.Error("quality_score mismatch") } // Check report string resolution report := resolved["report"].(string) if !strings.Contains(report, "0.92") || !strings.Contains(report, "abc123def456") { t.Errorf("Report not properly resolved: %s", report) } } func TestResolveEmptyInput(t *testing.T) { input := map[string]interface{}{} resolver := NewJSONPathResolver(input, map[string]interface{}{}) // Should resolve to just input when accessing input value, err := resolver.Resolve("${input}") if err != nil { t.Fatalf("Failed to resolve: %v", err) } // Should be empty map inputMap, ok := value.(map[string]interface{}) if !ok || len(inputMap) != 0 { t.Error("Expected empty input map") } }