mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
270 lines
8.6 KiB
Go
270 lines
8.6 KiB
Go
package openai
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"ds2api/internal/auth"
|
|
dsclient "ds2api/internal/deepseek/client"
|
|
)
|
|
|
|
type managedFilesAuthStub struct{}
|
|
|
|
func (managedFilesAuthStub) Determine(_ *http.Request) (*auth.RequestAuth, error) {
|
|
return &auth.RequestAuth{
|
|
UseConfigToken: true,
|
|
DeepSeekToken: "managed-token",
|
|
CallerID: "caller:test",
|
|
AccountID: "acct-123",
|
|
TriedAccounts: map[string]bool{},
|
|
}, nil
|
|
}
|
|
|
|
func (managedFilesAuthStub) DetermineCaller(_ *http.Request) (*auth.RequestAuth, error) {
|
|
return &auth.RequestAuth{
|
|
UseConfigToken: true,
|
|
DeepSeekToken: "managed-token",
|
|
CallerID: "caller:test",
|
|
AccountID: "acct-123",
|
|
TriedAccounts: map[string]bool{},
|
|
}, nil
|
|
}
|
|
|
|
func (managedFilesAuthStub) Release(_ *auth.RequestAuth) {}
|
|
|
|
type filesRouteDSStub struct {
|
|
lastReq dsclient.UploadFileRequest
|
|
upload *dsclient.UploadFileResult
|
|
fetched *dsclient.UploadFileResult
|
|
err error
|
|
}
|
|
|
|
func (m *filesRouteDSStub) CreateSession(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (m *filesRouteDSStub) GetPow(_ context.Context, _ *auth.RequestAuth, _ int) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (m *filesRouteDSStub) UploadFile(_ context.Context, _ *auth.RequestAuth, req dsclient.UploadFileRequest, _ int) (*dsclient.UploadFileResult, error) {
|
|
m.lastReq = req
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
if m.upload != nil {
|
|
return m.upload, nil
|
|
}
|
|
return &dsclient.UploadFileResult{ID: "file-123", Filename: req.Filename, Bytes: int64(len(req.Data)), Purpose: req.Purpose, Status: "uploaded"}, nil
|
|
}
|
|
|
|
func (m *filesRouteDSStub) FetchUploadedFile(_ context.Context, _ *auth.RequestAuth, fileID string) (*dsclient.UploadFileResult, error) {
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
if m.fetched != nil {
|
|
return m.fetched, nil
|
|
}
|
|
return &dsclient.UploadFileResult{ID: fileID, Filename: "notes.txt", Bytes: 11, Purpose: "assistants", Status: "processed"}, nil
|
|
}
|
|
|
|
func (m *filesRouteDSStub) CallCompletion(_ context.Context, _ *auth.RequestAuth, _ map[string]any, _ string, _ int) (*http.Response, error) {
|
|
return nil, errors.New("not implemented")
|
|
}
|
|
|
|
func (m *filesRouteDSStub) DeleteSessionForToken(_ context.Context, _ string, _ string) (*dsclient.DeleteSessionResult, error) {
|
|
return &dsclient.DeleteSessionResult{Success: true}, nil
|
|
}
|
|
|
|
func (m *filesRouteDSStub) DeleteAllSessionsForToken(_ context.Context, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func newMultipartUploadRequest(t *testing.T, purpose string, filename string, data []byte, model string) *http.Request {
|
|
t.Helper()
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
if purpose != "" {
|
|
if err := writer.WriteField("purpose", purpose); err != nil {
|
|
t.Fatalf("write purpose failed: %v", err)
|
|
}
|
|
}
|
|
if model != "" {
|
|
if err := writer.WriteField("model", model); err != nil {
|
|
t.Fatalf("write model failed: %v", err)
|
|
}
|
|
}
|
|
part, err := writer.CreateFormFile("file", filename)
|
|
if err != nil {
|
|
t.Fatalf("create form file failed: %v", err)
|
|
}
|
|
if _, err := part.Write(data); err != nil {
|
|
t.Fatalf("write file failed: %v", err)
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/files", &body)
|
|
req.Header.Set("Authorization", "Bearer direct-token")
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
return req
|
|
}
|
|
|
|
func TestFilesRouteUploadSuccess(t *testing.T) {
|
|
ds := &filesRouteDSStub{}
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: streamStatusAuthStub{}, DS: ds}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
req := newMultipartUploadRequest(t, "assistants", "notes.txt", []byte("hello world"), "deepseek-v4-vision")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
if ds.lastReq.Filename != "notes.txt" {
|
|
t.Fatalf("expected filename notes.txt, got %q", ds.lastReq.Filename)
|
|
}
|
|
if ds.lastReq.Purpose != "assistants" {
|
|
t.Fatalf("expected purpose assistants, got %q", ds.lastReq.Purpose)
|
|
}
|
|
if ds.lastReq.ModelType != "vision" {
|
|
t.Fatalf("expected vision model type, got %q", ds.lastReq.ModelType)
|
|
}
|
|
if string(ds.lastReq.Data) != "hello world" {
|
|
t.Fatalf("unexpected uploaded data: %q", string(ds.lastReq.Data))
|
|
}
|
|
var out map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
|
t.Fatalf("decode response failed: %v body=%s", err, rec.Body.String())
|
|
}
|
|
if out["object"] != "file" {
|
|
t.Fatalf("expected file object, got %#v", out)
|
|
}
|
|
if out["id"] != "file-123" {
|
|
t.Fatalf("expected file id file-123, got %#v", out["id"])
|
|
}
|
|
if out["filename"] != "notes.txt" {
|
|
t.Fatalf("expected filename notes.txt, got %#v", out["filename"])
|
|
}
|
|
}
|
|
|
|
func TestFilesRouteUploadIncludesAccountIDForManagedAccount(t *testing.T) {
|
|
ds := &filesRouteDSStub{}
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: managedFilesAuthStub{}, DS: ds}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
req := newMultipartUploadRequest(t, "assistants", "notes.txt", []byte("hello world"), "deepseek-v4-vision")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var out map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
|
t.Fatalf("decode response failed: %v body=%s", err, rec.Body.String())
|
|
}
|
|
if out["account_id"] != "acct-123" {
|
|
t.Fatalf("expected account_id acct-123, got %#v", out["account_id"])
|
|
}
|
|
}
|
|
|
|
func TestFilesRouteRetrieveSuccess(t *testing.T) {
|
|
ds := &filesRouteDSStub{fetched: &dsclient.UploadFileResult{
|
|
ID: "file-123",
|
|
Filename: "notes.txt",
|
|
Bytes: 11,
|
|
Purpose: "assistants",
|
|
Status: "processed",
|
|
}}
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: managedFilesAuthStub{}, DS: ds}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/files/file-123", nil)
|
|
req.Header.Set("Authorization", "Bearer direct-token")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
var out map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
|
t.Fatalf("decode response failed: %v body=%s", err, rec.Body.String())
|
|
}
|
|
if out["id"] != "file-123" || out["filename"] != "notes.txt" || out["status"] != "processed" {
|
|
t.Fatalf("unexpected file object: %#v", out)
|
|
}
|
|
if out["account_id"] != "acct-123" {
|
|
t.Fatalf("expected account_id acct-123, got %#v", out["account_id"])
|
|
}
|
|
}
|
|
|
|
func TestFilesRouteRetrieveNotFound(t *testing.T) {
|
|
ds := &filesRouteDSStub{err: dsclient.ErrUploadFileNotFound}
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: streamStatusAuthStub{}, DS: ds}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/files/missing-file", nil)
|
|
req.Header.Set("Authorization", "Bearer direct-token")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestFilesRouteRejectsNonMultipart(t *testing.T) {
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: streamStatusAuthStub{}, DS: &filesRouteDSStub{}}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/files", bytes.NewBufferString(`{"purpose":"assistants"}`))
|
|
req.Header.Set("Authorization", "Bearer direct-token")
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestFilesRouteRequiresFileField(t *testing.T) {
|
|
h := &openAITestSurface{Store: mockOpenAIConfig{}, Auth: streamStatusAuthStub{}, DS: &filesRouteDSStub{}}
|
|
r := chi.NewRouter()
|
|
registerOpenAITestRoutes(r, h)
|
|
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
if err := writer.WriteField("purpose", "assistants"); err != nil {
|
|
t.Fatalf("write field failed: %v", err)
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatalf("close writer failed: %v", err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/files", &body)
|
|
req.Header.Set("Authorization", "Bearer direct-token")
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
rec := httptest.NewRecorder()
|
|
r.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400, got %d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|