package vision import ( "bytes" "context" "encoding/base64" "errors" "image" "image/color" "image/jpeg" "strings" "testing" ) // jpegBytes 返回一张真实的 JPEG(1x1),与 mime_test.go 的 pngBytes 同用途: // 让「内容嗅探」在测试里有真实依据,而不是手写常量。 func jpegBytes(t *testing.T) []byte { t.Helper() img := image.NewRGBA(image.Rect(0, 0, 2, 2)) img.Set(0, 0, color.RGBA{R: 0xcc, G: 0x33, B: 0x33, A: 0xff}) var buf bytes.Buffer if err := jpeg.Encode(&buf, img, nil); err != nil { t.Fatalf("encode jpeg: %v", err) } return buf.Bytes() } // htmlDataURI 构造「把网页当图片传」的入参:声明 text/html,载荷是一段 HTML。 func htmlDataURI() string { return "data:text/html;base64," + base64.StdEncoding.EncodeToString(htmlBytes()) } // TestResolve_DataURIImagesDecode 钉住本缺陷的主用例:带完整 base64 载荷的 // data: URI(jpeg / png)必须照常解出字节与声明的 MIME —— 也就是 2026-10-07 // 那次失败里「本该成立的形状」。MIME 取前缀声明,字节逐字节不变。 func TestResolve_DataURIImagesDecode(t *testing.T) { t.Parallel() cases := []struct { name string mime string data []byte }{ {name: "jpeg", mime: "image/jpeg", data: jpegBytes(t)}, {name: "png", mime: "image/png", data: pngBytes(t)}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() src := DataURI(tc.mime, tc.data) ref, err := ParseImageRef(src) if err != nil { t.Fatalf("ParseImageRef: %v", err) } mime, data, err := Resolve(context.Background(), ref, nil) if err != nil { t.Fatalf("Resolve(%s): %v", tc.mime, err) } if mime != tc.mime { t.Errorf("mime = %q, want %q", mime, tc.mime) } if !bytes.Equal(data, tc.data) { t.Errorf("bytes = %d, want the %d decoded bytes", len(data), len(tc.data)) } }) } } // TestResolve_EmptyDataURIPayloadRejected 是本缺陷的回归用例(2026-10-07 // 18:32:51 实测):模型传的是 `data:image/png;base64,` —— **只有前缀、没有载荷**。 // 旧代码解出零字节却照样报 image/png,空载荷被当成图片发给上游,换回一句 // 「You have uploaded an unsupported image」。现在必须在本地就明确否决, // 并且说清病因(载荷为空)而不是甩一句 upstream 400。 func TestResolve_EmptyDataURIPayloadRejected(t *testing.T) { t.Parallel() ref, err := ParseImageRef("data:image/png;base64,") if err != nil { t.Fatalf("ParseImageRef: %v", err) } mime, data, err := Resolve(context.Background(), ref, nil) if err == nil { t.Fatalf("空载荷被接受(mime=%q, %d bytes),want 明确报错", mime, len(data)) } if !errors.Is(err, ErrNotImage) { t.Fatalf("error = %v, want ErrNotImage", err) } if !strings.Contains(err.Error(), "empty") { t.Errorf("错误文案没点出「载荷为空」:%v", err) } if !strings.Contains(err.Error(), "http(s) URL") { t.Errorf("错误文案没给可行动提示:%v", err) } } // TestResolve_NonImageDataURIRejected 钉住「这不是图片」:声明 text/html 的 // data: URI 必须报明确错误,写清嗅探到的类型与支持的格式,且**不得回显正文**。 func TestResolve_NonImageDataURIRejected(t *testing.T) { t.Parallel() ref, err := ParseImageRef(htmlDataURI()) if err != nil { t.Fatalf("ParseImageRef: %v", err) } if _, _, err := Resolve(context.Background(), ref, nil); err == nil { t.Fatal("text/html 载荷被接受,want ErrNotImage") } else { if !errors.Is(err, ErrNotImage) { t.Fatalf("error = %v, want ErrNotImage", err) } msg := err.Error() if strings.Contains(msg, "502 Bad Gateway") || strings.Contains(msg, "