package repository import ( "math" "strings" "testing" ) // TestFormatEmbeddingVector 向量串格式化是写入正确性的关键: // 维度必须精确、float32 精度不能丢(截断会让相似度漂移)。 func TestFormatEmbeddingVector(t *testing.T) { v := make([]float32, 768) v[0] = 0.5 v[767] = -0.25 s := FormatEmbeddingVector(v) if !strings.HasPrefix(s, "[") || !strings.HasSuffix(s, "]") { t.Fatalf("应输出 [..] 形式的向量串,得到 %q", truncForTest(s)) } if got := strings.Count(s, ",") + 1; got != 768 { t.Fatalf("维度应为 768,得到 %d", got) } if !strings.Contains(s, "0.5") || !strings.Contains(s, "-0.25") { t.Fatalf("首尾元素未按 float32 精度输出: %s", truncForTest(s)) } } // TestFormatEmbeddingVectorEmpty 空向量必须能安全格式化,不能 panic。 func TestFormatEmbeddingVectorEmpty(t *testing.T) { if got := FormatEmbeddingVector(nil); got != "[]" { t.Fatalf("nil 应格式化为 [],得到 %q", got) } } // TestParseEmbeddingVectorRoundTrip 格式化与解析必须能精确往返, // 否则「写进去的向量」与「读出来算相似度的向量」不是同一个,阈值校准会失真。 func TestParseEmbeddingVectorRoundTrip(t *testing.T) { v := make([]float32, 768) for i := range v { v[i] = float32(i%17-8) * 0.0173 } got := ParseEmbeddingVector(FormatEmbeddingVector(v)) if len(got) != len(v) { t.Fatalf("往返后长度应一致:%d != %d", len(got), len(v)) } for i := range v { if got[i] != v[i] { t.Fatalf("第 %d 维往返后不一致:%v != %v", i, got[i], v[i]) } } } // TestParseEmbeddingVectorInvalid 非法输入返回 nil(调用方据此跳过该行,不阻断渲染)。 func TestParseEmbeddingVectorInvalid(t *testing.T) { for _, bad := range []string{"", "[]", "0.1,0.2", "[0.1,abc]", "[", "null"} { if got := ParseEmbeddingVector(bad); got != nil { t.Fatalf("非法输入 %q 应返回 nil,得到 %v", bad, got) } } } // TestFormatEmbeddingVectorNormalized 归一化向量点积等于余弦相似度: // 用同一向量的自点积验证模长≈1(服务端已归一化的前提)。 func TestFormatEmbeddingVectorNormalized(t *testing.T) { v := []float32{0.6, 0.8} // 模长为 1 got := ParseEmbeddingVector(FormatEmbeddingVector(v)) var dot float64 for _, f := range got { dot += float64(f) * float64(f) } if math.Abs(dot-1) > 1e-6 { t.Fatalf("模长应≈1,得到 %f", dot) } } func truncForTest(s string) string { if len(s) > 80 { return s[:80] + "..." } return s }