| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- 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)
- }
|