438 lines
11 KiB
Go
438 lines
11 KiB
Go
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")
|
||
|
|
}
|
||
|
|
}
|