8 "github.com/charmbracelet/crush/internal/env"
9 "github.com/stretchr/testify/require"
12// mockShell implements the Shell interface for testing
13type mockShell struct {
14 execFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
17func (m *mockShell) Exec(ctx context.Context, command string) (stdout, stderr string, err error) {
18 if m.execFunc != nil {
19 return m.execFunc(ctx, command)
24func TestShellVariableResolver_ResolveValue(t *testing.T) {
28 envVars map[string]string
29 shellFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
34 name: "non-variable string returns as-is",
35 value: "plain-string",
36 expected: "plain-string",
39 name: "environment variable resolution",
41 envVars: map[string]string{"HOME": "/home/user"},
42 expected: "/home/user",
45 name: "missing environment variable returns error",
46 value: "$MISSING_VAR",
47 envVars: map[string]string{},
52 name: "shell command with whitespace trimming",
53 value: "$(echo ' spaced ')",
54 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
55 if command == "echo ' spaced '" {
56 return " spaced \n", "", nil
58 return "", "", errors.New("unexpected command")
63 name: "shell command execution error",
65 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
66 return "", "", errors.New("command failed")
71 name: "invalid format returns error",
77 for _, tt := range tests {
78 t.Run(tt.name, func(t *testing.T) {
79 testEnv := env.NewFromMap(tt.envVars)
80 resolver := &shellVariableResolver{
81 shell: &mockShell{execFunc: tt.shellFunc},
85 result, err := resolver.ResolveValue(tt.value)
90 require.NoError(t, err)
91 require.Equal(t, tt.expected, result)
97func TestShellVariableResolver_EnhancedResolveValue(t *testing.T) {
101 envVars map[string]string
102 shellFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
107 name: "command substitution within string",
108 value: "Bearer $(echo token123)",
109 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
110 if command == "echo token123" {
111 return "token123\n", "", nil
113 return "", "", errors.New("unexpected command")
115 expected: "Bearer token123",
118 name: "environment variable within string",
119 value: "Bearer $TOKEN",
120 envVars: map[string]string{"TOKEN": "sk-ant-123"},
121 expected: "Bearer sk-ant-123",
124 name: "environment variable with braces within string",
125 value: "Bearer ${TOKEN}",
126 envVars: map[string]string{"TOKEN": "sk-ant-456"},
127 expected: "Bearer sk-ant-456",
130 name: "mixed command and environment substitution",
131 value: "$USER-$(date +%Y)-$HOST",
132 envVars: map[string]string{
136 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
137 if command == "date +%Y" {
138 return "2024\n", "", nil
140 return "", "", errors.New("unexpected command")
142 expected: "testuser-2024-localhost",
145 name: "multiple command substitutions",
146 value: "$(echo hello) $(echo world)",
147 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
150 return "hello\n", "", nil
152 return "world\n", "", nil
154 return "", "", errors.New("unexpected command")
156 expected: "hello world",
159 name: "nested parentheses in command",
160 value: "$(echo $(echo inner))",
161 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
162 if command == "echo $(echo inner)" {
163 return "nested\n", "", nil
165 return "", "", errors.New("unexpected command")
170 name: "lone dollar with non-variable chars",
171 value: "prefix$123suffix", // Numbers can't start variable names
175 name: "dollar with special chars",
176 value: "a$@b$#c", // Special chars aren't valid in variable names
180 name: "empty environment variable substitution",
181 value: "Bearer $EMPTY_VAR",
182 envVars: map[string]string{},
186 name: "unmatched command substitution opening",
187 value: "Bearer $(echo test",
191 name: "unmatched environment variable braces",
192 value: "Bearer ${TOKEN",
196 name: "command substitution with error",
197 value: "Bearer $(false)",
198 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
199 return "", "", errors.New("command failed")
204 name: "complex real-world example",
205 value: "Bearer $(cat /tmp/token.txt | base64 -w 0)",
206 shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
207 if command == "cat /tmp/token.txt | base64 -w 0" {
208 return "c2stYW50LXRlc3Q=\n", "", nil
210 return "", "", errors.New("unexpected command")
212 expected: "Bearer c2stYW50LXRlc3Q=",
215 name: "environment variable with underscores and numbers",
216 value: "Bearer $API_KEY_V2",
217 envVars: map[string]string{"API_KEY_V2": "sk-test-123"},
218 expected: "Bearer sk-test-123",
221 name: "no substitution needed",
222 value: "Bearer sk-ant-static-token",
223 expected: "Bearer sk-ant-static-token",
226 name: "incomplete variable at end",
231 name: "variable with invalid character",
232 value: "Bearer $VAR-NAME", // Hyphen not allowed in variable names
236 name: "multiple invalid variables",
242 for _, tt := range tests {
243 t.Run(tt.name, func(t *testing.T) {
244 testEnv := env.NewFromMap(tt.envVars)
245 resolver := &shellVariableResolver{
246 shell: &mockShell{execFunc: tt.shellFunc},
250 result, err := resolver.ResolveValue(tt.value)
253 require.Error(t, err)
255 require.NoError(t, err)
256 require.Equal(t, tt.expected, result)
262func TestEnvironmentVariableResolver_ResolveValue(t *testing.T) {
266 envVars map[string]string
271 name: "non-variable string returns as-is",
272 value: "plain-string",
273 expected: "plain-string",
276 name: "environment variable resolution",
278 envVars: map[string]string{"HOME": "/home/user"},
279 expected: "/home/user",
282 name: "environment variable with complex value",
284 envVars: map[string]string{"PATH": "/usr/bin:/bin:/usr/local/bin"},
285 expected: "/usr/bin:/bin:/usr/local/bin",
288 name: "missing environment variable returns error",
289 value: "$MISSING_VAR",
290 envVars: map[string]string{},
294 name: "empty environment variable returns error",
296 envVars: map[string]string{"EMPTY_VAR": ""},
301 for _, tt := range tests {
302 t.Run(tt.name, func(t *testing.T) {
303 testEnv := env.NewFromMap(tt.envVars)
304 resolver := NewEnvironmentVariableResolver(testEnv)
306 result, err := resolver.ResolveValue(tt.value)
309 require.Error(t, err)
311 require.NoError(t, err)
312 require.Equal(t, tt.expected, result)
318func TestNewShellVariableResolver(t *testing.T) {
319 testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
320 resolver := NewShellVariableResolver(testEnv)
322 require.NotNil(t, resolver)
323 require.Implements(t, (*VariableResolver)(nil), resolver)
326func TestNewEnvironmentVariableResolver(t *testing.T) {
327 testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
328 resolver := NewEnvironmentVariableResolver(testEnv)
330 require.NotNil(t, resolver)
331 require.Implements(t, (*VariableResolver)(nil), resolver)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list