//go:build integration // 集成测试:打真实运行的视觉服务,验证 Go 客户端与服务端的接口契约。 // // 运行前先起服务(本地开发用项目 .venv,走 GPU): // // cd D:\project\fashion-siglip // .venv\Scripts\python.exe -m uvicorn app:app --host 127.0.0.1 --port 8900 --workers 1 // // 然后: // // go test -tags integration ./internal/vision/ -run TestClientAgainstRealService -v // // 服务未启动时本测试 **跳过**(不算失败),以免阻塞没有该服务的环境。 // 可用 VISION_ENDPOINT 环境变量指向其它地址。 package vision import ( "bytes" "context" "encoding/json" "image" "image/color" "image/png" "net/http" "os" "testing" "time" ) func TestClientAgainstRealService(t *testing.T) { endpoint := os.Getenv("VISION_ENDPOINT") if endpoint == "" { endpoint = "http://127.0.0.1:8900" } // 1) 探活:不通就跳过(本测试依赖外部进程,不应让 CI 变红)。 hc := &http.Client{Timeout: 3 * time.Second} resp, err := hc.Get(endpoint + "/health") if err != nil { t.Skipf("视觉服务不可达(%s),跳过集成测试:%v", endpoint, err) } defer func() { _ = resp.Body.Close() }() var health struct { Ready bool `json:"ready"` Dim int `json:"dim"` Device string `json:"device"` } if err := json.NewDecoder(resp.Body).Decode(&health); err != nil { t.Fatalf("解析 /health 失败: %v", err) } if !health.Ready { t.Skipf("视觉服务未就绪(模型仍在加载或加载失败),跳过:%+v", health) } t.Logf("服务就绪:dim=%d device=%s", health.Dim, health.Device) // 2) 用一张程序生成的 PNG 验证完整契约:base64 请求 → 服务解码 → 768 维归一化向量。 img := generateTestPNG(t) c := New(endpoint, health.Dim, 60*time.Second, 3) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() vecs, skipped, err := c.Embed(ctx, [][]byte{img}) if err != nil { t.Fatalf("Embed 失败: %v", err) } if len(skipped) != 0 { t.Fatalf("合法 PNG 不应被跳过,skipped=%v", skipped) } if len(vecs) != 1 { t.Fatalf("应返回 1 个向量,得到 %d", len(vecs)) } if got := len(vecs[0]); got != health.Dim { t.Fatalf("向量维度应为 %d,得到 %d", health.Dim, got) } // 服务端承诺已 L2 归一化:模长≈1 是「点积即余弦相似度」的前提。 var sumSq float64 for _, f := range vecs[0] { sumSq += float64(f) * float64(f) } norm := sumSq if norm < 0.99 || norm > 1.01 { t.Fatalf("向量应已 L2 归一化(模长²≈1),实际 %f", norm) } } // generateTestPNG 生成一张 64x64 的纯色 PNG(不引入外部测试资源)。 func generateTestPNG(t *testing.T) []byte { t.Helper() img := image.NewRGBA(image.Rect(0, 0, 64, 64)) for x := 0; x < 64; x++ { for y := 0; y < 64; y++ { img.Set(x, y, color.RGBA{R: 200, G: 60, B: 120, A: 255}) } } var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { t.Fatalf("生成测试 PNG 失败: %v", err) } return buf.Bytes() }