test(search): convert bleve geo/mtime tests to ginkgo

The package's engine suite is ginkgo; these new tests were plain.
This commit is contained in:
Dominik Schmidt
2026-07-05 16:56:10 +02:00
parent 9ebf755cfd
commit 22f46dd9d4
2 changed files with 104 additions and 166 deletions

View File

@@ -1,11 +1,10 @@
package bleve_test
import (
"sort"
"testing"
bleveSearch "github.com/blevesearch/bleve/v2"
"github.com/blevesearch/bleve/v2/search/query"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
"github.com/opencloud-eu/opencloud/services/search/pkg/bleve"
@@ -14,18 +13,14 @@ import (
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
)
// geoFixture builds an in-memory bleve index with a single resource that
// carries the given lon/lat/alt. Used by the search tests below.
func geoFixture(t *testing.T, lon, lat, alt float64) bleveSearch.Index {
t.Helper()
// geoFixture builds an in-memory bleve index with a single resource carrying
// the given lon/lat/alt, indexed through the full bleve pipeline.
func geoFixture(lon, lat, alt float64) bleveSearch.Index {
idxMapping, err := bleve.NewMapping()
if err != nil {
t.Fatalf("NewMapping: %v", err)
}
Expect(err).ToNot(HaveOccurred())
idx, err := bleveSearch.NewMemOnly(idxMapping)
if err != nil {
t.Fatalf("NewMemOnly: %v", err)
}
Expect(err).ToNot(HaveOccurred())
r := search.Resource{
ID: "x",
Document: content.Document{
@@ -38,141 +33,95 @@ func geoFixture(t *testing.T, lon, lat, alt float64) bleveSearch.Index {
},
}
doc, err := mapping.PrepareForIndex(r, r.SearchFieldOverrides())
if err != nil {
t.Fatalf("PrepareForIndex: %v", err)
}
if err := idx.Index(r.ID, doc); err != nil {
t.Fatalf("Index: %v", err)
}
Expect(err).ToNot(HaveOccurred())
Expect(idx.Index(r.ID, doc)).To(Succeed())
return idx
}
// TestLocationAltitudeRoundTrip proves that every subfield of Location
// (including altitude) ends up in hit.Fields when a Resource is indexed
// through the full bleve pipeline. This is the invariant the Move /
// Delete / Restore round-trip depends on.
func TestLocationAltitudeRoundTrip(t *testing.T) {
idx := geoFixture(t, 11.103870357204285, 49.48675890884328, 1047.7)
var _ = Describe("Location geo queries", func() {
// Every Location subfield (including altitude) must end up in hit.Fields
// when a Resource is indexed through the full bleve pipeline. This is the
// invariant the Move / Delete / Restore round-trip depends on.
It("round-trips every Location subfield into hit.Fields", func() {
idx := geoFixture(11.103870357204285, 49.48675890884328, 1047.7)
req := bleveSearch.NewSearchRequest(bleveSearch.NewMatchAllQuery())
req.Fields = []string{"*"}
res, err := idx.Search(req)
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) == 0 {
t.Fatal("no hits")
}
keys := make([]string, 0, len(res.Hits[0].Fields))
for k := range res.Hits[0].Fields {
keys = append(keys, k)
}
sort.Strings(keys)
t.Logf("hit.Fields keys: %v", keys)
req := bleveSearch.NewSearchRequest(bleveSearch.NewMatchAllQuery())
req.Fields = []string{"*"}
res, err := idx.Search(req)
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).ToNot(BeEmpty())
for _, k := range []string{"location.longitude", "location.latitude", "location.altitude"} {
if _, ok := res.Hits[0].Fields[k]; !ok {
t.Errorf("missing %q in hit.Fields (got %v)", k, keys)
for _, k := range []string{"location.longitude", "location.latitude", "location.altitude"} {
Expect(res.Hits[0].Fields).To(HaveKey(k))
}
}
}
})
func TestLocationLatitudeRangeQueryMatches(t *testing.T) {
idx := geoFixture(t, 11.1, 49.48, 1000)
It("matches a latitude numeric range and misses outside it", func() {
idx := geoFixture(11.1, 49.48, 1000)
// numeric range on the sub-field
min, max := 49.0, 50.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, &max, &incl, &incl)
q.SetField("location.latitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) != 1 {
t.Fatalf("latitude range: got %d hits, want 1", len(res.Hits))
}
min, max := 49.0, 50.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, &max, &incl, &incl)
q.SetField("location.latitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(HaveLen(1))
// same range excluding the indexed latitude => no match
lowMin, lowMax := 0.0, 10.0
q2 := query.NewNumericRangeInclusiveQuery(&lowMin, &lowMax, &incl, &incl)
q2.SetField("location.latitude")
res, err = idx.Search(bleveSearch.NewSearchRequest(q2))
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) != 0 {
t.Errorf("latitude range outside value: got %d hits, want 0", len(res.Hits))
}
}
lowMin, lowMax := 0.0, 10.0
q2 := query.NewNumericRangeInclusiveQuery(&lowMin, &lowMax, &incl, &incl)
q2.SetField("location.latitude")
res, err = idx.Search(bleveSearch.NewSearchRequest(q2))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(BeEmpty())
})
func TestLocationLongitudeRangeQueryMatches(t *testing.T) {
idx := geoFixture(t, 11.1, 49.48, 1000)
It("matches a longitude numeric range", func() {
idx := geoFixture(11.1, 49.48, 1000)
min, max := 11.0, 12.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, &max, &incl, &incl)
q.SetField("location.longitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) != 1 {
t.Fatalf("longitude range: got %d hits, want 1", len(res.Hits))
}
}
min, max := 11.0, 12.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, &max, &incl, &incl)
q.SetField("location.longitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(HaveLen(1))
})
func TestLocationAltitudeRangeQueryMatches(t *testing.T) {
idx := geoFixture(t, 11.1, 49.48, 1047.7)
It("matches an altitude lower-bound range and misses above it", func() {
idx := geoFixture(11.1, 49.48, 1047.7)
min := 1000.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, nil, &incl, nil)
q.SetField("location.altitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) != 1 {
t.Fatalf("altitude >= 1000: got %d hits, want 1", len(res.Hits))
}
min := 1000.0
incl := true
q := query.NewNumericRangeInclusiveQuery(&min, nil, &incl, nil)
q.SetField("location.altitude")
res, err := idx.Search(bleveSearch.NewSearchRequest(q))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(HaveLen(1))
// altitude floor above the indexed value => no hits
highMin := 2000.0
q2 := query.NewNumericRangeInclusiveQuery(&highMin, nil, &incl, nil)
q2.SetField("location.altitude")
res, err = idx.Search(bleveSearch.NewSearchRequest(q2))
if err != nil {
t.Fatalf("Search: %v", err)
}
if len(res.Hits) != 0 {
t.Errorf("altitude >= 2000 against 1047.7: got %d hits, want 0", len(res.Hits))
}
}
highMin := 2000.0
q2 := query.NewNumericRangeInclusiveQuery(&highMin, nil, &incl, nil)
q2.SetField("location.altitude")
res, err = idx.Search(bleveSearch.NewSearchRequest(q2))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(BeEmpty())
})
func TestLocationGeoDistanceQueryMatches(t *testing.T) {
// Nuremberg-ish coordinates.
idx := geoFixture(t, 11.103870357204285, 49.48675890884328, 1047.7)
It("matches a geo-distance query near the point and misses far away", func() {
// Nuremberg-ish coordinates.
idx := geoFixture(11.103870357204285, 49.48675890884328, 1047.7)
// 10 km radius around the indexed point should match.
near := query.NewGeoDistanceQuery(11.103870357204285, 49.48675890884328, "10km")
near.SetField("location" + mapping.GeopointSuffix)
res, err := idx.Search(bleveSearch.NewSearchRequest(near))
if err != nil {
t.Fatalf("Search (near): %v", err)
}
if len(res.Hits) != 1 {
t.Fatalf("geo distance near: got %d hits, want 1", len(res.Hits))
}
// 10 km radius around the indexed point should match.
near := query.NewGeoDistanceQuery(11.103870357204285, 49.48675890884328, "10km")
near.SetField("location" + mapping.GeopointSuffix)
res, err := idx.Search(bleveSearch.NewSearchRequest(near))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(HaveLen(1))
// Far away (Berlin, ~400 km) with a 10 km radius should miss.
far := query.NewGeoDistanceQuery(13.404954, 52.520008, "10km")
far.SetField("location" + mapping.GeopointSuffix)
res, err = idx.Search(bleveSearch.NewSearchRequest(far))
if err != nil {
t.Fatalf("Search (far): %v", err)
}
if len(res.Hits) != 0 {
t.Errorf("geo distance far: got %d hits, want 0", len(res.Hits))
}
}
// Far away (Berlin, ~400 km) with a 10 km radius should miss.
far := query.NewGeoDistanceQuery(13.404954, 52.520008, "10km")
far.SetField("location" + mapping.GeopointSuffix)
res, err = idx.Search(bleveSearch.NewSearchRequest(far))
Expect(err).ToNot(HaveOccurred())
Expect(res.Hits).To(BeEmpty())
})
})

