package vector import ( "context" "errors" "os" "testing" ) func TestNewPG_InvalidURL_异常(t *testing.T) { t.Parallel() if _, err := NewPG(context.Background(), " ", 4, testLogger()); !errors.Is(err, ErrInvalidPGURL) { t.Fatalf("NewPG(blank url) err = %v, want ErrInvalidPGURL", err) } } func TestNewPG_InvalidDim_异常(t *testing.T) { t.Parallel() // dim 在任何连接打开前就校验,因此这里没有网络 I/O。 if _, err := NewPG(context.Background(), "postgres://host/db", 0, testLogger()); !errors.Is(err, ErrInvalidDim) { t.Fatalf("NewPG(dim 0) err = %v, want ErrInvalidDim", err) } } func TestPGVectorLiteral(t *testing.T) { t.Parallel() tests := []struct { name string v Vector want string }{ {name: "single", v: Vector{1}, want: "[1]"}, {name: "three", v: Vector{1.5, 2, -0.25}, want: "[1.5,2,-0.25]"}, {name: "empty", v: Vector{}, want: "[]"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := pgVectorLiteral(tt.v); got != tt.want { t.Fatalf("pgVectorLiteral(%v) = %q, want %q", tt.v, got, tt.want) } }) } } // TestPG_RequiresEnv 在设置了 DB_PG_URL 时跑一次真实的 // PostgreSQL+pgvector 往返,否则跳过。本环境默认没有 PG // 服务,因此由 pgvector 支撑的 Add/Search 路径只有在 // 操作者提供带 pgvector 扩展的活数据库时才端到端验证。 // SQLite 路径(默认)由 sqlite_test.go 套件完整覆盖。 func TestPG_RequiresEnv(t *testing.T) { url := os.Getenv("DB_PG_URL") if url == "" { t.Skip("DB_PG_URL not set; skipping real PostgreSQL+pgvector integration test") } ctx := context.Background() idx, err := NewPG(ctx, url, 4, testLogger()) if err != nil { t.Fatalf("NewPG: %v", err) } idx.withEmbedder(&mockEmbedder{dim: 4}) defer func() { _ = idx.Close() }() if err := idx.Add(ctx, "a", Vector{1, 0, 0, 0}); err != nil { t.Fatalf("Add a: %v", err) } if err := idx.Add(ctx, "b", Vector{0, 1, 0, 0}); err != nil { t.Fatalf("Add b: %v", err) } hits, err := idx.Search(ctx, Vector{1, 0, 0, 0}, 1) if err != nil { t.Fatalf("Search: %v", err) } if len(hits) != 1 || hits[0].ID != "a" { t.Fatalf("pg top hit = %+v, want single a", hits) } if hits[0].Score < 0.99 { t.Fatalf("pg a score = %v, want ~1.0", hits[0].Score) } // embedder 已接入,因此 Index.Embed 可用。 vs, err := idx.Embed(ctx, []string{"hello"}) if err != nil { t.Fatalf("Embed: %v", err) } if len(vs) != 1 || len(vs[0]) != 4 { t.Fatalf("Embed => %d vectors, want one 4-dim", len(vs)) } // Vectors(增量向量化的读口):命中按位置给出,未命中为 nil —— // 这条断言顺带验 vector::text 的解析在真实 pgvector 上成立。 got, err := idx.Vectors(ctx, []string{"b", "missing", "a"}) if err != nil { t.Fatalf("Vectors: %v", err) } if len(got) != 3 { t.Fatalf("Vectors 返回 %d 条, want 3", len(got)) } if !equalVector(got[0], Vector{0, 1, 0, 0}) { t.Fatalf("Vectors[0] = %v, want b 的向量", got[0]) } if len(got[1]) != 0 { t.Fatalf("未命中的 id 返回了向量:%v", got[1]) } if !equalVector(got[2], Vector{1, 0, 0, 0}) { t.Fatalf("Vectors[2] = %v, want a 的向量", got[2]) } }