mirror of
https://github.com/charmbracelet/crush.git
synced 2025-08-02 05:20:46 +03:00
333 lines
8.8 KiB
Go
333 lines
8.8 KiB
Go
package config
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/charmbracelet/crush/internal/env"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// mockShell implements the Shell interface for testing
|
|
type mockShell struct {
|
|
execFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
|
|
}
|
|
|
|
func (m *mockShell) Exec(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if m.execFunc != nil {
|
|
return m.execFunc(ctx, command)
|
|
}
|
|
return "", "", nil
|
|
}
|
|
|
|
func TestShellVariableResolver_ResolveValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
envVars map[string]string
|
|
shellFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
|
|
expected string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "non-variable string returns as-is",
|
|
value: "plain-string",
|
|
expected: "plain-string",
|
|
},
|
|
{
|
|
name: "environment variable resolution",
|
|
value: "$HOME",
|
|
envVars: map[string]string{"HOME": "/home/user"},
|
|
expected: "/home/user",
|
|
},
|
|
{
|
|
name: "missing environment variable returns error",
|
|
value: "$MISSING_VAR",
|
|
envVars: map[string]string{},
|
|
expectError: true,
|
|
},
|
|
|
|
{
|
|
name: "shell command with whitespace trimming",
|
|
value: "$(echo ' spaced ')",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if command == "echo ' spaced '" {
|
|
return " spaced \n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "spaced",
|
|
},
|
|
{
|
|
name: "shell command execution error",
|
|
value: "$(false)",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
return "", "", errors.New("command failed")
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "invalid format returns error",
|
|
value: "$",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
testEnv := env.NewFromMap(tt.envVars)
|
|
resolver := &shellVariableResolver{
|
|
shell: &mockShell{execFunc: tt.shellFunc},
|
|
env: testEnv,
|
|
}
|
|
|
|
result, err := resolver.ResolveValue(tt.value)
|
|
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShellVariableResolver_EnhancedResolveValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
envVars map[string]string
|
|
shellFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
|
|
expected string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "command substitution within string",
|
|
value: "Bearer $(echo token123)",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if command == "echo token123" {
|
|
return "token123\n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "Bearer token123",
|
|
},
|
|
{
|
|
name: "environment variable within string",
|
|
value: "Bearer $TOKEN",
|
|
envVars: map[string]string{"TOKEN": "sk-ant-123"},
|
|
expected: "Bearer sk-ant-123",
|
|
},
|
|
{
|
|
name: "environment variable with braces within string",
|
|
value: "Bearer ${TOKEN}",
|
|
envVars: map[string]string{"TOKEN": "sk-ant-456"},
|
|
expected: "Bearer sk-ant-456",
|
|
},
|
|
{
|
|
name: "mixed command and environment substitution",
|
|
value: "$USER-$(date +%Y)-$HOST",
|
|
envVars: map[string]string{
|
|
"USER": "testuser",
|
|
"HOST": "localhost",
|
|
},
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if command == "date +%Y" {
|
|
return "2024\n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "testuser-2024-localhost",
|
|
},
|
|
{
|
|
name: "multiple command substitutions",
|
|
value: "$(echo hello) $(echo world)",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
switch command {
|
|
case "echo hello":
|
|
return "hello\n", "", nil
|
|
case "echo world":
|
|
return "world\n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "hello world",
|
|
},
|
|
{
|
|
name: "nested parentheses in command",
|
|
value: "$(echo $(echo inner))",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if command == "echo $(echo inner)" {
|
|
return "nested\n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "nested",
|
|
},
|
|
{
|
|
name: "lone dollar with non-variable chars",
|
|
value: "prefix$123suffix", // Numbers can't start variable names
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "dollar with special chars",
|
|
value: "a$@b$#c", // Special chars aren't valid in variable names
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "empty environment variable substitution",
|
|
value: "Bearer $EMPTY_VAR",
|
|
envVars: map[string]string{},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "unmatched command substitution opening",
|
|
value: "Bearer $(echo test",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "unmatched environment variable braces",
|
|
value: "Bearer ${TOKEN",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "command substitution with error",
|
|
value: "Bearer $(false)",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
return "", "", errors.New("command failed")
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "complex real-world example",
|
|
value: "Bearer $(cat /tmp/token.txt | base64 -w 0)",
|
|
shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
|
|
if command == "cat /tmp/token.txt | base64 -w 0" {
|
|
return "c2stYW50LXRlc3Q=\n", "", nil
|
|
}
|
|
return "", "", errors.New("unexpected command")
|
|
},
|
|
expected: "Bearer c2stYW50LXRlc3Q=",
|
|
},
|
|
{
|
|
name: "environment variable with underscores and numbers",
|
|
value: "Bearer $API_KEY_V2",
|
|
envVars: map[string]string{"API_KEY_V2": "sk-test-123"},
|
|
expected: "Bearer sk-test-123",
|
|
},
|
|
{
|
|
name: "no substitution needed",
|
|
value: "Bearer sk-ant-static-token",
|
|
expected: "Bearer sk-ant-static-token",
|
|
},
|
|
{
|
|
name: "incomplete variable at end",
|
|
value: "Bearer $",
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "variable with invalid character",
|
|
value: "Bearer $VAR-NAME", // Hyphen not allowed in variable names
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "multiple invalid variables",
|
|
value: "$1$2$3",
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
testEnv := env.NewFromMap(tt.envVars)
|
|
resolver := &shellVariableResolver{
|
|
shell: &mockShell{execFunc: tt.shellFunc},
|
|
env: testEnv,
|
|
}
|
|
|
|
result, err := resolver.ResolveValue(tt.value)
|
|
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvironmentVariableResolver_ResolveValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
envVars map[string]string
|
|
expected string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "non-variable string returns as-is",
|
|
value: "plain-string",
|
|
expected: "plain-string",
|
|
},
|
|
{
|
|
name: "environment variable resolution",
|
|
value: "$HOME",
|
|
envVars: map[string]string{"HOME": "/home/user"},
|
|
expected: "/home/user",
|
|
},
|
|
{
|
|
name: "environment variable with complex value",
|
|
value: "$PATH",
|
|
envVars: map[string]string{"PATH": "/usr/bin:/bin:/usr/local/bin"},
|
|
expected: "/usr/bin:/bin:/usr/local/bin",
|
|
},
|
|
{
|
|
name: "missing environment variable returns error",
|
|
value: "$MISSING_VAR",
|
|
envVars: map[string]string{},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "empty environment variable returns error",
|
|
value: "$EMPTY_VAR",
|
|
envVars: map[string]string{"EMPTY_VAR": ""},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
testEnv := env.NewFromMap(tt.envVars)
|
|
resolver := NewEnvironmentVariableResolver(testEnv)
|
|
|
|
result, err := resolver.ResolveValue(tt.value)
|
|
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNewShellVariableResolver(t *testing.T) {
|
|
testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
|
|
resolver := NewShellVariableResolver(testEnv)
|
|
|
|
require.NotNil(t, resolver)
|
|
require.Implements(t, (*VariableResolver)(nil), resolver)
|
|
}
|
|
|
|
func TestNewEnvironmentVariableResolver(t *testing.T) {
|
|
testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
|
|
resolver := NewEnvironmentVariableResolver(testEnv)
|
|
|
|
require.NotNil(t, resolver)
|
|
require.Implements(t, (*VariableResolver)(nil), resolver)
|
|
}
|