View File

@@ -1,10 +1,10 @@
package bleve_test
import (
"testing"
bleveSearch "github.com/blevesearch/bleve/v2"
bquery "github.com/blevesearch/bleve/v2/search/query"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opencloud-eu/opencloud/services/search/pkg/bleve"
"github.com/opencloud-eu/opencloud/services/search/pkg/content"
@@ -14,35 +14,24 @@ import (
// Mtime is typed as a date, so range queries are chronological, not a
// lexicographic keyword compare.
func TestMtimeDateRange(t *testing.T) {
m, err := bleve.NewMapping()
if err != nil {
t.Fatal(err)
}
idx, err := bleveSearch.NewMemOnly(m)
if err != nil {
t.Fatal(err)
}
r := search.Resource{ID: "x", Document: content.Document{Name: "f", Mtime: "2026-03-15T12:00:00.123456789Z"}}
doc, err := mapping.PrepareForIndex(r, r.SearchFieldOverrides())
if err != nil {
t.Fatal(err)
}
if err := idx.Index(r.ID, doc); err != nil {
t.Fatal(err)
}
var _ = Describe("Mtime date range", func() {
It("compares chronologically, not lexicographically", func() {
m, err := bleve.NewMapping()
Expect(err).ToNot(HaveOccurred())
idx, err := bleveSearch.NewMemOnly(m)
Expect(err).ToNot(HaveOccurred())
hits := func(qs string) uint64 {
res, err := idx.Search(bleveSearch.NewSearchRequest(bquery.NewQueryStringQuery(qs)))
if err != nil {
t.Fatalf("%s: %v", qs, err)
r := search.Resource{ID: "x", Document: content.Document{Name: "f", Mtime: "2026-03-15T12:00:00.123456789Z"}}
doc, err := mapping.PrepareForIndex(r, r.SearchFieldOverrides())
Expect(err).ToNot(HaveOccurred())
Expect(idx.Index(r.ID, doc)).To(Succeed())
hits := func(qs string) uint64 {
res, err := idx.Search(bleveSearch.NewSearchRequest(bquery.NewQueryStringQuery(qs)))
Expect(err).ToNot(HaveOccurred(), qs)
return res.Total
}
return res.Total
}
if got := hits(`Mtime:>"2026-01-01T00:00:00Z"`); got != 1 {
t.Errorf("in-range: got %d hits, want 1", got)
}
if got := hits(`Mtime:>"2026-06-01T00:00:00Z"`); got != 0 {
t.Errorf("out-of-range: got %d hits, want 0", got)
}
}
Expect(hits(`Mtime:>"2026-01-01T00:00:00Z"`)).To(Equal(uint64(1)), "in-range")
Expect(hits(`Mtime:>"2026-06-01T00:00:00Z"`)).To(Equal(uint64(0)), "out-of-range")
})
})