milergeo added
This commit is contained in:
120
internal/milergeo/milergeo_test.go
Normal file
120
internal/milergeo/milergeo_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package milergeo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// fakeRedis answers the three geo commands the way a given Redis would.
|
||||
type fakeRedis struct {
|
||||
searchErr error // GEOSEARCH result; nil = supported
|
||||
radiusErr error
|
||||
addErr error
|
||||
locs []redis.GeoLocation
|
||||
searchHits int
|
||||
radiusHits int
|
||||
}
|
||||
|
||||
func (f *fakeRedis) GeoSearchLocation(ctx context.Context, _ string, q *redis.GeoSearchLocationQuery) *redis.GeoSearchLocationCmd {
|
||||
f.searchHits++
|
||||
cmd := redis.NewGeoSearchLocationCmd(ctx, q)
|
||||
if f.searchErr != nil {
|
||||
cmd.SetErr(f.searchErr)
|
||||
} else {
|
||||
cmd.SetVal(f.locs)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (f *fakeRedis) GeoRadius(_ context.Context, _ string, _, _ float64, _ *redis.GeoRadiusQuery) *redis.GeoLocationCmd {
|
||||
f.radiusHits++
|
||||
return redis.NewGeoLocationCmdResult(f.locs, f.radiusErr)
|
||||
}
|
||||
|
||||
func (f *fakeRedis) GeoAdd(ctx context.Context, _ string, _ ...*redis.GeoLocation) *redis.IntCmd {
|
||||
cmd := redis.NewIntCmd(ctx)
|
||||
if f.addErr != nil {
|
||||
cmd.SetErr(f.addErr)
|
||||
} else {
|
||||
cmd.SetVal(1)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
var nearby = []redis.GeoLocation{{Name: "38", Dist: 0.03}, {Name: "23", Dist: 0.4}}
|
||||
|
||||
func TestSearchUsesGeosearchOnRedis62(t *testing.T) {
|
||||
legacyOnly.Store(false)
|
||||
f := &fakeRedis{locs: nearby}
|
||||
got, err := Search(context.Background(), f, 11.0053, 76.9511, 10, 10)
|
||||
if err != nil || len(got) != 2 || got[0].Name != "38" {
|
||||
t.Fatalf("got %v, %v", got, err)
|
||||
}
|
||||
if f.searchHits != 1 || f.radiusHits != 0 {
|
||||
t.Fatalf("GEOSEARCH must be used when supported: search=%d radius=%d", f.searchHits, f.radiusHits)
|
||||
}
|
||||
}
|
||||
|
||||
// The production failure: Redis < 6.2 has no GEOSEARCH, so every order found
|
||||
// "no riders". The fallback must find them, and stop retrying GEOSEARCH.
|
||||
func TestSearchFallsBackOnOldRedis(t *testing.T) {
|
||||
legacyOnly.Store(false)
|
||||
t.Cleanup(func() { legacyOnly.Store(false) })
|
||||
f := &fakeRedis{searchErr: errors.New("ERR unknown command 'GEOSEARCH', with args beginning with: 'milers:locations'"), locs: nearby}
|
||||
for i := 0; i < 3; i++ {
|
||||
got, err := Search(context.Background(), f, 11.0053, 76.9511, 10, 10)
|
||||
if err != nil || len(got) != 2 {
|
||||
t.Fatalf("call %d: got %v, %v", i, got, err)
|
||||
}
|
||||
}
|
||||
if f.searchHits != 1 || f.radiusHits != 3 {
|
||||
t.Fatalf("after the first rejection only GEORADIUS should run: search=%d radius=%d", f.searchHits, f.radiusHits)
|
||||
}
|
||||
if p := Probe(context.Background(), f); !strings.Contains(p, "GEORADIUS fallback") {
|
||||
t.Fatalf("probe = %q", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Any other failure must surface, not read as an empty street.
|
||||
func TestSearchReturnsOtherErrors(t *testing.T) {
|
||||
legacyOnly.Store(false)
|
||||
f := &fakeRedis{searchErr: errors.New("WRONGTYPE Operation against a key holding the wrong kind of value")}
|
||||
if _, err := Search(context.Background(), f, 11, 76, 10, 10); err == nil || !strings.Contains(err.Error(), "WRONGTYPE") {
|
||||
t.Fatalf("want the WRONGTYPE error, got %v", err)
|
||||
}
|
||||
if f.radiusHits != 0 || legacyOnly.Load() {
|
||||
t.Fatal("a non-'unknown command' error must not switch to the fallback")
|
||||
}
|
||||
if p := Probe(context.Background(), f); !strings.HasPrefix(p, "error: ") {
|
||||
t.Fatalf("probe = %q", p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexReportsWriteFailure(t *testing.T) {
|
||||
if err := Index(context.Background(), &fakeRedis{}, 38, 11.0, 76.9); err != nil {
|
||||
t.Fatalf("ok write: %v", err)
|
||||
}
|
||||
err := Index(context.Background(), &fakeRedis{addErr: errors.New("WRONGTYPE Operation")}, 38, 11.0, 76.9)
|
||||
if err == nil || !strings.Contains(err.Error(), "WRONGTYPE") {
|
||||
t.Fatalf("want the write error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// db.Rdb is a *redis.Client; before InitRedis it is a nil pointer, which in
|
||||
// an interface is not == nil. It must be refused, not dereferenced.
|
||||
func TestNilClientIsRefused(t *testing.T) {
|
||||
var rdb *redis.Client
|
||||
if _, err := Search(context.Background(), rdb, 11, 76, 10, 10); err == nil {
|
||||
t.Fatal("nil client must be an error")
|
||||
}
|
||||
if err := Index(context.Background(), rdb, 1, 11, 76); err == nil {
|
||||
t.Fatal("nil client must be an error")
|
||||
}
|
||||
if p := Probe(context.Background(), rdb); !strings.HasPrefix(p, "error") {
|
||||
t.Fatalf("probe = %q", p)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user