86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package windsurf
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// Simulates a Windsurf LS that rejects SendUserCascadeMessage with
|
|
// "panel state not found". Verifies that allowRecreate=false bubbles the
|
|
// error up (so chatCascade can rebuild full-history text) while
|
|
// allowRecreate=true still triggers the internal recreate path.
|
|
func TestSendUserCascadeMessage_AllowRecreateGatesSilentRetry(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
allowRecreate bool
|
|
wantErr bool
|
|
// When allowRecreate=true the client tries ForceWarmup + StartCascade.
|
|
// When allowRecreate=false we expect no such attempts.
|
|
wantStartCascadeCalled bool
|
|
}{
|
|
{"disabled bubbles error", false, true, false},
|
|
{"enabled attempts recreate", true, true, true}, // test LS keeps rejecting so overall errors, but StartCascade must be attempted
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var mu sync.Mutex
|
|
var paths []string
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mu.Lock()
|
|
paths = append(paths, r.URL.Path)
|
|
mu.Unlock()
|
|
// Every RPC returns panel-not-found so we can detect whether
|
|
// StartCascade was attempted after the initial failure.
|
|
w.Header().Set("Content-Type", "application/grpc")
|
|
w.Header().Set("grpc-status", "5")
|
|
w.Header().Set("grpc-message", "panel state not found for session abc")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewLocalLSClient(42099, "csrf")
|
|
client.BaseURL = server.URL
|
|
client.HTTP = server.Client()
|
|
// Pre-mark as warmed so the first SendUserCascadeMessage doesn't trigger
|
|
// warmup on its own path — keeps the test focused.
|
|
client.Warmed = true
|
|
|
|
_, err := client.SendUserCascadeMessage(
|
|
context.Background(),
|
|
"token",
|
|
"existing-cascade-id",
|
|
"hello",
|
|
"claude-sonnet-4",
|
|
"",
|
|
0,
|
|
nil,
|
|
tt.allowRecreate,
|
|
)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
t.Fatalf("err = %v, wantErr = %v", err, tt.wantErr)
|
|
}
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
startCascadeCalled := false
|
|
for _, p := range paths {
|
|
if strings.HasSuffix(p, "/StartCascade") {
|
|
startCascadeCalled = true
|
|
break
|
|
}
|
|
}
|
|
if startCascadeCalled != tt.wantStartCascadeCalled {
|
|
t.Fatalf("StartCascade called = %v, want %v (paths: %v)",
|
|
startCascadeCalled, tt.wantStartCascadeCalled, paths)
|
|
}
|
|
})
|
|
}
|
|
}
|