fix(media): 统一图片结果 URL 化并限制同步 Base64
将上游 URL 直接持久化,内联媒体经对象存储后仅保留 URL 与内部定位元数据;异步轮询、任务详情和幂等重放统一使用零对象读取的 URL 投影,并增加 64KiB 响应门禁。 OpenAI 图片接口接受 url 与 b64_json,同步 Base64 限制为 20MiB 和每 Pod 2 并发;新增历史结果迁移清零门禁、结果指标和 API GOMEMLIMIT。 验证:API go test ./...、go vet、聚焦 race、pnpm openapi、pnpm lint/test/build、迁移安全检查与 docker compose config 均通过。
This commit is contained in:
@@ -2,6 +2,7 @@ package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
@@ -14,6 +15,55 @@ import (
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func TestSynchronousInlineResponseLimitAndCapacity(t *testing.T) {
|
||||
request := map[string]any{"response_format": "b64_json"}
|
||||
if !synchronousInlineResponseRequested("images.generations", request) {
|
||||
t.Fatal("explicit b64_json image response was not detected")
|
||||
}
|
||||
encoded := strings.Repeat("A", base64.StdEncoding.EncodedLen(int(maxSynchronousInlineResponseBytes+1)))
|
||||
if got := inlineMediaDecodedSize(map[string]any{"data": []any{map[string]any{"b64_json": encoded}}}); got <= maxSynchronousInlineResponseBytes {
|
||||
t.Fatalf("decoded size=%d", got)
|
||||
}
|
||||
if got := base64DecodedSize(base64.StdEncoding.EncodeToString([]byte{1})); got != 1 {
|
||||
t.Fatalf("padded Base64 decoded size=%d", got)
|
||||
}
|
||||
|
||||
releaseFirst, err := acquireSynchronousInlineResponseSlot(t.Context(), "images.generations", request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer releaseFirst()
|
||||
releaseSecond, err := acquireSynchronousInlineResponseSlot(t.Context(), "images.generations", request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer releaseSecond()
|
||||
|
||||
cancelled, cancel := context.WithCancel(t.Context())
|
||||
cancel()
|
||||
_, err = acquireSynchronousInlineResponseSlot(cancelled, "images.generations", request)
|
||||
if clients.ErrorCode(err) != "response_format_capacity_timeout" {
|
||||
t.Fatalf("unexpected capacity error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMediaResponseFormat(t *testing.T) {
|
||||
for _, value := range []string{"url", "b64_json", " B64_JSON "} {
|
||||
body := map[string]any{"response_format": value}
|
||||
if err := validateMediaResponseFormat("images.generations", body); err != nil {
|
||||
t.Fatalf("validate %q: %v", value, err)
|
||||
}
|
||||
}
|
||||
for _, value := range []any{"base64", 1, map[string]any{"type": "url"}} {
|
||||
if err := validateMediaResponseFormat("images.generations", map[string]any{"response_format": value}); err == nil {
|
||||
t.Fatalf("accepted invalid response_format: %#v", value)
|
||||
}
|
||||
}
|
||||
if err := validateMediaResponseFormat("chat.completions", map[string]any{"response_format": map[string]any{"type": "json_object"}}); err != nil {
|
||||
t.Fatalf("image validation affected chat response_format: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolAPIKeyStoreFailureIs503InsteadOf401(t *testing.T) {
|
||||
authenticator := auth.New("test-secret", "", "")
|
||||
authenticator.LocalAPIKeyVerifier = func(context.Context, string) (*auth.User, error) {
|
||||
|
||||
Reference in New Issue
Block a user