177 lines
4.3 KiB
Go
177 lines
4.3 KiB
Go
package copilot
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/chenhg5/cc-connect/core"
|
|
)
|
|
|
|
func TestLSPWriter_WriteMessage(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
w := newLSPWriter(&buf)
|
|
|
|
msg := jsonRPCRequest{JSONRPC: "2.0", ID: 1, Method: "ping"}
|
|
if err := w.writeMessage(msg); err != nil {
|
|
t.Fatalf("writeMessage: %v", err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.HasPrefix(output, "Content-Length: ") {
|
|
t.Fatalf("output missing Content-Length header: %q", output)
|
|
}
|
|
if !strings.Contains(output, "\r\n\r\n") {
|
|
t.Fatalf("output missing header separator: %q", output)
|
|
}
|
|
// Verify JSON body
|
|
parts := strings.SplitN(output, "\r\n\r\n", 2)
|
|
var decoded jsonRPCRequest
|
|
if err := json.Unmarshal([]byte(parts[1]), &decoded); err != nil {
|
|
t.Fatalf("unmarshal body: %v", err)
|
|
}
|
|
if decoded.Method != "ping" {
|
|
t.Fatalf("method = %q, want ping", decoded.Method)
|
|
}
|
|
}
|
|
|
|
func TestLSPReader_ReadMessage(t *testing.T) {
|
|
body := `{"jsonrpc":"2.0","method":"test"}`
|
|
frame := fmt.Sprintf("Content-Length: %d\r\n\r\n%s", len(body), body)
|
|
|
|
r := newLSPReader(strings.NewReader(frame))
|
|
got, err := r.readMessage()
|
|
if err != nil {
|
|
t.Fatalf("readMessage: %v", err)
|
|
}
|
|
if string(got) != body {
|
|
t.Fatalf("body = %q, want %q", got, body)
|
|
}
|
|
}
|
|
|
|
func TestLSPReader_MultipleMessages(t *testing.T) {
|
|
body1 := `{"id":1}`
|
|
body2 := `{"id":2}`
|
|
frame := fmt.Sprintf("Content-Length: %d\r\n\r\n%sContent-Length: %d\r\n\r\n%s",
|
|
len(body1), body1, len(body2), body2)
|
|
|
|
r := newLSPReader(strings.NewReader(frame))
|
|
|
|
got1, err := r.readMessage()
|
|
if err != nil {
|
|
t.Fatalf("readMessage 1: %v", err)
|
|
}
|
|
if string(got1) != body1 {
|
|
t.Fatalf("msg1 = %q, want %q", got1, body1)
|
|
}
|
|
|
|
got2, err := r.readMessage()
|
|
if err != nil {
|
|
t.Fatalf("readMessage 2: %v", err)
|
|
}
|
|
if string(got2) != body2 {
|
|
t.Fatalf("msg2 = %q, want %q", got2, body2)
|
|
}
|
|
}
|
|
|
|
func TestLSPReader_MissingContentLength(t *testing.T) {
|
|
frame := "Some-Header: value\r\n\r\n{}"
|
|
r := newLSPReader(strings.NewReader(frame))
|
|
_, err := r.readMessage()
|
|
if err == nil {
|
|
t.Fatal("expected error for missing Content-Length")
|
|
}
|
|
}
|
|
|
|
func TestLSPReader_TooLargeContentLength(t *testing.T) {
|
|
frame := "Content-Length: 99999999999\r\n\r\n"
|
|
r := newLSPReader(strings.NewReader(frame))
|
|
_, err := r.readMessage()
|
|
if err == nil {
|
|
t.Fatal("expected error for oversized Content-Length")
|
|
}
|
|
}
|
|
|
|
func TestRPCClient_CallAndDispatch(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
c := newRPCClient(&buf)
|
|
|
|
id, ch := c.call("test.method", map[string]string{"key": "val"})
|
|
|
|
// Simulate response
|
|
resp := &jsonRPCResponse{
|
|
JSONRPC: "2.0",
|
|
Result: json.RawMessage(`{"ok":true}`),
|
|
}
|
|
idBytes, _ := json.Marshal(id)
|
|
resp.ID = idBytes
|
|
|
|
if !c.dispatch(resp) {
|
|
t.Fatal("dispatch returned false")
|
|
}
|
|
|
|
got := <-ch
|
|
if got.Error != nil {
|
|
t.Fatalf("unexpected error: %v", got.Error)
|
|
}
|
|
if string(got.Result) != `{"ok":true}` {
|
|
t.Fatalf("result = %s, want {\"ok\":true}", got.Result)
|
|
}
|
|
}
|
|
|
|
func TestRPCClient_CancelAll(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
c := newRPCClient(&buf)
|
|
|
|
_, ch := c.call("foo", nil)
|
|
c.cancelAll(fmt.Errorf("test cancel"))
|
|
|
|
got := <-ch
|
|
if got.Error == nil {
|
|
t.Fatal("expected error after cancelAll")
|
|
}
|
|
}
|
|
|
|
func TestRPCClient_Notify(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
c := newRPCClient(&buf)
|
|
|
|
err := c.notify("permission.respond", map[string]string{"requestId": "123"})
|
|
if err != nil {
|
|
t.Fatalf("notify: %v", err)
|
|
}
|
|
|
|
output := buf.String()
|
|
if !strings.Contains(output, "permission.respond") {
|
|
t.Fatalf("output missing method: %q", output)
|
|
}
|
|
// Notifications should NOT have an id field
|
|
if strings.Contains(output, `"id"`) {
|
|
t.Fatalf("notification should not have id field: %q", output)
|
|
}
|
|
}
|
|
|
|
func TestSummarizeToolInput(t *testing.T) {
|
|
got := summarizeToolInput("shell", map[string]any{"command": "ls -la"})
|
|
if !strings.Contains(got, "ls -la") {
|
|
t.Fatalf("summarizeToolInput = %q, want to contain 'ls -la'", got)
|
|
}
|
|
|
|
// Nil input
|
|
if got := summarizeToolInput("test", nil); got != "" {
|
|
t.Fatalf("summarizeToolInput(nil) = %q, want empty", got)
|
|
}
|
|
|
|
// Long input gets truncated
|
|
longInput := map[string]any{"data": strings.Repeat("x", 300)}
|
|
got = summarizeToolInput("test", longInput)
|
|
if len(got) > 210 {
|
|
t.Fatalf("summarizeToolInput length = %d, should be truncated", len(got))
|
|
}
|
|
}
|
|
|
|
// Verify interface compliance at compile time
|
|
var _ core.ContextUsageReporter = (*copilotSession)(nil)
|