Restore MiniMax media routing parity across image and video flows

The server now keeps portable image ratio normalization, enforces documented Seedream size presets, routes MiniMax img2img through generations-style payloads, and submits Hailuo video variants through the video generations API with proper reference-image handling.

Constraint: The deployed test chain relies on popiartServer as the lowest-risk place to adapt provider-specific routing without broad newapi changes
Rejected: Expand newapi relay modes for every MiniMax image/video branch | larger blast radius than needed for the verified server-managed flows
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep MiniMax-specific media routing in popiartServer unless upstream newapi adapters are upgraded to the same contract
Tested: go test ./internal/server -run TestInferRouteKeyForModelRecognizesViduAsVideo|TestGenerate|TestResolve|TestSubmitMiniMaxVideoTask|TestExecuteImageToImageJobUsesGenerationsPathForMiniMax|TestExecuteImageToVideoJob|TestResolveVideoReferencesSupportsImagesArray
Not-tested: Full PopiNewAPI unit suite (blocked earlier by external module checksum drift in local checkout)
This commit is contained in:
wtgoku
2026-04-15 11:57:05 +08:00
parent a73b85c45f
commit 6ff9ad6997
7 changed files with 982 additions and 67 deletions
+104
View File
@@ -138,6 +138,110 @@ func TestSubmitImageToVideoTaskUsesURLImagesForViduModels(t *testing.T) {
}
}
func TestSubmitMiniMaxVideoTaskUsesVideoGenerationsEndpoint(t *testing.T) {
var gotPath string
var gotBody map[string]any
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
t.Fatalf("decode request body: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"task_id":"task_minimax_video_123","status":"submitted"}`))
}))
defer srv.Close()
client := newNewAPIClient(Config{NewAPIBaseURL: srv.URL})
taskID, err := client.submitMiniMaxVideoTask(context.Background(), "sk-test", "I2V-01", map[string]any{
"prompt": "animate this portrait gently",
"size": "720x1280",
}, []imageEditReference{{
URL: "https://example.com/reference.png",
}})
if err != nil {
t.Fatalf("submitMiniMaxVideoTask: %v", err)
}
if taskID != "task_minimax_video_123" {
t.Fatalf("unexpected task id: %q", taskID)
}
if gotPath != "/v1/video/generations" {
t.Fatalf("expected /v1/video/generations, got %q", gotPath)
}
if gotBody["model"] != "I2V-01" {
t.Fatalf("unexpected model: %#v", gotBody["model"])
}
images, ok := gotBody["images"].([]any)
if !ok || len(images) != 1 || images[0] != "https://example.com/reference.png" {
t.Fatalf("unexpected images payload: %#v", gotBody["images"])
}
if gotBody["size"] != "720P" {
t.Fatalf("expected 720P resolution, got %#v", gotBody["size"])
}
}
func TestSubmitMiniMaxVideoTaskKeepsS2VImagesArray(t *testing.T) {
var gotBody map[string]any
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
t.Fatalf("decode request body: %v", err)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"task_id":"task_s2v_123","status":"submitted"}`))
}))
defer srv.Close()
client := newNewAPIClient(Config{NewAPIBaseURL: srv.URL})
taskID, err := client.submitMiniMaxVideoTask(context.Background(), "sk-test", "S2V-01", map[string]any{
"prompt": "animate the subject gently",
}, []imageEditReference{{
URL: "https://example.com/reference.png",
}})
if err != nil {
t.Fatalf("submitMiniMaxVideoTask: %v", err)
}
if taskID != "task_s2v_123" {
t.Fatalf("unexpected task id: %q", taskID)
}
images, ok := gotBody["images"].([]any)
if !ok || len(images) != 1 || images[0] != "https://example.com/reference.png" {
t.Fatalf("unexpected images payload for S2V: %#v", gotBody["images"])
}
}
func TestResolveVideoReferencesSupportsImagesArray(t *testing.T) {
cfg := Config{
SQLitePath: filepath.Join(t.TempDir(), "popiart.db"),
SkillhubDir: makeEmptySkillhub(t),
SessionSecret: "test-secret",
}
server, err := NewWithConfig(cfg)
if err != nil {
t.Fatalf("NewWithConfig: %v", err)
}
imageBytes := tinyPNG(t)
refSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write(imageBytes)
}))
defer refSrv.Close()
refs, err := server.resolveVideoReferences(context.Background(), &job{
UserID: "user_test",
UpstreamKey: "sk-upstream",
}, map[string]any{
"images": []any{refSrv.URL + "/a.png", refSrv.URL + "/b.png"},
})
if err != nil {
t.Fatalf("resolveVideoReferences: %v", err)
}
if len(refs) != 2 {
t.Fatalf("expected 2 refs, got %#v", refs)
}
}
func TestFetchVideoTaskFallsBackToGenericTaskEnvelope(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {