test: use forward slashes in shell commands for Windows compat

Christian Rocha created

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.

Change summary

internal/config/resolve_real_test.go | 32 ++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 8 deletions(-)

Detailed changes

internal/config/resolve_real_test.go 🔗

@@ -6,6 +6,7 @@ import (
 	"os"
 	"path/filepath"
 	"slices"
+	"strings"
 	"testing"
 
 	"github.com/charmbracelet/crush/internal/env"
@@ -43,8 +44,11 @@ func writeTempFile(t *testing.T, content string) string {
 func TestResolvedEnv_RealShell_Success(t *testing.T) {
 	t.Parallel()
 
-	withNL := writeTempFile(t, "token-with-nl\n")
-	noNL := writeTempFile(t, "token-no-nl")
+	// filepath.ToSlash so Windows temp paths (C:\Users\...) survive
+	// being injected into a shell command string — the embedded shell
+	// treats backslashes as escapes, forward slashes work on every OS.
+	withNL := filepath.ToSlash(writeTempFile(t, "token-with-nl\n"))
+	noNL := filepath.ToSlash(writeTempFile(t, "token-no-nl"))
 
 	m := MCPConfig{
 		Env: map[string]string{
@@ -175,14 +179,17 @@ func TestResolvedEnv_RealShell_NounsetRegression(t *testing.T) {
 }
 
 // TestResolvedEnv_RealShell_FailureDetail pins that a failing inner
-// command surfaces enough detail (exit code + stderr) to diagnose
-// without forcing the user to re-run the command by hand. Also
-// verifies the template is included so they know which Env entry
-// blew up.
+// command surfaces enough detail (exit code + stderr on POSIX, the
+// underlying OS error on Windows where coreutils runs in-process) to
+// diagnose without forcing the user to re-run the command by hand.
+// Also verifies the template is included so they know which Env
+// entry blew up.
 func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
 	t.Parallel()
 
-	missing := filepath.Join(t.TempDir(), "definitely-not-here")
+	// Forward slashes so the path survives shell-string injection on
+	// Windows; see TestResolvedEnv_RealShell_Success for the same note.
+	missing := filepath.ToSlash(filepath.Join(t.TempDir(), "definitely-not-here"))
 	m := MCPConfig{Env: map[string]string{
 		"FORGEJO_ACCESS_TOKEN": fmt.Sprintf("$(cat %s)", missing),
 	}}
@@ -192,7 +199,16 @@ func TestResolvedEnv_RealShell_FailureDetail(t *testing.T) {
 	msg := err.Error()
 	require.Contains(t, msg, "FORGEJO_ACCESS_TOKEN", "must identify the failing env var")
 	require.Contains(t, msg, missing, "must include the template so users see what failed")
-	require.Contains(t, msg, "exit status", "must surface the inner exit code")
+
+	// Inner diagnostic detail must survive. POSIX surfaces "exit
+	// status N" + stderr; Windows' in-process coreutils surfaces the
+	// Go OS error instead. Accept either shape so the test is
+	// portable without weakening the intent.
+	lower := strings.ToLower(msg)
+	hasDetail := strings.Contains(lower, "exit status") ||
+		strings.Contains(lower, "no such file") ||
+		strings.Contains(lower, "cannot find")
+	require.True(t, hasDetail, "must surface inner error detail: %s", msg)
 }
 
 // TestResolvedHeaders_RealShell_FailurePreservesOriginal pins two