thread-master/apps/backend/internal/module/ai/openai_compatible_test.go

101 lines
3.6 KiB
Go
Raw Normal View History

2026-07-13 01:15:30 +00:00
package ai
import (
2026-07-20 06:33:14 +00:00
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
2026-07-13 01:15:30 +00:00
"testing"
"github.com/stretchr/testify/require"
)
func TestExtractChatContent_String(t *testing.T) {
raw := []byte(`{"model":"grok-3","choices":[{"finish_reason":"stop","message":{"content":"你好世界"}}]}`)
text, meta, err := extractChatContent(raw)
require.NoError(t, err)
require.Equal(t, "你好世界", text)
require.Equal(t, "stop", meta.FinishReason)
}
2026-07-20 06:33:14 +00:00
func TestCompleteContinuesLengthResponseWithoutOverlap(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
w.Header().Set("Content-Type", "application/json")
if calls == 1 {
_, _ = fmt.Fprint(w, `{"model":"grok-3","choices":[{"finish_reason":"length","message":{"content":"第一段,還沒"}}]}`)
return
}
_, _ = fmt.Fprint(w, `{"model":"grok-3","choices":[{"finish_reason":"stop","message":{"content":"還沒講完。第二段。"}}]}`)
}))
defer server.Close()
client := NewOpenAICompatible(ProviderXAI, server.URL)
text, err := client.Complete(context.Background(), "key", "grok-3", "寫完整")
require.NoError(t, err)
require.Equal(t, "第一段,還沒講完。第二段。", text)
require.Equal(t, 2, calls)
}
func TestExtractStreamEventFinishReason(t *testing.T) {
chunk, finish, ok := extractStreamEvent(`{"choices":[{"finish_reason":"length","delta":{}}]}`)
require.True(t, ok)
require.Empty(t, chunk)
require.Equal(t, "length", finish)
}
func TestCompleteStreamContinuesLengthResponse(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
if calls == 1 {
w.Header().Set("Content-Type", "text/event-stream")
_, _ = fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"第一段,還沒\"}}]}\n\n")
_, _ = fmt.Fprint(w, "data: {\"choices\":[{\"finish_reason\":\"length\",\"delta\":{}}]}\n\n")
_, _ = fmt.Fprint(w, "data: [DONE]\n\n")
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = fmt.Fprint(w, `{"model":"grok-3","choices":[{"finish_reason":"stop","message":{"content":"還沒講完。第二段。"}}]}`)
}))
defer server.Close()
client := NewOpenAICompatible(ProviderXAI, server.URL)
var streamed strings.Builder
text, err := client.CompleteStream(context.Background(), "key", "grok-3", "寫完整", func(chunk string) error {
streamed.WriteString(chunk)
return nil
})
require.NoError(t, err)
require.Equal(t, "第一段,還沒講完。第二段。", text)
require.Equal(t, text, streamed.String())
require.Equal(t, 2, calls)
}
2026-07-13 01:15:30 +00:00
func TestExtractChatContent_ArrayParts(t *testing.T) {
raw := []byte(`{"choices":[{"message":{"content":[{"type":"text","text":"第一段"},{"type":"text","text":"第二段"}]}}]}`)
text, _, err := extractChatContent(raw)
require.NoError(t, err)
require.Contains(t, text, "第一段")
require.Contains(t, text, "第二段")
}
func TestExtractChatContent_Empty(t *testing.T) {
raw := []byte(`{"choices":[{"finish_reason":"length","message":{"content":""}}]}`)
text, meta, err := extractChatContent(raw)
require.NoError(t, err)
require.Equal(t, "", text)
require.Equal(t, "length", meta.FinishReason)
}
func TestExtractChatContent_IgnoresReasoningOnly(t *testing.T) {
// deepseek / kimi 思考模型常只回 reasoning_content不可當聊天回覆
raw := []byte(`{"choices":[{"finish_reason":"length","message":{"content":null,"reasoning_content":"我們需要根據最近的對話來回應…"}}]}`)
text, meta, err := extractChatContent(raw)
require.NoError(t, err)
require.Equal(t, "", text)
require.Equal(t, "length", meta.FinishReason)
}