Files
multica/server/pkg/agent/cursor_invocation_windows_test.go
Kagura f02bc56e70 fix(agent/cursor): remove obsolete 'chat' subcommand from argv (#3077) (#3092)
The current cursor-agent CLI no longer has a 'chat' subcommand. The
positional 'chat' argument was silently treated as prompt text, leaking
into the user message (e.g. 'chat <actual prompt>').

Remove 'chat' from buildCursorArgs so the generated argv matches the
current cursor-agent CLI interface.

Fixes #3077
2026-05-27 16:40:29 +08:00

125 lines
4.4 KiB
Go

//go:build windows
package agent
import (
"io"
"log/slog"
"os"
"path/filepath"
"reflect"
"testing"
)
// stubPowerShell installs a deterministic PowerShell lookup for the duration
// of a test and restores the original on cleanup.
func stubPowerShell(t *testing.T, path string, ok bool) {
t.Helper()
prev := powerShellLookup
powerShellLookup = func() (string, bool) { return path, ok }
t.Cleanup(func() { powerShellLookup = prev })
}
func writeFile(t *testing.T, path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatalf("write %s: %v", path, err)
}
}
// TestPlatformCursorInvocation_RewritesCmdLauncherToPowerShellFile is the core
// Windows test: when LookPath resolves cursor-agent to the official .cmd
// launcher and a sibling cursor-agent.ps1 exists, we should invoke
// PowerShell with -File <ps1> and forward every original arg unchanged
// (including a multi-line -p prompt that would otherwise be mangled by the
// cmd.exe %* re-expansion in the .cmd launcher).
func TestPlatformCursorInvocation_RewritesCmdLauncherToPowerShellFile(t *testing.T) {
dir := t.TempDir()
cmdPath := filepath.Join(dir, "cursor-agent.cmd")
ps1Path := filepath.Join(dir, "cursor-agent.ps1")
writeFile(t, cmdPath, "@echo off\r\npowershell -NoProfile -ExecutionPolicy Bypass -File \"%~dp0cursor-agent.ps1\" %*\r\n")
writeFile(t, ps1Path, "# fake cursor-agent.ps1\r\n")
fakePS := filepath.Join(dir, "powershell.exe")
writeFile(t, fakePS, "")
stubPowerShell(t, fakePS, true)
args := []string{
"-p", "line1\nline2\nline3",
"--output-format", "stream-json",
"--yolo",
"--workspace", `C:\some\workspace`,
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
gotExec, gotArgs, ok := platformCursorInvocation(cmdPath, args, logger)
if !ok {
t.Fatalf("expected platform rewrite to be applied, got ok=false")
}
if gotExec != fakePS {
t.Errorf("argv0: got %q want %q", gotExec, fakePS)
}
wantArgs := append([]string{
"-NoProfile",
"-ExecutionPolicy", "Bypass",
"-File", ps1Path,
}, args...)
if !reflect.DeepEqual(gotArgs, wantArgs) {
t.Errorf("argv mismatch:\n got %#v\n want %#v", gotArgs, wantArgs)
}
}
// TestPlatformCursorInvocation_SkipsWhenNotCmdOrBat ensures we leave argv
// alone when the user explicitly resolved cursor-agent to something that
// isn't a batch launcher (e.g. a real binary or a node script).
func TestPlatformCursorInvocation_SkipsWhenNotCmdOrBat(t *testing.T) {
dir := t.TempDir()
exePath := filepath.Join(dir, "cursor-agent.exe")
writeFile(t, exePath, "")
// A sibling .ps1 must not trick us into rewriting a non-launcher exec.
writeFile(t, filepath.Join(dir, "cursor-agent.ps1"), "")
stubPowerShell(t, filepath.Join(dir, "powershell.exe"), true)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
if _, _, ok := platformCursorInvocation(exePath, []string{"-p", "hello"}, logger); ok {
t.Fatalf("expected ok=false for non-.cmd/.bat launcher")
}
}
// TestPlatformCursorInvocation_SkipsWhenPS1Missing covers the rare case where
// a .cmd was found but its companion .ps1 is missing (e.g. a partial install).
// We must fall back to the original launcher rather than synthesising an
// invalid powershell -File invocation.
func TestPlatformCursorInvocation_SkipsWhenPS1Missing(t *testing.T) {
dir := t.TempDir()
cmdPath := filepath.Join(dir, "cursor-agent.cmd")
writeFile(t, cmdPath, "@echo off\r\n")
stubPowerShell(t, filepath.Join(dir, "powershell.exe"), true)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
if _, _, ok := platformCursorInvocation(cmdPath, []string{"-p", "hello"}, logger); ok {
t.Fatalf("expected ok=false when cursor-agent.ps1 is missing")
}
}
// TestPlatformCursorInvocation_SkipsWhenPowerShellMissing covers a stripped
// down environment in which neither pwsh.exe nor powershell.exe can be
// resolved. We must not fabricate an empty-string argv[0].
func TestPlatformCursorInvocation_SkipsWhenPowerShellMissing(t *testing.T) {
dir := t.TempDir()
cmdPath := filepath.Join(dir, "cursor-agent.cmd")
ps1Path := filepath.Join(dir, "cursor-agent.ps1")
writeFile(t, cmdPath, "@echo off\r\n")
writeFile(t, ps1Path, "# fake\r\n")
stubPowerShell(t, "", false)
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
if _, _, ok := platformCursorInvocation(cmdPath, []string{"-p", "hello"}, logger); ok {
t.Fatalf("expected ok=false when no powershell host is available")
}
}