Files
2026-06-02 23:14:41 +08:00

178 lines
4.7 KiB
Go

package kimi
import (
"context"
"testing"
"time"
"github.com/chenhg5/cc-connect/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewKimiSession(t *testing.T) {
ctx := context.Background()
ks, err := newKimiSession(ctx, "kimi", "/tmp", "kimi-k2", "default", "resume-123", nil, 0)
require.NoError(t, err)
require.NotNil(t, ks)
assert.True(t, ks.Alive())
assert.Equal(t, "resume-123", ks.CurrentSessionID())
err = ks.Close()
assert.NoError(t, err)
assert.False(t, ks.Alive())
}
func TestExtractResumeSessionID(t *testing.T) {
cases := []struct {
input string
expected string
}{
{"To resume this session: kimi -r e3690555-60eb-4d50-874b-e3647e9cee5b", "e3690555-60eb-4d50-874b-e3647e9cee5b"},
{"To resume this session: kimi --resume abc-def", ""},
{"To resume this session: no-id-here", ""},
{"random text", ""},
}
for _, c := range cases {
assert.Equal(t, c.expected, extractResumeSessionID(c.input), "input: %s", c.input)
}
}
func TestHandleAssistantWithText(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.handleEvent(map[string]any{
"role": "assistant",
"content": []any{
map[string]any{"type": "text", "text": "Hello!"},
},
})
// pendingMsgs should buffer the text
assert.Len(t, ks.pendingMsgs, 1)
assert.Equal(t, "Hello!", ks.pendingMsgs[0])
}
func TestHandleAssistantWithThink(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.handleEvent(map[string]any{
"role": "assistant",
"content": []any{
map[string]any{"type": "think", "think": "Let me think..."},
map[string]any{"type": "text", "text": "Done!"},
},
})
events := drainEvents(ks.events, 2)
require.Len(t, events, 1)
assert.Equal(t, core.EventThinking, events[0].Type)
assert.Equal(t, "Let me think...", events[0].Content)
assert.Equal(t, "Done!", ks.pendingMsgs[0])
}
func TestHandleAssistantWithToolCalls(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.handleEvent(map[string]any{
"role": "assistant",
"content": []any{
map[string]any{"type": "text", "text": "I will run a command"},
},
"tool_calls": []any{
map[string]any{
"id": "tool_abc",
"function": map[string]any{
"name": "Shell",
"arguments": `{"command":"echo hello"}`,
},
},
},
})
events := drainEvents(ks.events, 3)
require.Len(t, events, 2)
assert.Equal(t, core.EventThinking, events[0].Type)
assert.Equal(t, "I will run a command", events[0].Content)
assert.Equal(t, core.EventToolUse, events[1].Type)
assert.Equal(t, "Shell", events[1].ToolName)
assert.Equal(t, `{"command":"echo hello"}`, events[1].ToolInput)
assert.Equal(t, "tool_abc", events[1].RequestID)
}
func TestHandleTool(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.handleEvent(map[string]any{
"role": "tool",
"tool_call_id": "tool_abc",
"content": []any{
map[string]any{"type": "text", "text": "hello\n"},
},
})
events := drainEvents(ks.events, 1)
require.Len(t, events, 1)
assert.Equal(t, core.EventToolResult, events[0].Type)
assert.Equal(t, "tool_abc", events[0].ToolName)
assert.Contains(t, events[0].ToolResult, "hello")
}
func TestFlushPendingAsText(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.pendingMsgs = []string{"Hello", " ", "world"}
ks.flushPendingAsText()
events := drainEvents(ks.events, 1)
require.Len(t, events, 1)
assert.Equal(t, core.EventText, events[0].Type)
assert.Equal(t, "Hello world", events[0].Content)
assert.Empty(t, ks.pendingMsgs)
}
func TestFlushPendingAsThinking(t *testing.T) {
ctx := context.Background()
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
defer ks.Close()
ks.pendingMsgs = []string{"Thinking..."}
ks.flushPendingAsThinking()
events := drainEvents(ks.events, 1)
require.Len(t, events, 1)
assert.Equal(t, core.EventThinking, events[0].Type)
assert.Equal(t, "Thinking...", events[0].Content)
}
func TestTruncate(t *testing.T) {
assert.Equal(t, "hello", truncate("hello", 10))
assert.Equal(t, "hello world", truncate("hello world", 11))
assert.Equal(t, "hello worl...", truncate("hello world", 10))
}
func drainEvents(ch <-chan core.Event, max int) []core.Event {
var events []core.Event
timeout := time.After(500 * time.Millisecond)
for i := 0; i < max; i++ {
select {
case evt := <-ch:
events = append(events, evt)
case <-timeout:
return events
}
}
return events
}