From 22f46dd9d47695d7bbda8f13a8d6ce6b770f2176 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sun, 5 Jul 2026 16:56:10 +0200 Subject: [PATCH] test(search): convert bleve geo/mtime tests to ginkgo The package's engine suite is ginkgo; these new tests were plain. --- services/search/pkg/bleve/geo_verify_test.go | 217 +++++++------------ services/search/pkg/bleve/mtime_test.go | 53 ++--- 2 files changed, 104 insertions(+), 166 deletions(-) diff --git a/services/search/pkg/bleve/geo_verify_test.go b/services/search/pkg/bleve/geo_verify_test.go index 27cf0e7a01..2bfb0d6a69 100644 --- a/services/search/pkg/bleve/geo_verify_test.go +++ b/services/search/pkg/bleve/geo_verify_test.go @@ -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()) + }) +}) diff --git a/services/search/pkg/bleve/mtime_test.go b/services/search/pkg/bleve/mtime_test.go index d7475b0316..c29c83d9b8 100644 --- a/services/search/pkg/bleve/mtime_test.go +++ b/services/search/pkg/bleve/mtime_test.go @@ -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") + }) +})