Files
wild-central-api/internal/tools/yq_test.go
2025-11-08 20:10:13 +00:00

470 lines
10 KiB
Go

package tools
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestNewYQ(t *testing.T) {
t.Run("creates YQ instance with default path", func(t *testing.T) {
yq := NewYQ()
if yq == nil {
t.Fatal("NewYQ() returned nil")
}
if yq.yqPath == "" {
t.Error("yqPath should not be empty")
}
})
}
func TestYQGet(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) (string, string)
expression string
want string
wantErr bool
}{
{
name: "get simple value",
setup: func(tmpDir string) (string, string) {
yamlContent := `name: test
version: "1.0"
`
filePath := filepath.Join(tmpDir, "test.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath, ".name"
},
want: "test",
wantErr: false,
},
{
name: "get nested value",
setup: func(tmpDir string) (string, string) {
yamlContent := `person:
name: John
age: 30
`
filePath := filepath.Join(tmpDir, "nested.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath, ".person.name"
},
want: "John",
wantErr: false,
},
{
name: "non-existent file returns error",
setup: func(tmpDir string) (string, string) {
return filepath.Join(tmpDir, "nonexistent.yaml"), ".name"
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
filePath, expression := tt.setup(tmpDir)
yq := NewYQ()
got, err := yq.Get(filePath, expression)
if (err != nil) != tt.wantErr {
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got != tt.want {
t.Errorf("Get() = %q, want %q", got, tt.want)
}
})
}
}
func TestYQSet(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) string
expression string
value string
verify func(t *testing.T, filePath string)
wantErr bool
}{
{
name: "set simple value",
setup: func(tmpDir string) string {
yamlContent := `name: old`
filePath := filepath.Join(tmpDir, "test.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
expression: ".name",
value: "new",
verify: func(t *testing.T, filePath string) {
content, err := os.ReadFile(filePath)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(content), "new") {
t.Errorf("File does not contain expected value 'new': %s", content)
}
},
wantErr: false,
},
{
name: "set value with special characters",
setup: func(tmpDir string) string {
yamlContent := `message: hello`
filePath := filepath.Join(tmpDir, "special.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
expression: ".message",
value: `hello "world"`,
verify: func(t *testing.T, filePath string) {
content, err := os.ReadFile(filePath)
if err != nil {
t.Fatal(err)
}
// Should contain escaped quotes
if !strings.Contains(string(content), "hello") {
t.Errorf("File does not contain expected value: %s", content)
}
},
wantErr: false,
},
{
name: "expression without leading dot gets dot prepended",
setup: func(tmpDir string) string {
yamlContent := `key: value`
filePath := filepath.Join(tmpDir, "nodot.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
expression: "key",
value: "newvalue",
verify: func(t *testing.T, filePath string) {
content, err := os.ReadFile(filePath)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(content), "newvalue") {
t.Errorf("File does not contain expected value: %s", content)
}
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
filePath := tt.setup(tmpDir)
yq := NewYQ()
err := yq.Set(filePath, tt.expression, tt.value)
if (err != nil) != tt.wantErr {
t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.verify != nil {
tt.verify(t, filePath)
}
})
}
}
func TestYQDelete(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) string
expression string
verify func(t *testing.T, filePath string)
wantErr bool
}{
{
name: "delete simple key",
setup: func(tmpDir string) string {
yamlContent := `name: test
version: "1.0"
`
filePath := filepath.Join(tmpDir, "delete.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
expression: ".name",
verify: func(t *testing.T, filePath string) {
content, err := os.ReadFile(filePath)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(content), "name:") {
t.Errorf("Key 'name' was not deleted: %s", content)
}
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
filePath := tt.setup(tmpDir)
yq := NewYQ()
err := yq.Delete(filePath, tt.expression)
if (err != nil) != tt.wantErr {
t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.verify != nil {
tt.verify(t, filePath)
}
})
}
}
func TestYQValidate(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) string
wantErr bool
}{
{
name: "valid YAML",
setup: func(tmpDir string) string {
yamlContent := `name: test
version: "1.0"
nested:
key: value
`
filePath := filepath.Join(tmpDir, "valid.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
wantErr: false,
},
{
name: "invalid YAML",
setup: func(tmpDir string) string {
invalidYaml := `name: test
invalid indentation
version: "1.0"
`
filePath := filepath.Join(tmpDir, "invalid.yaml")
if err := os.WriteFile(filePath, []byte(invalidYaml), 0644); err != nil {
t.Fatal(err)
}
return filePath
},
wantErr: true,
},
{
name: "non-existent file",
setup: func(tmpDir string) string {
return filepath.Join(tmpDir, "nonexistent.yaml")
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
filePath := tt.setup(tmpDir)
yq := NewYQ()
err := yq.Validate(filePath)
if (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestYQExec(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) (string, []string)
wantErr bool
}{
{
name: "exec with valid args",
setup: func(tmpDir string) (string, []string) {
yamlContent := `name: test`
filePath := filepath.Join(tmpDir, "exec.yaml")
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
t.Fatal(err)
}
return filePath, []string{"eval", ".name", filePath}
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
_, args := tt.setup(tmpDir)
yq := NewYQ()
output, err := yq.Exec(args...)
if (err != nil) != tt.wantErr {
t.Errorf("Exec() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && len(output) == 0 {
t.Error("Exec() returned empty output")
}
})
}
}
func TestCleanYQOutput(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{
name: "removes trailing newline",
input: "value\n",
want: "value",
},
{
name: "converts null to empty string",
input: "null",
want: "",
},
{
name: "removes whitespace",
input: " value \n",
want: "value",
},
{
name: "handles empty string",
input: "",
want: "",
},
{
name: "handles multiple newlines",
input: "value\n\n",
want: "value",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CleanYQOutput(tt.input)
if got != tt.want {
t.Errorf("CleanYQOutput(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestYQMerge(t *testing.T) {
tests := []struct {
name string
setup func(tmpDir string) (string, string, string)
verify func(t *testing.T, outputPath string)
wantErr bool
}{
{
name: "merge two files",
setup: func(tmpDir string) (string, string, string) {
file1 := filepath.Join(tmpDir, "file1.yaml")
file2 := filepath.Join(tmpDir, "file2.yaml")
output := filepath.Join(tmpDir, "output.yaml")
if err := os.WriteFile(file1, []byte("key1: value1\n"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(file2, []byte("key2: value2\n"), 0644); err != nil {
t.Fatal(err)
}
return file1, file2, output
},
verify: func(t *testing.T, outputPath string) {
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
t.Error("Output file was not created")
}
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Skip if yq is not available
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
t.Skip("yq not installed, skipping test")
}
tmpDir := t.TempDir()
file1, file2, output := tt.setup(tmpDir)
yq := NewYQ()
err := yq.Merge(file1, file2, output)
if (err != nil) != tt.wantErr {
t.Errorf("Merge() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && tt.verify != nil {
tt.verify(t, output)
}
})
}
}