package deepseek import ( "context" "encoding/base64" "encoding/hex" "encoding/json" "io" "net/http" "strings" "testing" "ds2api/internal/auth" powpkg "ds2api/pow" ) func TestBuildUploadMultipartBodyIncludesPurposeAndFilePart(t *testing.T) { body, contentType, err := buildUploadMultipartBody(`../demo.txt`, "text/plain", "assistants", []byte("hello")) if err != nil { t.Fatalf("buildUploadMultipartBody error: %v", err) } if !strings.HasPrefix(contentType, "multipart/form-data; boundary=") { t.Fatalf("unexpected content type: %q", contentType) } payload := string(body) if !strings.Contains(payload, `name="purpose"`) || !strings.Contains(payload, "assistants") { t.Fatalf("expected purpose field in payload: %q", payload) } if !strings.Contains(payload, `name="file"; filename="demo.txt"`) { t.Fatalf("expected sanitized filename in payload: %q", payload) } if !strings.Contains(payload, "Content-Type: text/plain") { t.Fatalf("expected file content type in payload: %q", payload) } if !strings.Contains(payload, "hello") { t.Fatalf("expected file content in payload: %q", payload) } } func TestExtractUploadFileResultSupportsNestedShapes(t *testing.T) { got := extractUploadFileResult(map[string]any{ "data": map[string]any{ "biz_data": map[string]any{ "file": map[string]any{ "file_id": "file_123", "file_name": "report.pdf", "file_size": 99, "status": "processed", "purpose": "assistants", }, }, }, }) if got.ID != "file_123" { t.Fatalf("expected id file_123, got %#v", got) } if got.Filename != "report.pdf" { t.Fatalf("expected filename report.pdf, got %#v", got) } if got.Bytes != 99 { t.Fatalf("expected bytes 99, got %#v", got) } if got.Status != "processed" { t.Fatalf("expected status processed, got %#v", got) } if got.Purpose != "assistants" { t.Fatalf("expected purpose assistants, got %#v", got) } } func TestUploadFileUsesUploadTargetPowAndMultipartHeaders(t *testing.T) { challengeHash := powpkg.DeepSeekHashV1([]byte(powpkg.BuildPrefix("salt", 1712345678) + "42")) powResponse := `{"code":0,"msg":"ok","data":{"biz_code":0,"biz_data":{"challenge":{"algorithm":"DeepSeekHashV1","challenge":"` + hex.EncodeToString(challengeHash[:]) + `","salt":"salt","expire_at":1712345678,"difficulty":1000,"signature":"sig","target_path":"` + DeepSeekUploadTargetPath + `"}}}}` uploadResponse := `{"code":0,"msg":"ok","data":{"biz_code":0,"biz_data":{"file":{"file_id":"file_789","filename":"demo.txt","bytes":5,"status":"uploaded","purpose":"assistants"}}}}` var seenPow string var seenTargetPath string var seenContentType string var seenFileSize string var seenBody string call := 0 client := &Client{ regular: doerFunc(func(req *http.Request) (*http.Response, error) { call++ bodyBytes, _ := io.ReadAll(req.Body) switch call { case 1: seenTargetPath = string(bodyBytes) return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(powResponse)), Request: req}, nil case 2: seenPow = req.Header.Get("x-ds-pow-response") seenContentType = req.Header.Get("Content-Type") seenFileSize = req.Header.Get("x-file-size") seenBody = string(bodyBytes) return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(uploadResponse)), Request: req}, nil default: t.Fatalf("unexpected request count %d", call) return nil, nil } }), fallback: &http.Client{Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) { return nil, nil })}, maxRetries: 1, } result, err := client.UploadFile(context.Background(), &auth.RequestAuth{DeepSeekToken: "token", TriedAccounts: map[string]bool{}}, UploadFileRequest{ Filename: "demo.txt", ContentType: "text/plain", Purpose: "assistants", Data: []byte("hello"), }, 1) if err != nil { t.Fatalf("UploadFile error: %v", err) } if result.ID != "file_789" { t.Fatalf("expected uploaded file id file_789, got %#v", result) } if !strings.Contains(seenTargetPath, `"target_path":"`+DeepSeekUploadTargetPath+`"`) { t.Fatalf("expected upload target_path in pow request, got %q", seenTargetPath) } if strings.TrimSpace(seenPow) == "" { t.Fatal("expected x-ds-pow-response header") } rawPow, err := base64.StdEncoding.DecodeString(seenPow) if err != nil { t.Fatalf("decode pow header failed: %v", err) } var powHeader map[string]any if err := json.Unmarshal(rawPow, &powHeader); err != nil { t.Fatalf("unmarshal pow header failed: %v", err) } if powHeader["target_path"] != DeepSeekUploadTargetPath { t.Fatalf("expected pow target_path %q, got %#v", DeepSeekUploadTargetPath, powHeader["target_path"]) } if seenFileSize != "5" { t.Fatalf("expected x-file-size=5, got %q", seenFileSize) } if !strings.HasPrefix(seenContentType, "multipart/form-data; boundary=") { t.Fatalf("expected multipart content type, got %q", seenContentType) } if !strings.Contains(seenBody, `name="file"; filename="demo.txt"`) { t.Fatalf("expected file part in upload body: %q", seenBody) } }