on main help sign in
test: use forward slashes in shell commands for Windows compat

Windows t.TempDir() returns C:\... paths whose backslashes get consumed as escapes when injected into $(cat ...). Convert to forward slashes so the embedded shell resolves the path on every OS. Also accept the Windows coreutils error shape ("cannot find") in addition to POSIX "exit status" when asserting that inner diagnostic detail is surfaced — mvdan/sh runs coreutils in-process on Windows so there is no subprocess exit status to report.

commit40684228138303a922ff71a8f39dfe85fad30572 authorChristian Rocha <christian@rocha.is> dateSat 2 May 2026, 15:37 (6 months ago) parentd745ff5
1 file changed+24−8
internal/config/resolve_real_test.go+24−8
internal/config/resolve_real_test.go+24−8
@@ -6,6 +6,7 @@ import (
66 "os"
77 "path/filepath"
88 "slices"
9+ "strings"
910 "testing"
1011
1112 "github.com/charmbracelet/crush/internal/env"
@@ -43,8 +44,11 @@ func writeTempFile(t *testing.T, content string) string {
4344func TestResolvedEnv_RealShell_Success(t *testing.T) {
4445 t.Parallel()
4546
46− withNL := writeTempFile(t, "token-with-nl\n")
47− noNL := writeTempFile(t, "token-no-nl")
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.
50+ withNL := filepath.ToSlash(writeTempFile(t, "token-with-nl\n"))
51+ noNL := filepath.ToSlash(writeTempFile(t, "token-no-nl"))
4852
4953 m := MCPConfig{
5054 Env: map[string]string{
@@ -175,14 +179,17 @@ func TestResolvedEnv_RealShell_NounsetRegression(t *testing.T) {
175179}
176180
177181// TestResolvedEnv_RealShell_FailureDetail pins that a failing inner
178−// command surfaces enough detail (exit code + stderr) to diagnose
179−// without forcing the user to re-run the command by hand. Also
180−// verifies the template is included so they know which Env entry
181−// blew up.
182+// command surfaces enough detail (exit code + stderr on POSIX, the
183+// underlying OS error on Windows where coreutils runs in-process) to
184+// diagnose without forcing the user to re-run the command by hand.
185+// Also verifies the template is included so they know which Env
186+// entry blew up.
182187func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
183188 t.Parallel()
184189
185− missing := filepath.Join(t.TempDir(), "definitely-not-here")
190+ // Forward slashes so the path survives shell-string injection on
191+ // Windows; see TestResolvedEnv_RealShell_Success for the same note.
192+ missing := filepath.ToSlash(filepath.Join(t.TempDir(), "definitely-not-here"))
186193 m := MCPConfig{Env: map[string]string{
187194 "FORGEJO_ACCESS_TOKEN": fmt.Sprintf("$(cat %s)", missing),
188195 }}
@@ -192,7 +199,16 @@ func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
192199 msg := err.Error()
193200 require.Contains(t, msg, "FORGEJO_ACCESS_TOKEN", "must identify the failing env var")
194201 require.Contains(t, msg, missing, "must include the template so users see what failed")
195− require.Contains(t, msg, "exit status", "must surface the inner exit code")
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")
211+ require.True(t, hasDetail, "must surface inner error detail: %s", msg)
196212}
197213
198214// TestResolvedHeaders_RealShell_FailurePreservesOriginal pins two