FILE / ScuroNeko/go-deepseek

methods_test.go

Исходный файл и его история в репозитории.
FILE 92ceb8bec3f3c9cbed878886d1257ab462d71145
Files
go-deepseek/methods_test.go
T
2026-07-31 14:21:48 +03:00

195 lines
5.8 KiB
Go

package deepseek
import (
"context"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"reflect"
"strings"
"testing"
)
func TestCompletionReqBuilder(t *testing.T) {
req := NewCompletionReq("chat-id").
SetParentMessageID(42).
SetModelType(ModelTypeExpert).
AddPrompt("hello").
AddPrompt(" world").
BuildPrompt().
SetThinkingEnabled(true).
SetSearchEnabled(true)
req.RefFileIDs = []string{"file-id"}
if req.ChatSessionID != "chat-id" || req.ModelType != ModelTypeExpert || req.Prompt != "hello world" {
t.Fatalf("request = %#v", req)
}
if req.ParentMessageID == nil || *req.ParentMessageID != 42 {
t.Fatalf("parent message ID = %v", req.ParentMessageID)
}
if !req.ThinkingEnabled || !req.SearchEnabled || !reflect.DeepEqual(req.RefFileIDs, []string{"file-id"}) {
t.Fatalf("request options = %#v", req)
}
req.AddPrompt("new").BuildPrompt()
if req.Prompt != "new" {
t.Fatalf("rebuilt prompt = %q, want new", req.Prompt)
}
}
func TestCreateChatWithContext(t *testing.T) {
client := testAPI(func(r *http.Request) (*http.Response, error) {
if r.URL.Path != "/chat_session/create" {
t.Errorf("path = %q", r.URL.Path)
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Errorf("read body: %v", err)
}
if string(body) != `{}` {
t.Errorf("body = %s, want {}", body)
}
return testHTTPResponse(`{"code":0,"data":{"biz_code":0,"biz_data":{"chat_session":{"id":"chat-id","seq_id":7,"model_type":"default"},"ttl_seconds":60}}}`), nil
})
got, err := client.CreateChatWithContext(context.Background())
if err != nil {
t.Fatalf("CreateChatWithContext() error = %v", err)
}
if got.ID != "chat-id" || got.SeqID != 7 || got.ModelType != "default" {
t.Fatalf("session = %#v", got)
}
}
func TestCompletionWithContextPreservesRequestOptions(t *testing.T) {
const challenge = "2f90572ad390d758b5e55b3bb74f14722166388023b3b28876d056a358591197"
const salt = "2eeb8f3a703002bfca70"
const expectedAnswer = uint64(61830)
client := testAPI(func(r *http.Request) (*http.Response, error) {
switch r.URL.Path {
case "/chat/create_pow_challenge":
return testHTTPResponse(`{"code":0,"data":{"biz_code":0,"biz_data":{"challenge":{"algorithm":"DeepSeekHashV1","challenge":"` + challenge + `","salt":"` + salt + `","signature":"signature","difficulty":144000,"expire_at":1785483643587,"target_path":"/api/v0/chat/completion"}}}}`), nil
case "/chat/completion":
encoded := r.Header.Get("x-ds-pow-response")
raw, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
t.Errorf("decode PoW header: %v", err)
}
var proof struct {
Answer uint64 `json:"answer"`
Signature string `json:"signature"`
TargetPath string `json:"target_path"`
}
if err := json.Unmarshal(raw, &proof); err != nil {
t.Errorf("decode PoW JSON: %v", err)
}
if proof.Answer != expectedAnswer || proof.Signature != "signature" || proof.TargetPath != "/api/v0/chat/completion" {
t.Errorf("proof = %#v", proof)
}
var body CompletionReq
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode completion: %v", err)
}
if body.ChatSessionID != "chat-id" || body.ParentMessageID == nil || *body.ParentMessageID != 9 || body.ModelType != ModelTypeExpert || body.Prompt != "prompt" || !body.ThinkingEnabled || !body.SearchEnabled || !reflect.DeepEqual(body.RefFileIDs, []string{"file-id"}) {
t.Errorf("completion body = %#v", body)
}
resp := testHTTPResponse("event: close\ndata: {}\n\n")
resp.Header.Set("Content-Type", "text/event-stream")
return resp, nil
default:
t.Fatalf("unexpected path %q", r.URL.Path)
return nil, nil
}
})
parentID := uint64(9)
body := CompletionReq{
ChatSessionID: "chat-id",
ParentMessageID: &parentID,
ModelType: ModelTypeExpert,
Prompt: "prompt",
RefFileIDs: []string{"file-id"},
ThinkingEnabled: true,
SearchEnabled: true,
}
resp, err := client.CompletionWithContext(context.Background(), body)
if err != nil {
t.Fatalf("CompletionWithContext() error = %v", err)
}
defer resp.Body.Close()
}
type trackingReadCloser struct {
io.Reader
closed bool
}
func (r *trackingReadCloser) Close() error {
r.closed = true
return nil
}
func TestReadStream(t *testing.T) {
stream := strings.Join([]string{
`event: ready`,
`data: {"request_message_id":1,"response_message_id":2,"model_type":"default"}`,
``,
`data: {"p":"response/content","o":"APPEND","v":"hel"}`,
``,
`data: {"v":"lo"}`,
``,
`event: close`,
`data: {}`,
``,
}, "\n")
body := &trackingReadCloser{Reader: strings.NewReader(stream)}
resp := &http.Response{Body: body}
var chunks []string
state, err := NewClient().ReadStream(context.Background(), resp, func(s string) {
chunks = append(chunks, s)
})
if err != nil {
t.Fatalf("ReadStream() error = %v", err)
}
if !body.closed {
t.Fatal("response body was not closed")
}
if got := state.Content.String(); got != "hello" {
t.Fatalf("content = %q, want hello", got)
}
if !reflect.DeepEqual(chunks, []string{"hel", "lo"}) {
t.Fatalf("chunks = %#v", chunks)
}
if state.RequestMessageID != 1 || state.ResponseMessageID != 2 || !state.Closed {
t.Fatalf("state = %#v", state)
}
}
func TestReadStreamAsStringReturnsFinalSetValue(t *testing.T) {
stream := strings.Join([]string{
`data: {"p":"response/content","o":"APPEND","v":"obsolete value"}`,
``,
`data: {"p":"response/content","o":"SET","v":"final"}`,
``,
`event: close`,
`data: {}`,
``,
}, "\n")
body := &trackingReadCloser{Reader: strings.NewReader(stream)}
got, err := NewClient().ReadStreamAsString(context.Background(), &http.Response{Body: body})
if err != nil {
t.Fatalf("ReadStreamAsString() error = %v", err)
}
if got != "final" {
t.Fatalf("ReadStreamAsString() = %q, want final", got)
}
if !body.closed {
t.Fatal("response body was not closed")
}
}