package clients import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) func TestGeminiGenerationConfigDropsDefaultRatioTokensAndNormalizesKSize(t *testing.T) { config := geminiGenerationConfig(map[string]any{ "aspect_ratio": "auto", "size": "2k", "generationConfig": map[string]any{ "imageConfig": map[string]any{ "aspectRatio": "adaptive", "imageSize": "2k", }, }, }, true) imageConfig := mapFromAny(config["imageConfig"]) if _, ok := imageConfig["aspectRatio"]; ok { t.Fatalf("non-ratio tokens must not be sent to Gemini: %+v", config) } if imageConfig["imageSize"] != "2K" { t.Fatalf("Gemini imageSize should use canonical K format: %+v", config) } } func TestNormalizeOfficialOpenAIImageSizeUsesModelSpecificWireFormat(t *testing.T) { legacy := map[string]any{ "model": "gpt-image-1", "size": "2K", "width": 2048, "height": 1152, "aspect_ratio": "16:9", } normalizeOfficialOpenAIImageSize(legacy, map[string]any{ "size": "2K", "aspect_ratio": "16:9", }) if legacy["size"] != "1536x1024" { t.Fatalf("legacy GPT Image should use an official orientation size: %+v", legacy) } flexible := map[string]any{ "model": "gpt-image-2", "size": "2K", "resolution": "2k", "aspect_ratio": "16:9", } normalizeOfficialOpenAIImageSize(flexible, map[string]any{ "size": "2K", "aspect_ratio": "16:9", }) if flexible["size"] != "2048x1152" { t.Fatalf("GPT Image 2 should receive flexible pixel dimensions: %+v", flexible) } rounded := map[string]any{ "model": "gpt-image-2", "size": "2K", "resolution": "2K", "aspect_ratio": "3:2", } normalizeOfficialOpenAIImageSize(rounded, map[string]any{ "size": "2K", "aspect_ratio": "3:2", }) if rounded["size"] != "2048x1360" { t.Fatalf("GPT Image 2 dimensions should satisfy the official 16-pixel multiple: %+v", rounded) } explicit := map[string]any{ "model": "gpt-image-2", "size": "1232x768", "width": 1232, "height": 768, } normalizeOfficialOpenAIImageSize(explicit, map[string]any{ "size": "1234x777", }) if explicit["size"] != "1234x777" { t.Fatalf("an explicit WxH size must be sent unchanged to OpenAI: %+v", explicit) } explicitLegacy := map[string]any{ "model": "gpt-image-1", "size": "2048x1152", } normalizeOfficialOpenAIImageSize(explicitLegacy, map[string]any{ "size": "2048x1152", }) if explicitLegacy["size"] != "2048x1152" { t.Fatalf("explicit WxH must also win for legacy OpenAI image models: %+v", explicitLegacy) } } func TestOpenAIClientUsesOriginalWxHAndConvertsKResolution(t *testing.T) { received := make([]map[string]any, 0, 3) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode OpenAI image request: %v", err) } received = append(received, body) _ = json.NewEncoder(w).Encode(map[string]any{ "data": []any{map[string]any{"b64_json": "aW1hZ2U="}}, }) })) defer server.Close() candidate := store.RuntimeModelCandidate{ BaseURL: server.URL, Provider: "openai", ProviderModelName: "gpt-image-2", ModelType: "image_generate", Credentials: map[string]any{"apiKey": "openai-key"}, } requests := []Request{ { Kind: "images.generations", ModelType: "image_generate", Body: map[string]any{ "prompt": "explicit dimensions", "size": "1232x768", "width": 1232, "height": 768, }, OriginalBody: map[string]any{ "size": "1234x777", }, Candidate: candidate, }, { Kind: "images.generations", ModelType: "image_generate", Body: map[string]any{ "prompt": "resolution conversion", "size": "2048x1365", "resolution": "2K", "aspect_ratio": "3:2", "width": 2048, "height": 1365, }, OriginalBody: map[string]any{ "size": "2K", "aspect_ratio": "3:2", }, Candidate: candidate, }, { Kind: "images.generations", ModelType: "image_generate", Body: map[string]any{ "prompt": "resolution field conversion", "size": "1024x1024", "resolution": "1K", "aspect_ratio": "1:1", "width": 1024, "height": 1024, }, OriginalBody: map[string]any{ "resolution": "1k", "aspect_ratio": "1:1", }, Candidate: candidate, }, } for _, request := range requests { if _, err := (OpenAIClient{HTTPClient: server.Client()}).Run(context.Background(), request); err != nil { t.Fatalf("run OpenAI image request: %v", err) } } if len(received) != 3 { t.Fatalf("unexpected request count: %d", len(received)) } if received[0]["size"] != "1234x777" { t.Fatalf("explicit WxH should win over preprocessed dimensions: %+v", received[0]) } if received[1]["size"] != "2048x1360" { t.Fatalf("2K with 3:2 should use normalized OpenAI dimensions: %+v", received[1]) } if received[2]["size"] != "1024x1024" { t.Fatalf("resolution=1K should be converted to OpenAI dimensions: %+v", received[2]) } for _, body := range received { for _, key := range []string{"resolution", "aspect_ratio", "width", "height"} { if _, ok := body[key]; ok { t.Fatalf("generic geometry field %q must not reach OpenAI: %+v", key, body) } } } } func TestVolcesImageBodySelectsConfiguredSizeFormat(t *testing.T) { body := volcesImageBody(Request{ Kind: "images.generations", ModelType: "image_generate", Body: map[string]any{ "model": "Seedream-5.0-Pro", "resolution": "2k", "size": "2048x1152", "width": 2048, "height": 1152, "aspect_ratio": "16:9", "platformId": "gateway-internal-platform", }, Candidate: store.RuntimeModelCandidate{ ProviderModelName: "doubao-seedream-5-0-pro-260628", ModelType: "image_generate", Capabilities: map[string]any{ "image_generate": map[string]any{ "size_param_format": "resolution", }, }, }, }) if body["size"] != "2K" { t.Fatalf("resolution-only Volces model should receive canonical K size: %+v", body) } for _, key := range []string{"resolution", "width", "height", "aspect_ratio", "platformId"} { if _, ok := body[key]; ok { t.Fatalf("generic geometry field %q must not reach Volces: %+v", key, body) } } }