Files
osmedeus/internal/executor/foreach_executor_test.go
T
j3ssie 7f339a69f0 feat: add run priority support and server submission API
- Add RunClient for submitting runs to server with priority support (low, normal, high, critical)
- Implement --run-priority and --server-url CLI flags for server submission mode
- Add RunPriority and RunMode fields to database Run model for persistence
- Update CreateRunRequest with priority and run_mode validation
- Implement runServerSubmission() to submit workflows with priority to server API
- Comprehensive E2E tests for run endpoints with priority validation and multiple targets
- Add foreach-preprocess test workflow with variable_pre_process support
2026-01-26 21:14:04 +08:00

345 lines
9.5 KiB
Go

package executor
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/j3ssie/osmedeus/v5/internal/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestForeachExecutor_VariablePreProcess(t *testing.T) {
// Create temp directory for test files
tmpDir := t.TempDir()
// Create test input file with URLs
inputFile := filepath.Join(tmpDir, "urls.txt")
err := os.WriteFile(inputFile, []byte(`https://example.com/path/file.php?id=1
https://example.com/api/v1/users
https://example.com/deep/nested/path/resource
`), 0644)
require.NoError(t, err)
// Create output file path
outputFile := filepath.Join(tmpDir, "output.txt")
// Create dispatcher
dispatcher := NewStepDispatcher()
// Create execution context
execCtx := core.NewExecutionContext("test-workflow", core.KindModule, "test-run-uuid", "test-target")
execCtx.SetVariable("Output", tmpDir)
// Test case 1: Basic pre-processing with get_parent_url
t.Run("get_parent_url pre-processing", func(t *testing.T) {
step := &core.Step{
Name: "test-preprocess",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "url",
VariablePreProcess: "get_parent_url([[url]])",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[url]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output contains parent URLs
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "https://example.com/path/")
assert.Contains(t, outputStr, "https://example.com/api/v1/")
assert.Contains(t, outputStr, "https://example.com/deep/nested/path/")
})
// Test case 2: No pre-processing (existing behavior unchanged)
t.Run("no pre-processing", func(t *testing.T) {
step := &core.Step{
Name: "test-no-preprocess",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "url",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[url]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output contains original URLs
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "https://example.com/path/file.php?id=1")
assert.Contains(t, outputStr, "https://example.com/api/v1/users")
})
// Test case 3: Pre-processing with trim
t.Run("trim pre-processing", func(t *testing.T) {
// Create input with whitespace
trimInputFile := filepath.Join(tmpDir, "trim-input.txt")
err := os.WriteFile(trimInputFile, []byte(` hello
world
`), 0644)
require.NoError(t, err)
step := &core.Step{
Name: "test-trim-preprocess",
Type: core.StepTypeForeach,
Input: trimInputFile,
Variable: "line",
VariablePreProcess: "trim([[line]])",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[line]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output contains trimmed values
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
// The file iterator already trims lines, but the pre-process should work anyway
assert.Contains(t, outputStr, "hello")
assert.Contains(t, outputStr, "world")
})
// Test case 4: Pre-processing with parse_url
t.Run("parse_url pre-processing", func(t *testing.T) {
step := &core.Step{
Name: "test-parse-url-preprocess",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "url",
VariablePreProcess: "parse_url([[url]], '%d')", // Extract domain only
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[url]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output contains just domains
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "example.com")
})
// Test case 5: Pre-processing with chained functions
t.Run("chained functions pre-processing", func(t *testing.T) {
step := &core.Step{
Name: "test-chained-preprocess",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "url",
VariablePreProcess: "to_lower_case(parse_url([[url]], '%d'))",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[url]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "example.com")
})
}
func TestForeachExecutor_VariablePreProcess_ErrorHandling(t *testing.T) {
// Create temp directory for test files
tmpDir := t.TempDir()
// Create test input file
inputFile := filepath.Join(tmpDir, "input.txt")
err := os.WriteFile(inputFile, []byte(`line1
line2
`), 0644)
require.NoError(t, err)
outputFile := filepath.Join(tmpDir, "output.txt")
// Create dispatcher
dispatcher := NewStepDispatcher()
// Create execution context
execCtx := core.NewExecutionContext("test-workflow", core.KindModule, "test-run-uuid", "test-target")
// Test case: Invalid function should fall back to original value
t.Run("invalid function fallback", func(t *testing.T) {
step := &core.Step{
Name: "test-invalid-func",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "line",
VariablePreProcess: "invalid_nonexistent_function([[line]])",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[line]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Should still produce output with original values since invalid function falls back
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "line1")
assert.Contains(t, outputStr, "line2")
})
}
func TestForeachExecutor_VariablePreProcess_SpecialChars(t *testing.T) {
// Create temp directory for test files
tmpDir := t.TempDir()
// Create test input file with URLs containing special characters
inputFile := filepath.Join(tmpDir, "special-urls.txt")
err := os.WriteFile(inputFile, []byte(`https://example.com/path?foo='bar'&baz=1
https://example.com/it's/a/test
`), 0644)
require.NoError(t, err)
outputFile := filepath.Join(tmpDir, "output.txt")
// Create dispatcher
dispatcher := NewStepDispatcher()
// Create execution context
execCtx := core.NewExecutionContext("test-workflow", core.KindModule, "test-run-uuid", "test-target")
t.Run("special characters in URL", func(t *testing.T) {
step := &core.Step{
Name: "test-special-chars",
Type: core.StepTypeForeach,
Input: inputFile,
Variable: "url",
VariablePreProcess: "get_parent_url([[url]])",
Threads: "1",
Step: &core.Step{
Type: core.StepTypeBash,
Command: "echo '[[url]]' >> " + outputFile,
},
}
// Clean output file
_ = os.Remove(outputFile)
ctx := context.Background()
result, err := dispatcher.Dispatch(ctx, step, execCtx)
require.NoError(t, err)
assert.Equal(t, core.StepStatusSuccess, result.Status)
// Verify output contains parent URLs
output, err := os.ReadFile(outputFile)
require.NoError(t, err)
outputStr := string(output)
assert.Contains(t, outputStr, "https://example.com/")
})
}
func TestAutoQuoteForJS(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "simple string",
input: "hello",
expected: "'hello'",
},
{
name: "string with single quote",
input: "it's a test",
expected: "'it\\'s a test'",
},
{
name: "URL",
input: "https://example.com/path?foo=bar",
expected: "'https://example.com/path?foo=bar'",
},
{
name: "URL with quotes",
input: "https://example.com/path?foo='bar'",
expected: "'https://example.com/path?foo=\\'bar\\''",
},
{
name: "empty string",
input: "",
expected: "''",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := autoQuoteForJS(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}