Files
Travis Cline 77504b877f all: expand test coverage (#1312)
* scripts: cleanup test scripts
* lint: add test pattern and architectural linting
* internal/httprr: expand httprr testing
* test: add comprehensive unit test coverage
* docs: add comprehensive testing guide to CONTRIBUTING.md
* examples: standardize module versions and cleanup dependencies
* all: re-record several httprr recordings
2025-06-16 18:14:02 +02:00

252 lines
6.0 KiB
Go

// nolint
package cloudsql_test
import (
"context"
"fmt"
"net/http"
"os"
"testing"
"github.com/tmc/langchaingo/internal/httprr"
"github.com/tmc/langchaingo/schema"
"github.com/tmc/langchaingo/util/cloudsqlutil"
"github.com/tmc/langchaingo/vectorstores/cloudsql"
)
type EnvVariables struct {
Username string
Password string
Database string
ProjectID string
Region string
Instance string
Cluster string
Table string
}
func getEnvVariables(t *testing.T) EnvVariables {
t.Helper()
// Check for OpenAI API key since we use OpenAI embeddings
httprr.SkipIfNoCredentialsAndRecordingMissing(t, "OPENAI_API_KEY")
username := os.Getenv("CLOUDSQL_USERNAME")
if username == "" {
t.Skip("env variable CLOUDSQL_USERNAME is empty")
}
// Requires environment variable CLOUDSQL_PASSWORD to be set.
password := os.Getenv("CLOUDSQL_PASSWORD")
if password == "" {
t.Skip("env variable CLOUDSQL_PASSWORD is empty")
}
// Requires environment variable CLOUDSQL_DATABASE to be set.
database := os.Getenv("CLOUDSQL_DATABASE")
if database == "" {
t.Skip("env variable CLOUDSQL_DATABASE is empty")
}
// Requires environment variable CLOUDSQL_ID to be set.
projectID := os.Getenv("PROJECT_ID")
if projectID == "" {
t.Skip("env variable PROJECT_ID is empty")
}
// Requires environment variable ALLOYDB_REGION to be set.
region := os.Getenv("CLOUDSQL_REGION")
if region == "" {
t.Skip("env variable CLOUDSQL_REGION is empty")
}
// Requires environment variable ALLOYDB_INSTANCE to be set.
instance := os.Getenv("CLOUDSQL_INSTANCE")
if instance == "" {
t.Skip("env variable CLOUDSQL_INSTANCE is empty")
}
// Requires environment variable CLOUDSQL_CLUSTER to be set.
cluster := os.Getenv("CLOUDSQL_CLUSTER")
if cluster == "" {
t.Skip("env variable CLOUDSQL_CLUSTER is empty")
}
// Requires environment variable CLOUDSQL_TABLE to be set.
table := os.Getenv("CLOUDSQL_TABLE")
if table == "" {
t.Skip("env variable CLOUDSQL_TABLE is empty")
}
envVariables := EnvVariables{
Username: username,
Password: password,
Database: database,
ProjectID: projectID,
Region: region,
Instance: instance,
Cluster: cluster,
Table: table,
}
return envVariables
}
func setEngine(t *testing.T, envVariables EnvVariables) cloudsqlutil.PostgresEngine {
t.Helper()
ctx := context.Background()
pgEngine, err := cloudsqlutil.NewPostgresEngine(ctx,
cloudsqlutil.WithUser(envVariables.Username),
cloudsqlutil.WithPassword(envVariables.Password),
cloudsqlutil.WithDatabase(envVariables.Database),
cloudsqlutil.WithCloudSQLInstance(envVariables.ProjectID, envVariables.Region, envVariables.Instance),
)
if err != nil {
t.Fatal("Could not set Engine: ", err)
}
return pgEngine
}
func vectorStore(t *testing.T, envVariables EnvVariables) (cloudsql.VectorStore, func() error) {
t.Helper()
if testing.Short() {
t.Skip("skipping cloudsql tests in short mode")
}
pgEngine := setEngine(t, envVariables)
ctx := context.Background()
vectorstoreTableoptions := cloudsqlutil.VectorstoreTableOptions{
TableName: envVariables.Table,
OverwriteExisting: true,
VectorSize: 1536,
StoreMetadata: true,
}
err := pgEngine.InitVectorstoreTable(ctx, vectorstoreTableoptions)
if err != nil {
t.Fatal(err)
}
// Initialize OpenAI embedder with httprr
e := createOpenAIEmbedder(t)
vs, err := cloudsql.NewVectorStore(pgEngine, e, envVariables.Table)
if err != nil {
t.Fatal(err)
}
cleanUpTableFn := func() error {
_, err := pgEngine.Pool.Exec(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s", envVariables.Table))
return err
}
return vs, cleanUpTableFn
}
func TestApplyVectorIndexAndDropIndex(t *testing.T) {
t.Parallel()
envVariables := getEnvVariables(t)
vs, cleanUpTableFn := vectorStore(t, envVariables)
ctx := context.Background()
idx := vs.NewBaseIndex("testindex", "hnsw", cloudsql.CosineDistance{}, []string{}, cloudsql.HNSWOptions{M: 4, EfConstruction: 16})
err := vs.ApplyVectorIndex(ctx, idx, "testindex", false)
if err != nil {
t.Fatal(err)
}
err = vs.DropVectorIndex(ctx, "testindex")
if err != nil {
t.Fatal(err)
}
err = cleanUpTableFn()
if err != nil {
t.Fatal(err)
}
}
func TestIsValidIndex(t *testing.T) {
t.Parallel()
envVariables := getEnvVariables(t)
vs, cleanUpTableFn := vectorStore(t, envVariables)
ctx := context.Background()
idx := vs.NewBaseIndex("testindex", "hnsw", cloudsql.CosineDistance{}, []string{}, cloudsql.HNSWOptions{M: 4, EfConstruction: 16})
err := vs.ApplyVectorIndex(ctx, idx, "testindex", false)
if err != nil {
t.Fatal(err)
}
_, err = vs.IsValidIndex(ctx, "testindex")
if err != nil {
t.Fatal(err)
}
err = vs.DropVectorIndex(ctx, "testindex")
if err != nil {
t.Fatal(err)
}
err = cleanUpTableFn()
if err != nil {
t.Fatal(err)
}
}
func TestAddDocuments(t *testing.T) {
httprr.SkipIfNoCredentialsAndRecordingMissing(t, "OPENAI_API_KEY")
rr := httprr.OpenForTest(t, http.DefaultTransport)
defer rr.Close()
if !rr.Recording() {
t.Parallel()
}
ctx := context.Background()
envVariables := getEnvVariables(t)
vs, cleanUpTableFn := vectorStore(t, envVariables)
_, err := vs.AddDocuments(ctx, []schema.Document{
{
PageContent: "Tokyo",
Metadata: map[string]any{
"population": 38,
"area": 2190,
},
},
{
PageContent: "Paris",
Metadata: map[string]any{
"population": 11,
"area": 105,
},
},
{
PageContent: "London",
Metadata: map[string]any{
"population": 9.5,
"area": 1572,
},
},
{
PageContent: "Santiago",
Metadata: map[string]any{
"population": 6.9,
"area": 641,
},
},
{
PageContent: "Buenos Aires",
Metadata: map[string]any{
"population": 15.5,
"area": 203,
},
},
{
PageContent: "Rio de Janeiro",
Metadata: map[string]any{
"population": 13.7,
"area": 1200,
},
},
{
PageContent: "Sao Paulo",
Metadata: map[string]any{
"population": 22.6,
"area": 1523,
},
},
})
if err != nil {
t.Fatal(err)
}
err = cleanUpTableFn()
if err != nil {
t.Fatal(err)
}
}