Estate-wide consolidation pass. golangci-lint v2 default set enables NO formatter, so gofmt drift accumulated silently across the estate -- 8 repos were carrying it, this one included. Same hole that let two files rot in dma-cli until they were found by hand (dma-cli#12). Mechanical and deterministic: `gofmt -w`, nothing hand-edited. Verified before commit -- gofmt -l now reports 0, go build and go vet both pass. Clears this repo to adopt the canonical .golangci.yml (infra ci-templates/golangci.yml, infra#309 pattern) in a later wave.
96 lines
2.5 KiB
Go
96 lines
2.5 KiB
Go
package vectorstore_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mathiasbq/hyperguild/ingestion/internal/vectorstore"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// integration tests run against a real postgres18 + pgvector. Gated by
|
|
// BRAIN_PG_TEST_DSN so `task check` stays hermetic on hosts without a
|
|
// reachable database.
|
|
//
|
|
// To run:
|
|
//
|
|
// BRAIN_PG_TEST_DSN='postgres://brain_app:pwd@127.0.0.1:5432/brain' \
|
|
// go test ./internal/vectorstore/... -run Integration
|
|
func dsn(t *testing.T) string {
|
|
t.Helper()
|
|
v := os.Getenv("BRAIN_PG_TEST_DSN")
|
|
if v == "" {
|
|
t.Skip("BRAIN_PG_TEST_DSN not set; skipping pgvector integration tests")
|
|
}
|
|
return v
|
|
}
|
|
|
|
func freshStore(t *testing.T) (*vectorstore.PGStore, context.Context) {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
t.Cleanup(cancel)
|
|
s, err := vectorstore.New(ctx, dsn(t))
|
|
require.NoError(t, err)
|
|
t.Cleanup(s.Close)
|
|
require.NoError(t, s.Init(ctx))
|
|
// Clean slate per test.
|
|
_, _ = s.KnownPathsWithTime(ctx)
|
|
require.NoError(t, s.Delete(ctx, "%test-fixture%"))
|
|
return s, ctx
|
|
}
|
|
|
|
func vec(dim int, fill float32) []float32 {
|
|
v := make([]float32, dim)
|
|
for i := range v {
|
|
v[i] = fill
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestIntegration_UpsertAndSearch(t *testing.T) {
|
|
s, ctx := freshStore(t)
|
|
|
|
require.NoError(t, s.Upsert(ctx, "wiki/a.md", vec(768, 1.0)))
|
|
require.NoError(t, s.Upsert(ctx, "wiki/b.md", vec(768, -1.0)))
|
|
|
|
hits, err := s.Search(ctx, vec(768, 1.0), 2)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(hits), 1)
|
|
assert.Equal(t, "wiki/a.md", hits[0].Path)
|
|
assert.InDelta(t, 0.0, hits[0].Distance, 1e-5)
|
|
|
|
t.Cleanup(func() {
|
|
_ = s.Delete(ctx, "wiki/a.md")
|
|
_ = s.Delete(ctx, "wiki/b.md")
|
|
})
|
|
}
|
|
|
|
func TestIntegration_KnownPathsWithTime(t *testing.T) {
|
|
s, ctx := freshStore(t)
|
|
before := time.Now()
|
|
require.NoError(t, s.Upsert(ctx, "wiki/k.md", vec(768, 0.5)))
|
|
t.Cleanup(func() { _ = s.Delete(ctx, "wiki/k.md") })
|
|
|
|
paths, err := s.KnownPathsWithTime(ctx)
|
|
require.NoError(t, err)
|
|
at, ok := paths["wiki/k.md"]
|
|
require.True(t, ok)
|
|
assert.False(t, at.IsZero(), "updated_at must not be zero")
|
|
assert.WithinDuration(t, before, at, 5*time.Second, "updated_at must be recent")
|
|
}
|
|
|
|
func TestUpsert_RejectsWrongDimension(t *testing.T) {
|
|
s := &vectorstore.PGStore{}
|
|
err := s.Upsert(context.Background(), "x", vec(100, 0))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestSearch_RejectsWrongDimension(t *testing.T) {
|
|
s := &vectorstore.PGStore{}
|
|
_, err := s.Search(context.Background(), vec(100, 0), 5)
|
|
require.Error(t, err)
|
|
}
|