package providers import ( "bytes" "context" "encoding/json" "errors" "io" "log" "net/http" "reflect" "strings" "testing" "unicode/utf8" ) func TestMinimaxPreservesRejectionReasonForPersistedTextVideoRequest(t *testing.T) { var output bytes.Buffer previousOutput := log.Writer() log.SetOutput(&output) t.Cleanup(func() { log.SetOutput(previousOutput) }) var request Request if err := json.Unmarshal([]byte(`{"model":"MiniMax-H3","prompt":"一段盛开的烟花","settings":{"ratio":"1:1","duration":4,"resolution":"768P"},"capability":"video.generate"}`), &request); err != nil { t.Fatal(err) } client := roundTripFunc(func(r *http.Request) (*http.Response, error) { if r.Method != http.MethodPost || r.URL.Path != "/v2/video_generation" { t.Fatalf("unexpected endpoint: %s %s", r.Method, r.URL.Path) } var payload map[string]any if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { t.Fatal(err) } want := map[string]any{ "model": "MiniMax-H3", "resolution": "768P", "duration": float64(4), "ratio": "1:1", "aigc_watermark": false, "content": []any{map[string]any{"type": "text", "text": "一段盛开的烟花"}}, } if !reflect.DeepEqual(payload, want) { t.Fatalf("wire payload=%#v want=%#v", payload, want) } // A synthetic validation response: this does not assert that 4 seconds // is unsupported by the live API, only that its explanation survives. return &http.Response{StatusCode: 400, Header: http.Header{}, Body: io.NopCloser(strings.NewReader(`{"error":{"type":"bad_request_error","message":"invalid params, duration is not supported (2013)"},"request_id":"request-minimax-1"}`))}, nil }) adapter := NewMinimax(Config{BaseURL: "https://api.minimax.cn", APIKey: "private-api-key"}, client) _, err := adapter.Submit(context.Background(), request) var failure *ProviderError if !errors.As(err, &failure) || failure.Code != "2013" || failure.RequestID != "request-minimax-1" || failure.Detail != "invalid params, duration is not supported (2013)" { t.Fatalf("failure=%#v err=%v", failure, err) } if strings.Contains(err.Error(), "invalid params") { t.Fatalf("generic error exposed diagnostic: %v", err) } for _, expected := range []string{`detail="invalid params, duration is not supported (2013)"`, `requestId="request-minimax-1"`, `model="MiniMax-H3"`, `resolution="768P"`, "duration=4", `ratio="1:1"`, "promptChars=7", "imageItems=0", "endpointClass=official-cn", "basePathClass=root"} { if !strings.Contains(output.String(), expected) { t.Errorf("missing %q from %s", expected, output.String()) } } for _, private := range []string{"一段盛开的烟花", "private-api-key"} { if strings.Contains(output.String(), private) { t.Errorf("log leaked %q", private) } } } func TestMinimaxFailureDetailRedactsEchoedInputsCredentialsAndURLs(t *testing.T) { const prompt = "private prompt\nsecond line" const imageURL = "https://private.test/a.png?Signature=private-signature" request, _ := json.Marshal(map[string]any{"content": []any{ map[string]any{"type": "text", "text": prompt}, map[string]any{"type": "image_url", "image_url": map[string]any{"url": imageURL}}, }}) encodedPrompt, _ := json.Marshal(prompt) response, _ := json.Marshal(map[string]any{"error": map[string]any{"message": "invalid params, 'model' \"MiniMax-H3\" not supported; " + prompt + " " + string(encodedPrompt) + " " + imageURL + " Bearer private-api-key api_key=another-key Authorization: Bearer unrelated-token 'private-user-name' https://other-private.test/a.png?token=other-secret (2013)"}}) detail := minimaxSafeFailureDetail(response, request, "private-api-key") for _, expected := range []string{"invalid params", "model", "MiniMax-H3", "not supported", "(2013)", "[redacted]"} { if !strings.Contains(detail, expected) { t.Fatalf("missing %q in %q", expected, detail) } } for _, private := range []string{"private", "second line", "another-key", "unrelated-token", "other-secret", "Signature=", "https://", "Bearer", "\n"} { if strings.Contains(detail, private) { t.Fatalf("detail leaked %q: %s", private, detail) } } } func TestMinimaxFailureDetailIsBoundedAndIgnoresOtherResponseFields(t *testing.T) { response, _ := json.Marshal(map[string]any{"error": map[string]any{"message": strings.Repeat("参数无效 ", 200), "request": "private request body"}, "secret": "private secret"}) detail := minimaxSafeFailureDetail(response, nil, "") if utf8.RuneCountInString(detail) > 512+len("...[truncated]") || !strings.HasSuffix(detail, "...[truncated]") || strings.Contains(detail, "private") { t.Fatalf("unsafe/unbounded detail: %s", detail) } if detail := minimaxSafeFailureDetail([]byte("not json"), nil, ""); detail != "" { t.Fatalf("invalid JSON detail=%q", detail) } }