feat(plugin): 海螺视频 - 更新 v2 插件合同并补充失败响应验证

This commit is contained in:
ddCat
2026-09-19 20:29:46 +08:00
parent 0d4d6870b5
commit a59b77cf70
6 changed files with 1387 additions and 0 deletions

View File

@@ -599,6 +599,60 @@ func TestDeclarativeNewAPIChannel2TaskNotExistExhaustion(t *testing.T) {
}
}
func TestDeclarativeMiniMaxFailureReachesTaskError(t *testing.T) {
allowLoopbackProviderTest(t)
data, err := os.ReadFile(filepath.Join("..", "..", "..", "plugin-packages", "minimax-hailuo-video-v2.yingce-plugin"))
if err != nil {
t.Fatal(err)
}
pkg, err := protocol.ParsePluginPackage(data)
if err != nil {
t.Fatal(err)
}
adapters, err := protocol.LoadInstalledProviders(pkg.ManifestRaw, nil)
if err != nil || len(adapters) != 1 {
t.Fatalf("load MiniMax adapter: count=%d, error=%v", len(adapters), err)
}
const message = "content[1].image_url: media dimensions must be between 256 and 5760 pixels"
const failedResponse = `{"task":{"id":"video-1","status":"failed","error":{"code":"2013","message":"` + message + `"}}}`
for _, phase := range []string{"create", "poll"} {
t.Run(phase, func(t *testing.T) {
createCalls, pollCalls := 0, 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/v2/video_generation":
createCalls++
if phase == "create" {
_, _ = w.Write([]byte(failedResponse))
} else {
_, _ = w.Write([]byte(`{"task":{"id":"video-1","status":"pending"}}`))
}
case "/v2/query/video_generation/video-1":
pollCalls++
_, _ = w.Write([]byte(failedResponse))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
config := providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video", VideoSeconds: "12"}
result, err := runProtocolAdapterTaskWithPolicy(context.Background(), canvasGenerationInput{Mode: "video", Prompt: "test", Config: config}, adapters[0], fastVideoPollPolicy())
want := "声明式协议任务失败(任务 video-1):" + message
if result != nil || err == nil || taskFailureMessage(err) != want {
t.Fatalf("result=%#v, error=%v, want task error %q", result, err, want)
}
wantPollCalls := 0
if phase == "poll" {
wantPollCalls = 1
}
if createCalls != 1 || pollCalls != wantPollCalls {
t.Fatalf("create calls=%d, poll calls=%d; failed tasks must not be retried", createCalls, pollCalls)
}
})
}
}
func TestProviderTaskNotReadyStrictClassification(t *testing.T) {
tests := []struct {
name string

View File

@@ -0,0 +1,39 @@
package protocol
import (
"context"
"testing"
)
func TestMiniMaxVideoFailureMessages(t *testing.T) {
adapter := officialPackageAdapter(t, "minimax-hailuo-video-v2.yingce-plugin", "minimax-video")
const dimensionError = "content[1].image_url: media dimensions must be between 256 and 5760 pixels"
for _, tc := range []struct {
name, payload, message string
status Status
}{
{"nested task error", `{"task":{"id":"video-1","status":"failed","error":{"code":"2013","message":"` + dimensionError + `"}}}`, dimensionError, StatusFailed},
{"nested error without status", `{"task":{"id":"video-1","error":{"code":"2013","message":"` + dimensionError + `"}}}`, dimensionError, StatusFailed},
{"specific error precedes envelope", `{"base_resp":{"status_code":0,"status_msg":"success"},"task":{"id":"video-1","status":"failed","message":"task failed","error":{"code":"2013","message":"` + dimensionError + `"}}}`, dimensionError, StatusFailed},
{"base response error", `{"task_id":"video-1","base_resp":{"status_code":2013,"status_msg":"invalid input"}}`, "invalid input", StatusFailed},
{"task message", `{"task":{"id":"video-1","status":"failed","message":"task rejected"}}`, "task rejected", StatusFailed},
{"successful task", `{"task":{"id":"video-1","status":"succeeded","error":{"code":0},"content":{"url":"https://cdn.example/video.mp4"}}}`, "", StatusSucceeded},
} {
t.Run(tc.name, func(t *testing.T) {
created, err := adapter.ParseCreate(context.Background(), []byte(tc.payload))
if err != nil {
t.Fatal(err)
}
if created.TaskID != "video-1" || created.Status != tc.status || created.Message != tc.message {
t.Fatalf("create = %#v, want status %s and message %q", created, tc.status, tc.message)
}
polled, err := adapter.ParsePoll(context.Background(), PollContext{TaskID: "video-1"}, []byte(tc.payload))
if err != nil {
t.Fatal(err)
}
if polled.TaskID != "video-1" || polled.Status != tc.status || polled.Message != tc.message {
t.Fatalf("poll = %#v, want status %s and message %q", polled, tc.status, tc.message)
}
})
}
}