Files
summercms/modules/beachcomber/searchpage_test.go
Jakub Zych 58e6324da8 feat(14-01): beachcomber DropIndex and EnsureIndex for reindex tooling
- optional IndexDropper reports whether a dropped index existed; DropIndex falls back to Flush
- optional IndexEnsurer creates an empty index from its schema; EnsureIndex is a no-op otherwise
- typesense implements both (404 is already absent; ensure reuses collection creation)
2026-10-03 20:01:36 +02:00

148 lines
4.4 KiB
Go

package beachcomber
import (
"context"
"errors"
"reflect"
"testing"
)
// idsOnlyEngine implements Engine without PageSearcher.
type idsOnlyEngine struct {
ids []string
err error
got Query
}
func (*idsOnlyEngine) Name() string { return "ids-only" }
func (*idsOnlyEngine) Configured() bool { return true }
func (*idsOnlyEngine) Upsert(context.Context, string, map[string]any, []map[string]any) error {
return nil
}
func (*idsOnlyEngine) Delete(context.Context, string, []string) error { return nil }
func (*idsOnlyEngine) Flush(context.Context, string) error { return nil }
func (e *idsOnlyEngine) SearchIDs(_ context.Context, _ string, q Query) ([]string, error) {
e.got = q
return e.ids, e.err
}
// pagedEngine implements PageSearcher.
type pagedEngine struct {
idsOnlyEngine
found int
}
func (e *pagedEngine) SearchPage(_ context.Context, _ string, q Query) (SearchResult, error) {
e.got = q
return SearchResult{IDs: e.ids, Found: e.found}, nil
}
func TestSearchPageFallsBackToSearchIDs(t *testing.T) {
e := &idsOnlyEngine{ids: []string{"3", "1"}}
q := Query{Q: "blue", QueryBy: []string{"name", "notes"}, QueryByWeights: []int{10, 1}, PerPage: 20}
res, err := SearchPage(context.Background(), e, "posts", q)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(res, SearchResult{IDs: []string{"3", "1"}, Found: 2}) {
t.Fatalf("res = %+v", res)
}
if !reflect.DeepEqual(e.got, q) {
t.Fatalf("query = %+v", e.got)
}
e.err = errors.New("down")
if _, err := SearchPage(context.Background(), e, "posts", q); err == nil {
t.Fatal("engine error must surface")
}
}
func TestSearchPageUsesPageSearcher(t *testing.T) {
e := &pagedEngine{idsOnlyEngine: idsOnlyEngine{ids: []string{"7"}}, found: 1234}
res, err := SearchPage(context.Background(), e, "posts", Query{Q: "x"})
if err != nil {
t.Fatal(err)
}
if res.Found != 1234 || !reflect.DeepEqual(res.IDs, []string{"7"}) {
t.Fatalf("res = %+v", res)
}
}
func TestSearchPageNullEngine(t *testing.T) {
var e Engine = nullEngine{}
if _, ok := e.(PageSearcher); !ok {
t.Fatal("the null engine implements PageSearcher")
}
res, err := SearchPage(context.Background(), e, "posts", Query{})
if err != nil || res.Found != 0 || res.IDs == nil || len(res.IDs) != 0 {
t.Fatalf("res = %+v err = %v", res, err)
}
}
// flushEngine records Flush calls and can fail them.
type flushEngine struct {
idsOnlyEngine
flushed []string
err error
}
func (e *flushEngine) Flush(_ context.Context, index string) error {
e.flushed = append(e.flushed, index)
return e.err
}
// dropperEngine implements IndexDropper and IndexEnsurer.
type dropperEngine struct {
flushEngine
existing map[string]bool
ensured []string
}
func (e *dropperEngine) DropIndex(_ context.Context, index string) (bool, error) {
existed := e.existing[index]
delete(e.existing, index)
return existed, nil
}
func (e *dropperEngine) EnsureIndex(_ context.Context, index string, _ map[string]any) error {
e.ensured = append(e.ensured, index)
e.existing[index] = true
return nil
}
func TestDropIndex(t *testing.T) {
ctx := context.Background()
fallback := &flushEngine{}
existed, err := DropIndex(ctx, fallback, "albums")
if err != nil || !existed || !reflect.DeepEqual(fallback.flushed, []string{"albums"}) {
t.Fatalf("fallback: existed=%v err=%v flushed=%v", existed, err, fallback.flushed)
}
boom := errors.New("boom")
failing := &flushEngine{err: boom}
if existed, err := DropIndex(ctx, failing, "albums"); !errors.Is(err, boom) || existed {
t.Fatalf("fallback error: existed=%v err=%v", existed, err)
}
d := &dropperEngine{existing: map[string]bool{"albums": true}}
if existed, err := DropIndex(ctx, d, "albums"); err != nil || !existed {
t.Fatalf("dropper first: existed=%v err=%v", existed, err)
}
if existed, err := DropIndex(ctx, d, "albums"); err != nil || existed {
t.Fatalf("dropper second: existed=%v err=%v, want already absent", existed, err)
}
if len(d.flushed) != 0 {
t.Fatalf("dropper path must not call Flush: %v", d.flushed)
}
if err := EnsureIndex(ctx, d, "albums", map[string]any{"fields": []any{}}); err != nil || !reflect.DeepEqual(d.ensured, []string{"albums"}) {
t.Fatalf("EnsureIndex: err=%v ensured=%v", err, d.ensured)
}
if err := EnsureIndex(ctx, fallback, "albums", nil); err != nil {
t.Fatalf("EnsureIndex without IndexEnsurer must be a no-op: %v", err)
}
if err := EnsureIndex(ctx, nullEngine{}, "albums", nil); err != nil {
t.Fatalf("null engine: %v", err)
}
}