package httpapi import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func TestKlingV1O1CompatibilityBody(t *testing.T) { body, externalID, err := klingCompatTaskBody("v1", klingO1Model, map[string]any{ "prompt": "一只纸鹤飞过湖面", "duration": json.Number("10"), "aspect_ratio": "16:9", "sound": "on", "external_task_id": "client-o1-1", "watermark_info": map[string]any{"enabled": true}, }) if err != nil { t.Fatalf("build O1 compatibility body: %v", err) } if externalID != "client-o1-1" || body["model"] != klingO1Model || body["modelType"] != "omni_video" { t.Fatalf("unexpected identity fields: %#v", body) } if body["mode"] != "pro" || body["resolution"] != "1080p" || body["audio"] != true { t.Fatalf("unexpected V1 defaults: %#v", body) } content := mapListFromRequest(body["content"]) if len(content) != 1 || content[0]["type"] != "text" || content[0]["text"] != "一只纸鹤飞过湖面" { t.Fatalf("unexpected canonical content: %#v", content) } } func TestKlingV1V3OmniCompatibilityBody(t *testing.T) { body, _, err := klingCompatTaskBody("v1", klingV3OmniModel, map[string]any{ "multi_shot": true, "shot_type": "customize", "mode": "4k", "multi_prompt": []any{ map[string]any{"index": json.Number("1"), "prompt": "推近人物", "duration": json.Number("7")}, map[string]any{"index": json.Number("2"), "prompt": "切到城市远景", "duration": json.Number("8")}, }, "image_list": []any{ map[string]any{"image_url": "https://example.com/first.png", "type": "first_frame"}, }, }) if err != nil { t.Fatalf("build 3.0 Omni compatibility body: %v", err) } if body["resolution"] != "2160p" { t.Fatalf("4k mode was not normalized: %#v", body) } content := mapListFromRequest(body["content"]) if len(content) != 1 || content[0]["role"] != "first_frame" { t.Fatalf("image input was not normalized: %#v", content) } } func TestKlingV2CompatibilityBody(t *testing.T) { body, externalID, err := klingCompatTaskBody("v2", klingV3OmniModel, map[string]any{ "contents": []any{ map[string]any{"type": "prompt", "text": "让角色向镜头挥手"}, map[string]any{"type": "first_frame", "url": "https://example.com/first.png"}, map[string]any{"type": "element", "id": json.Number("42")}, }, "settings": map[string]any{ "resolution": "1080p", "duration": json.Number("15"), "aspect_ratio": "9:16", "audio": "native", }, "options": map[string]any{ "external_task_id": "client-v2-1", "callback_url": "https://example.com/callback", }, }) if err != nil { t.Fatalf("build V2 compatibility body: %v", err) } if externalID != "client-v2-1" || body["mode"] != "pro" || body["audio"] != true { t.Fatalf("unexpected V2 settings: %#v", body) } if len(mapListFromRequest(body["image_list"])) != 1 || len(mapListFromRequest(body["element_list"])) != 1 { t.Fatalf("unexpected V2 references: %#v", body) } } func TestKlingCompatibilityValidation(t *testing.T) { tests := []struct { name string model string body map[string]any }{ {name: "O1 duration", model: klingO1Model, body: map[string]any{"prompt": "test", "duration": 11}}, {name: "O1 text-only flexible duration", model: klingO1Model, body: map[string]any{"prompt": "test", "duration": 3}}, {name: "O1 4k", model: klingO1Model, body: map[string]any{"prompt": "test", "duration": 5, "mode": "4k"}}, {name: "O1 multi-shot", model: klingO1Model, body: map[string]any{"multi_shot": true, "multi_prompt": []any{map[string]any{"prompt": "test", "duration": 3}}}}, {name: "custom multi-shot without prompts", model: klingV3OmniModel, body: map[string]any{"multi_shot": true, "shot_type": "customize"}}, {name: "intelligence multi-shot without prompt", model: klingV3OmniModel, body: map[string]any{"multi_shot": true, "shot_type": "intelligence"}}, {name: "video with native audio", model: klingV3OmniModel, body: map[string]any{"prompt": "test", "sound": "on", "video_list": []any{map[string]any{"video_url": "https://example.com/input.mp4"}}}}, {name: "video duration too long", model: klingV3OmniModel, body: map[string]any{"prompt": "test", "duration": 15, "video_list": []any{map[string]any{"video_url": "https://example.com/input.mp4"}}}}, {name: "too many references", model: klingV3OmniModel, body: map[string]any{"prompt": "test", "image_list": []any{ map[string]any{}, map[string]any{}, map[string]any{}, map[string]any{}, map[string]any{}, map[string]any{}, map[string]any{}, map[string]any{}, }}}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if _, _, err := klingCompatTaskBody("v1", test.model, test.body); err == nil { t.Fatal("expected validation error") } }) } if _, _, err := klingCompatTaskBody("v1", klingV3OmniModel, map[string]any{"prompt": "test", "duration": 15}); err != nil { t.Fatalf("3.0 Omni should allow a 15-second duration: %v", err) } } func TestDecodeKlingJSONRejectsTrailingData(t *testing.T) { request := httptest.NewRequest(http.MethodPost, "/kling/v1/videos/omni-video", strings.NewReader(`{"prompt":"ok"} trailing`)) var body map[string]any if err := decodeKlingJSON(request, &body); err == nil { t.Fatal("expected trailing JSON error") } } func TestWriteKlingCompatErrorUsesOfficialNumericEnvelope(t *testing.T) { recorder := httptest.NewRecorder() writeKlingCompatError(recorder, http.StatusBadRequest, "bad request", "invalid_parameter") var body map[string]any if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatalf("decode error response: %v", err) } if body["code"] != float64(1001) || body["error"] != "invalid_parameter" { t.Fatalf("unexpected error envelope: %#v", body) } }