| package utils |
|
|
| import ( |
| "github.com/libaxuan/cursor2api-go/models" |
| "encoding/json" |
| "net/http/httptest" |
| "strings" |
| "testing" |
|
|
| "github.com/gin-gonic/gin" |
| ) |
|
|
| func TestCursorProtocolParserParsesThinkingAndToolCallsAcrossChunks(t *testing.T) { |
| parser := NewCursorProtocolParser(models.CursorParseConfig{ |
| TriggerSignal: "<<CALL_test>>", |
| ThinkingEnabled: true, |
| }) |
|
|
| var events []models.AssistantEvent |
| events = append(events, parser.Feed("Hello <think")...) |
| events = append(events, parser.Feed("ing>draft</thinking> world ")...) |
| events = append(events, parser.Feed("<<CALL_test>>\n<invoke name=\"lookup\">{\"q\":\"hel")...) |
| events = append(events, parser.Feed("lo\"}</invoke>!")...) |
| events = append(events, parser.Finish()...) |
|
|
| if len(events) != 5 { |
| t.Fatalf("event count = %v, want 5", len(events)) |
| } |
| if events[0].Kind != models.AssistantEventText || events[0].Text != "Hello " { |
| t.Fatalf("event[0] = %#v, want text Hello", events[0]) |
| } |
| if events[1].Kind != models.AssistantEventThinking || events[1].Thinking != "draft" { |
| t.Fatalf("event[1] = %#v, want thinking draft", events[1]) |
| } |
| if events[2].Kind != models.AssistantEventText || events[2].Text != " world " { |
| t.Fatalf("event[2] = %#v, want text world", events[2]) |
| } |
| if events[3].Kind != models.AssistantEventToolCall || events[3].ToolCall == nil { |
| t.Fatalf("event[3] = %#v, want tool call", events[3]) |
| } |
| if events[3].ToolCall.Function.Name != "lookup" { |
| t.Fatalf("tool name = %v, want lookup", events[3].ToolCall.Function.Name) |
| } |
| if events[3].ToolCall.Function.Arguments != `{"q":"hello"}` { |
| t.Fatalf("tool arguments = %v, want compact json", events[3].ToolCall.Function.Arguments) |
| } |
| if events[4].Kind != models.AssistantEventText || events[4].Text != "!" { |
| t.Fatalf("event[4] = %#v, want trailing exclamation text", events[4]) |
| } |
| } |
|
|
| func TestNonStreamChatCompletionReturnsToolCalls(t *testing.T) { |
| gin.SetMode(gin.TestMode) |
| recorder := httptest.NewRecorder() |
| ctx, _ := gin.CreateTestContext(recorder) |
| ctx.Request = httptest.NewRequest("POST", "/v1/chat/completions", nil) |
|
|
| ch := make(chan interface{}, 4) |
| ch <- models.AssistantEvent{Kind: models.AssistantEventText, Text: "Let me check."} |
| ch <- models.AssistantEvent{ |
| Kind: models.AssistantEventToolCall, |
| ToolCall: &models.ToolCall{ |
| ID: "call_1", |
| Type: "function", |
| Function: models.FunctionCall{ |
| Name: "lookup", |
| Arguments: `{"q":"revivalquant"}`, |
| }, |
| }, |
| } |
| ch <- models.Usage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15} |
| close(ch) |
|
|
| NonStreamChatCompletion(ctx, ch, "claude-sonnet-4.6") |
|
|
| var response models.ChatCompletionResponse |
| if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { |
| t.Fatalf("unmarshal response: %v", err) |
| } |
| if response.Choices[0].FinishReason != "tool_calls" { |
| t.Fatalf("finish reason = %v, want tool_calls", response.Choices[0].FinishReason) |
| } |
| if response.Choices[0].Message.ToolCalls[0].Function.Name != "lookup" { |
| t.Fatalf("tool call name = %v, want lookup", response.Choices[0].Message.ToolCalls[0].Function.Name) |
| } |
| if response.Choices[0].Message.Content != "Let me check." { |
| t.Fatalf("message content = %#v, want Let me check.", response.Choices[0].Message.Content) |
| } |
| } |
|
|
| func TestStreamChatCompletionEmitsToolCallChunks(t *testing.T) { |
| gin.SetMode(gin.TestMode) |
| recorder := httptest.NewRecorder() |
| ctx, _ := gin.CreateTestContext(recorder) |
| ctx.Request = httptest.NewRequest("POST", "/v1/chat/completions", nil) |
|
|
| ch := make(chan interface{}, 2) |
| ch <- models.AssistantEvent{ |
| Kind: models.AssistantEventToolCall, |
| ToolCall: &models.ToolCall{ |
| ID: "call_1", |
| Type: "function", |
| Function: models.FunctionCall{ |
| Name: "lookup", |
| Arguments: `{"q":"revivalquant"}`, |
| }, |
| }, |
| } |
| close(ch) |
|
|
| StreamChatCompletion(ctx, ch, "claude-sonnet-4.6") |
|
|
| body := recorder.Body.String() |
| if !strings.Contains(body, `"tool_calls":[{"index":0,"id":"call_1","type":"function"`) { |
| t.Fatalf("stream body missing tool_calls delta: %s", body) |
| } |
| if !strings.Contains(body, `"finish_reason":"tool_calls"`) { |
| t.Fatalf("stream body missing tool_calls finish reason: %s", body) |
| } |
| if !strings.Contains(body, "[DONE]") { |
| t.Fatalf("stream body missing DONE marker: %s", body) |
| } |
| } |
|
|