| 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()) |
| } |
| } |
|
|