Files
dingtalk-workspace-cli/internal/helpers/connect_gemini_api_coverage_test.go

159 lines
6.8 KiB
Go

package helpers
import (
"context"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func geminiResponse(status int, body string, headers map[string]string) *http.Response {
header := make(http.Header)
for key, value := range headers {
header.Set(key, value)
}
return &http.Response{StatusCode: status, Header: header, Body: io.NopCloser(strings.NewReader(body))}
}
func TestCrossPlatformCoverageGeminiAttachmentAndUploadFailureEdges(t *testing.T) {
f := &geminiAPIForwarder{model: "model", apiKey: "key", baseURL: "https://example.test/v1beta", timeout: time.Second, httpClient: http.DefaultClient}
parts, err := f.partsWithAttachments(context.Background(), "text", []connectMediaAttachment{{LocalPath: ""}})
if err != nil || len(parts) != 1 {
t.Fatalf("parts(empty path) = %#v, %v", parts, err)
}
if _, err := f.partsWithAttachments(context.Background(), "text", []connectMediaAttachment{{LocalPath: filepath.Join(t.TempDir(), "missing")}}); err == nil {
t.Fatal("parts(missing path) error = nil")
}
if _, err := f.uploadFile(context.Background(), filepath.Join(t.TempDir(), "missing"), "text/plain", ""); err == nil {
t.Fatal("uploadFile(missing) error = nil")
}
path := filepath.Join(t.TempDir(), "file.txt")
if err := os.WriteFile(path, []byte("data"), 0o600); err != nil {
t.Fatal(err)
}
invalid := *f
invalid.baseURL = "%"
if _, err := invalid.uploadFile(context.Background(), path, "text/plain", ""); err == nil {
t.Fatal("uploadFile(invalid endpoint) error = nil")
}
tests := []struct {
name string
transport roundTripFunc
want string
}{
{"start transport", func(*http.Request) (*http.Response, error) { return nil, errors.New("start") }, "start"},
{"start status", func(*http.Request) (*http.Response, error) { return geminiResponse(500, "bad", nil), nil }, "启动上传"},
{"missing upload URL", func(*http.Request) (*http.Response, error) { return geminiResponse(200, "{}", nil), nil }, "X-Goog-Upload-URL"},
{"invalid upload URL", func(*http.Request) (*http.Response, error) {
return geminiResponse(200, "{}", map[string]string{"X-Goog-Upload-URL": ":"}), nil
}, "missing protocol scheme"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
candidate := *f
candidate.httpClient = &http.Client{Transport: test.transport}
if _, err := candidate.uploadFile(context.Background(), path, "text/plain", ""); err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("uploadFile() error = %v, want %q", err, test.want)
}
})
}
sequenceTest := func(t *testing.T, second *http.Response, secondErr error, want string) {
t.Helper()
calls := 0
candidate := *f
candidate.httpClient = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
calls++
if calls == 1 {
return geminiResponse(200, "{}", map[string]string{"X-Goog-Upload-URL": "https://upload.test/session"}), nil
}
return second, secondErr
})}
_, err := candidate.uploadFile(context.Background(), path, "text/plain", "")
if want == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), want) {
t.Fatalf("uploadFile() error = %v, want %q", err, want)
}
}
t.Run("upload transport", func(t *testing.T) { sequenceTest(t, nil, errors.New("upload"), "upload") })
t.Run("upload status", func(t *testing.T) { sequenceTest(t, geminiResponse(500, "bad", nil), nil, "上传 HTTP") })
t.Run("invalid result", func(t *testing.T) { sequenceTest(t, geminiResponse(200, "bad", nil), nil, "invalid") })
t.Run("missing uri", func(t *testing.T) {
sequenceTest(t, geminiResponse(200, `{"file":{"state":"ACTIVE"}}`, nil), nil, "file.uri")
})
t.Run("mime fallback", func(t *testing.T) {
sequenceTest(t, geminiResponse(200, `{"file":{"uri":"uri","state":"ACTIVE"}}`, nil), nil, "")
})
removable := filepath.Join(t.TempDir(), "remove-after-start")
if err := os.WriteFile(removable, []byte("data"), 0o600); err != nil {
t.Fatal(err)
}
removeCandidate := *f
removeCandidate.httpClient = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
_ = os.Remove(removable)
return geminiResponse(200, "{}", map[string]string{"X-Goog-Upload-URL": "https://upload.test/session"}), nil
})}
if _, err := removeCandidate.uploadFile(context.Background(), removable, "text/plain", ""); err == nil {
t.Fatal("uploadFile(open after removal) error = nil")
}
}
func TestCrossPlatformCoverageGeminiUploadedFilePollingEdges(t *testing.T) {
originalInterval := geminiFilePollInterval
t.Cleanup(func() { geminiFilePollInterval = originalInterval })
f := &geminiAPIForwarder{apiKey: "key", baseURL: "https://example.test/v1beta", httpClient: http.DefaultClient}
ctx, cancel := context.WithCancel(context.Background())
cancel()
geminiFilePollInterval = time.Hour
if _, err := f.waitForUploadedFile(ctx, geminiUploadedFile{State: "PROCESSING"}); !errors.Is(err, context.Canceled) {
t.Fatalf("waitForUploadedFile(cancelled) = %v", err)
}
geminiFilePollInterval = 0
tests := []struct {
name string
baseURL string
response *http.Response
err error
wantError string
}{
{"bad endpoint", "%", nil, nil, "BASE_URL"},
{"transport", "https://example.test/v1beta", nil, errors.New("poll"), "poll"},
{"status", "https://example.test/v1beta", geminiResponse(500, "bad", nil), nil, "查询 HTTP"},
{"invalid json", "https://example.test/v1beta", geminiResponse(200, "bad", nil), nil, "invalid"},
{"failed", "https://example.test/v1beta", geminiResponse(200, `{"name":"files/1","state":"FAILED"}`, nil), nil, "处理附件失败"},
{"active", "https://example.test/v1beta", geminiResponse(200, `{"name":"files/1","state":"ACTIVE","uri":"uri"}`, nil), nil, ""},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
candidate := *f
candidate.baseURL = test.baseURL
candidate.httpClient = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { return test.response, test.err })}
_, err := candidate.waitForUploadedFile(context.Background(), geminiUploadedFile{Name: "/files/1", State: "PROCESSING"})
if test.wantError == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), test.wantError) {
t.Fatalf("waitForUploadedFile() error = %v, want %q", err, test.wantError)
}
})
}
if endpoint, err := (&geminiAPIForwarder{baseURL: "https://example.test/custom"}).filesEndpoint(true); err != nil || !strings.Contains(endpoint, "/custom/upload/v1beta/files") {
t.Fatalf("filesEndpoint(custom upload) = %q, %v", endpoint, err)
}
if endpoint, err := (&geminiAPIForwarder{baseURL: "https://example.test/v1beta/files"}).filesEndpoint(false); err != nil || strings.Count(endpoint, "/files") != 1 {
t.Fatalf("filesEndpoint(files) = %q, %v", endpoint, err)
}
}