206 lines
6.6 KiB
Go
206 lines
6.6 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeWindsurfRequestCanonicalizesToolsChoiceAndHistory(t *testing.T) {
|
|
req := windsurfMessagesRequest{
|
|
Tools: []windsurfRequestTool{
|
|
{
|
|
Name: "list_files",
|
|
Description: "List files",
|
|
InputSchema: json.RawMessage(`{"type":"object"}`),
|
|
},
|
|
{
|
|
Name: "glob",
|
|
Description: "Duplicate canonical alias",
|
|
InputSchema: json.RawMessage(`{"type":"object","properties":{"path":{"type":"string"}}}`),
|
|
},
|
|
{
|
|
Name: "applyPatch",
|
|
Description: "Patch files",
|
|
InputSchema: json.RawMessage(`{"type":"object"}`),
|
|
},
|
|
},
|
|
ToolChoice: map[string]any{
|
|
"type": "tool",
|
|
"name": "searchFiles",
|
|
},
|
|
Messages: []windsurfRequestMessage{
|
|
{
|
|
Role: "assistant",
|
|
Content: json.RawMessage(`[
|
|
{"type":"tool_use","id":"call-1","name":"read_file","input":{"filePath":"a.go"}},
|
|
{"type":"text","text":"done"}
|
|
]`),
|
|
},
|
|
},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
if len(req.Tools) != 2 {
|
|
t.Fatalf("normalized tools len = %d, want 2", len(req.Tools))
|
|
}
|
|
if req.Tools[0].Name != "glob" {
|
|
t.Fatalf("first tool name = %q, want glob", req.Tools[0].Name)
|
|
}
|
|
if req.Tools[1].Name != "edit" {
|
|
t.Fatalf("second tool name = %q, want edit", req.Tools[1].Name)
|
|
}
|
|
|
|
toolChoice, ok := req.ToolChoice.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("normalized tool choice type = %T, want map[string]any", req.ToolChoice)
|
|
}
|
|
if toolChoice["name"] != "grep" {
|
|
t.Fatalf("tool choice name = %v, want grep", toolChoice["name"])
|
|
}
|
|
|
|
var blocks []windsurfContentBlock
|
|
if err := json.Unmarshal(req.Messages[0].Content, &blocks); err != nil {
|
|
t.Fatalf("unmarshal normalized message content: %v", err)
|
|
}
|
|
if len(blocks) == 0 || blocks[0].Name != "read" {
|
|
t.Fatalf("tool_use name = %q, want read", blocks[0].Name)
|
|
}
|
|
}
|
|
|
|
func TestWindsurfExtractContentTextFromRawPreservesStructuredToolResult(t *testing.T) {
|
|
raw := json.RawMessage(`[
|
|
{"type":"text","text":"summary"},
|
|
{"type":"json","value":{"entries":["main.go"]}}
|
|
]`)
|
|
|
|
got := windsurfExtractContentTextFromRaw(raw)
|
|
if !strings.Contains(got, `"type":"json"`) {
|
|
t.Fatalf("structured tool_result content should be preserved, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeWindsurfRequestInjectsWorkspaceContext(t *testing.T) {
|
|
req := windsurfMessagesRequest{
|
|
CWD: "/Users/alice/projects/billing",
|
|
Messages: []windsurfRequestMessage{{Role: "user", Content: json.RawMessage(`"analyze billing"`)}},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
var system []windsurfContentBlock
|
|
if err := json.Unmarshal(req.System, &system); err != nil {
|
|
t.Fatalf("unmarshal system: %v", err)
|
|
}
|
|
if len(system) == 0 || system[0].Type != "text" {
|
|
t.Fatalf("expected injected text system block, got %#v", system)
|
|
}
|
|
if !strings.Contains(system[0].Text, "Working directory: /Users/alice/projects/billing") {
|
|
t.Fatalf("system context should include normalized cwd, got %q", system[0].Text)
|
|
}
|
|
if !strings.Contains(system[0].Text, "Relative file paths") {
|
|
t.Fatalf("system context should explain relative path semantics, got %q", system[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeWindsurfRequestPreservesExistingSystemAfterWorkspaceContext(t *testing.T) {
|
|
req := windsurfMessagesRequest{
|
|
Metadata: map[string]any{
|
|
"workspace": map[string]any{"path": "/home/bob/src/project"},
|
|
},
|
|
System: json.RawMessage(`"existing instructions"`),
|
|
Messages: []windsurfRequestMessage{{Role: "user", Content: json.RawMessage(`"hello"`)}},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
var system []windsurfContentBlock
|
|
if err := json.Unmarshal(req.System, &system); err != nil {
|
|
t.Fatalf("unmarshal system: %v", err)
|
|
}
|
|
if len(system) != 2 {
|
|
t.Fatalf("system len = %d, want 2", len(system))
|
|
}
|
|
if !strings.Contains(system[0].Text, "Workspace: /home/bob/src/project") {
|
|
t.Fatalf("first system block should contain workspace context, got %q", system[0].Text)
|
|
}
|
|
if system[1].Text != "existing instructions" {
|
|
t.Fatalf("existing system should be preserved after context block, got %q", system[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeWindsurfRequestLiftsCWDFromEnvSystem(t *testing.T) {
|
|
req := windsurfMessagesRequest{
|
|
System: json.RawMessage(`"You are Claude Code.\n<env>\nWorking directory: /Users/alice/IdeaProjects/flux-panel\nIs directory a git repo: Yes\n</env>"`),
|
|
Messages: []windsurfRequestMessage{
|
|
{Role: "user", Content: json.RawMessage(`"check branches"`)},
|
|
},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
var system []windsurfContentBlock
|
|
if err := json.Unmarshal(req.System, &system); err != nil {
|
|
t.Fatalf("unmarshal system: %v", err)
|
|
}
|
|
if !strings.Contains(system[0].Text, "Working directory: /Users/alice/IdeaProjects/flux-panel") {
|
|
t.Fatalf("expected cwd lifted from system env, got %q", system[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeWindsurfRequestLiftsCWDFromSystemReminderBarePath(t *testing.T) {
|
|
reminder := "<system-reminder>" + strings.Repeat("x", 1000) + "</system-reminder>\n\n"
|
|
content, err := json.Marshal(reminder + `C:\Users\renfei\Downloads\WindsurfAPI-master 分析下这个项目`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req := windsurfMessagesRequest{
|
|
Messages: []windsurfRequestMessage{
|
|
{Role: "user", Content: content},
|
|
},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
var system []windsurfContentBlock
|
|
if err := json.Unmarshal(req.System, &system); err != nil {
|
|
t.Fatalf("unmarshal system: %v", err)
|
|
}
|
|
if !strings.Contains(system[0].Text, `Working directory: C:\Users\renfei\Downloads\WindsurfAPI-master`) {
|
|
t.Fatalf("expected cwd lifted after system reminder, got %q", system[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeWindsurfRequestDoesNotLiftSingleFilePathAsCWD(t *testing.T) {
|
|
req := windsurfMessagesRequest{
|
|
Messages: []windsurfRequestMessage{
|
|
{Role: "user", Content: json.RawMessage(`"C:\\Users\\me\\notes.md 解释这个文件"`)},
|
|
},
|
|
}
|
|
|
|
normalizeWindsurfRequest(&req)
|
|
|
|
if len(req.System) != 0 {
|
|
t.Fatalf("single file path should not be lifted as cwd, got %s", string(req.System))
|
|
}
|
|
}
|
|
|
|
func TestWindsurfExtractContentTextStripsSlashCommandSpec(t *testing.T) {
|
|
raw := json.RawMessage(`[
|
|
{"type":"text","text":"<command-name>/ccg:plan</command-name>\n<command-message>Ask the user for a feature name.</command-message>\n<command-args>分析一下这个项目 我感觉 计费逻辑出问题了</command-args>\n真正的问题"}
|
|
]`)
|
|
|
|
got := windsurfExtractContentText(raw)
|
|
|
|
if strings.Contains(got, "Ask the user for a feature name") {
|
|
t.Fatalf("command-message spec should be stripped, got %q", got)
|
|
}
|
|
if strings.Contains(got, "<command-args>") {
|
|
t.Fatalf("command-args tag should be stripped, got %q", got)
|
|
}
|
|
if !strings.Contains(got, "真正的问题") {
|
|
t.Fatalf("normal user content should remain, got %q", got)
|
|
}
|
|
}
|