on main help sign in
test: use forward slashes in shell commands for Windows compat
4068422Christian Rocha
internal/config/resolve_real_test.go+24−8
@@ -6,6 +6,7 @@ import (
6 "os"
6 "os"
7 "path/filepath"
7 "path/filepath"
8 "slices"
8 "slices"
9+ "strings"
9 "testing"
10 "testing"
10
11
11 "github.com/charmbracelet/crush/internal/env"
12 "github.com/charmbracelet/crush/internal/env"
@@ -43,8 +44,11 @@ func writeTempFile(t *testing.T, content string) string {
43func TestResolvedEnv_RealShell_Success(t *testing.T) {
44func TestResolvedEnv_RealShell_Success(t *testing.T) {
44 t.Parallel()
45 t.Parallel()
45
46
47+ // filepath.ToSlash so Windows temp paths (C:\Users\...) survive
48+ // being injected into a shell command string — the embedded shell
49+ // treats backslashes as escapes, forward slashes work on every OS.
46− withNL := writeTempFile(t, "token-with-nl\n")
50+ withNL := filepath.ToSlash(writeTempFile(t, "token-with-nl\n"))
47− noNL := writeTempFile(t, "token-no-nl")
51+ noNL := filepath.ToSlash(writeTempFile(t, "token-no-nl"))
48
52
49 m := MCPConfig{
53 m := MCPConfig{
50 Env: map[string]string{
54 Env: map[string]string{
@@ -175,14 +179,17 @@ func TestResolvedEnv_RealShell_NounsetRegression(t *testing.T) {
175}
179}
176
180
177// TestResolvedEnv_RealShell_FailureDetail pins that a failing inner
181// TestResolvedEnv_RealShell_FailureDetail pins that a failing inner
178−// command surfaces enough detail (exit code + stderr) to diagnose
182+// command surfaces enough detail (exit code + stderr on POSIX, the
183+// underlying OS error on Windows where coreutils runs in-process) to
179−// without forcing the user to re-run the command by hand. Also
184+// diagnose without forcing the user to re-run the command by hand.
180−// verifies the template is included so they know which Env entry
185+// Also verifies the template is included so they know which Env
181−// blew up.
186+// entry blew up.
182func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
187func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
183 t.Parallel()
188 t.Parallel()
184
189
190+ // Forward slashes so the path survives shell-string injection on
191+ // Windows; see TestResolvedEnv_RealShell_Success for the same note.
185− missing := filepath.Join(t.TempDir(), "definitely-not-here")
192+ missing := filepath.ToSlash(filepath.Join(t.TempDir(), "definitely-not-here"))
186 m := MCPConfig{Env: map[string]string{
193 m := MCPConfig{Env: map[string]string{
187 "FORGEJO_ACCESS_TOKEN": fmt.Sprintf("$(cat %s)", missing),
194 "FORGEJO_ACCESS_TOKEN": fmt.Sprintf("$(cat %s)", missing),
188 }}
195 }}
@@ -192,7 +199,16 @@ func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
192 msg := err.Error()
199 msg := err.Error()
193 require.Contains(t, msg, "FORGEJO_ACCESS_TOKEN", "must identify the failing env var")
200 require.Contains(t, msg, "FORGEJO_ACCESS_TOKEN", "must identify the failing env var")
194 require.Contains(t, msg, missing, "must include the template so users see what failed")
201 require.Contains(t, msg, missing, "must include the template so users see what failed")
202+
203+ // Inner diagnostic detail must survive. POSIX surfaces "exit
204+ // status N" + stderr; Windows' in-process coreutils surfaces the
205+ // Go OS error instead. Accept either shape so the test is
206+ // portable without weakening the intent.
207+ lower := strings.ToLower(msg)
208+ hasDetail := strings.Contains(lower, "exit status") ||
209+ strings.Contains(lower, "no such file") ||
210+ strings.Contains(lower, "cannot find")
195− require.Contains(t, msg, "exit status", "must surface the inner exit code")
211+ require.True(t, hasDetail, "must surface inner error detail: %s", msg)
196}
212}
197
213
198// TestResolvedHeaders_RealShell_FailurePreservesOriginal pins two
214// TestResolvedHeaders_RealShell_FailurePreservesOriginal pins two