Files
easyai-ai-gateway/apps/api/internal/runner/media_parameter_normalization_test.go
T
wangbo 95776c84f6 fix(media): 规范比例与 OpenAI 图像尺寸参数
统一过滤非 x:x 比例,避免默认值参与候选匹配或透传上游。

支持 xK 分辨率归一化,并按 Gemini、Volces 和 OpenAI 的参数规范生成上游请求;OpenAI 显式 WxH 始终保留用户原始尺寸。

验证:在独立临时 worktree 中执行 apps/api 全量 go test ./... -count=1 通过,真实 OpenAI 请求已到达上游但受本地无效凭据阻断。
2026-07-27 23:50:17 +08:00

174 lines
5.4 KiB
Go

package runner
import (
"testing"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
func TestParamProcessorTreatsNonRatioValuesAsUnspecifiedForImagesAndVideos(t *testing.T) {
for _, modelType := range []string{"image_generate", "video_generate"} {
t.Run(modelType, func(t *testing.T) {
result := preprocessRequestWithLog(mediaKindForTest(modelType), map[string]any{
"aspect_ratio": "auto",
"aspectRatio": "adaptive",
"ratio": "keep_ratio",
}, store.RuntimeModelCandidate{
ModelType: modelType,
Capabilities: map[string]any{
modelType: map[string]any{
"aspect_ratio_allowed": []any{"adaptive"},
},
},
})
for _, key := range []string{"aspect_ratio", "aspectRatio", "ratio"} {
if _, ok := result.Body[key]; ok {
t.Fatalf("%s should be removed for non-ratio input: %+v", key, result.Body)
}
}
if result.Err != nil {
t.Fatalf("non-ratio input should use provider default instead of failing: %v", result.Err)
}
})
}
}
func TestParamProcessorNormalizesImageKSizeAndPreservesProviderResolutionChoice(t *testing.T) {
tests := []struct {
name string
capability map[string]any
wantSize string
}{
{
name: "dimension upstream",
capability: map[string]any{
"output_resolutions": []any{"1K", "2K", "4K"},
"aspect_ratio_allowed": []any{"16:9"},
},
wantSize: "2048x1152",
},
{
name: "resolution upstream",
capability: map[string]any{
"output_resolutions": []any{"1K", "2K"},
"aspect_ratio_allowed": []any{"16:9"},
"size_param_format": "resolution",
},
wantSize: "2K",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := preprocessRequestWithLog("images.generations", map[string]any{
"size": "2k",
"aspect_ratio": "16 : 9",
}, store.RuntimeModelCandidate{
ModelType: "image_generate",
Capabilities: map[string]any{
"image_generate": test.capability,
},
})
if result.Err != nil {
t.Fatalf("preprocess image size: %v", result.Err)
}
if result.Body["resolution"] != "2K" || result.Body["size"] != test.wantSize {
t.Fatalf("unexpected normalized size: %+v", result.Body)
}
if result.Body["width"] != 2048 || result.Body["height"] != 1152 || result.Body["aspect_ratio"] != "16:9" {
t.Fatalf("unexpected normalized dimensions: %+v", result.Body)
}
})
}
}
func TestParamProcessorNormalizesVideo4KSize(t *testing.T) {
result := preprocessRequestWithLog("videos.generations", map[string]any{
"size": "4K",
"aspect_ratio": "auto",
}, store.RuntimeModelCandidate{
ModelType: "video_generate",
Capabilities: map[string]any{
"video_generate": map[string]any{
"output_resolutions": []any{"2160p"},
"aspect_ratio_allowed": []any{"16:9"},
},
},
})
if result.Body["resolution"] != "2160p" {
t.Fatalf("video 4K size should normalize to 2160p: %+v", result.Body)
}
if _, ok := result.Body["aspect_ratio"]; ok {
t.Fatalf("video auto ratio should stay unspecified: %+v", result.Body)
}
}
func TestCandidateFilterIgnoresNonRatioValuesAndFiltersRealRatios(t *testing.T) {
candidates := []store.RuntimeModelCandidate{
candidateWithAspectRatios("square", "image_generate", "1:1"),
candidateWithAspectRatios("landscape", "image_generate", "16:9"),
}
filtered, summary, err := filterRuntimeCandidatesByRequest("images.generations", "demo-image", "image_generate", map[string]any{
"aspect_ratio": "auto",
}, candidates)
if err != nil || len(filtered) != 2 || summary != nil {
t.Fatalf("auto ratio should not participate in candidate matching: filtered=%+v summary=%+v err=%v", filtered, summary, err)
}
filtered, summary, err = filterRuntimeCandidatesByRequest("images.generations", "demo-image", "image_generate", map[string]any{
"aspect_ratio": "16 : 9",
}, candidates)
if err != nil || len(filtered) != 1 || filtered[0].PlatformKey != "landscape" {
t.Fatalf("real ratio should filter candidates: filtered=%+v summary=%+v err=%v", filtered, summary, err)
}
}
func TestCandidateFilterNormalizesKSizeAndAllowsUnconfiguredResolution(t *testing.T) {
candidates := []store.RuntimeModelCandidate{
candidateWithImageResolutions("one-k", "1K"),
{
PlatformID: "platform-unrestricted",
PlatformKey: "unrestricted",
PlatformModelID: "model-unrestricted",
ModelName: "demo-image",
ModelType: "image_generate",
Capabilities: map[string]any{
"image_generate": map[string]any{},
},
},
}
filtered, _, err := filterRuntimeCandidatesByRequest("images.generations", "demo-image", "image_generate", map[string]any{
"size": "2k",
}, candidates)
if err != nil {
t.Fatalf("2k size should be accepted and normalized for matching: %v", err)
}
if len(filtered) != 1 || filtered[0].PlatformKey != "unrestricted" {
t.Fatalf("unexpected candidates for normalized 2K size: %+v", filtered)
}
}
func candidateWithAspectRatios(platformKey string, modelType string, ratios ...string) store.RuntimeModelCandidate {
return store.RuntimeModelCandidate{
PlatformID: "platform-" + platformKey,
PlatformKey: platformKey,
PlatformModelID: "model-" + platformKey,
ModelName: "demo-media",
ModelType: modelType,
Capabilities: map[string]any{
modelType: map[string]any{
"aspect_ratio_allowed": stringsToAny(ratios),
},
},
}
}
func mediaKindForTest(modelType string) string {
if modelType == "image_generate" {
return "images.generations"
}
return "videos.generations"
}