1
0
Fork 0
golang-github-pocketbase-po.../tools/search/token_functions_test.go
Daniel Baumann e28c88ef14
Adding upstream version 0.28.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 10:57:38 +02:00

277 lines
7.1 KiB
Go

package search
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/ganigeorgiev/fexpr"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/tools/security"
)
func TestTokenFunctionsGeoDistance(t *testing.T) {
t.Parallel()
testDB, err := createTestDB()
if err != nil {
t.Fatal(err)
}
defer testDB.Close()
fn, ok := TokenFunctions["geoDistance"]
if !ok {
t.Error("Expected geoDistance token function to be registered.")
}
baseTokenResolver := func(t fexpr.Token) (*ResolverResult, error) {
placeholder := "t" + security.PseudorandomString(5)
return &ResolverResult{Identifier: "{:" + placeholder + "}", Params: map[string]any{placeholder: t.Literal}}, nil
}
scenarios := []struct {
name string
args []fexpr.Token
resolver func(t fexpr.Token) (*ResolverResult, error)
result *ResolverResult
expectErr bool
}{
{
"no args",
nil,
baseTokenResolver,
nil,
true,
},
{
"< 4 args",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenNumber},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
},
baseTokenResolver,
nil,
true,
},
{
"> 4 args",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenNumber},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
{Literal: "4", Type: fexpr.TokenNumber},
{Literal: "5", Type: fexpr.TokenNumber},
},
baseTokenResolver,
nil,
true,
},
{
"unsupported function argument",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenFunction},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
{Literal: "4", Type: fexpr.TokenNumber},
},
baseTokenResolver,
nil,
true,
},
{
"unsupported text argument",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenText},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
{Literal: "4", Type: fexpr.TokenNumber},
},
baseTokenResolver,
nil,
true,
},
{
"4 valid arguments but with resolver error",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenNumber},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
{Literal: "4", Type: fexpr.TokenNumber},
},
func(t fexpr.Token) (*ResolverResult, error) {
return nil, errors.New("test")
},
nil,
true,
},
{
"4 valid arguments",
[]fexpr.Token{
{Literal: "1", Type: fexpr.TokenNumber},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "3", Type: fexpr.TokenNumber},
{Literal: "4", Type: fexpr.TokenNumber},
},
baseTokenResolver,
&ResolverResult{
NoCoalesce: true,
Identifier: `(6371 * acos(cos(radians({:latA})) * cos(radians({:latB})) * cos(radians({:lonB}) - radians({:lonA})) + sin(radians({:latA})) * sin(radians({:latB}))))`,
Params: map[string]any{
"lonA": 1,
"latA": 2,
"lonB": 3,
"latB": 4,
},
},
false,
},
{
"mixed arguments",
[]fexpr.Token{
{Literal: "null", Type: fexpr.TokenIdentifier},
{Literal: "2", Type: fexpr.TokenNumber},
{Literal: "false", Type: fexpr.TokenIdentifier},
{Literal: "4", Type: fexpr.TokenNumber},
},
baseTokenResolver,
&ResolverResult{
NoCoalesce: true,
Identifier: `(6371 * acos(cos(radians({:latA})) * cos(radians({:latB})) * cos(radians({:lonB}) - radians({:lonA})) + sin(radians({:latA})) * sin(radians({:latB}))))`,
Params: map[string]any{
"lonA": "null",
"latA": 2,
"lonB": false,
"latB": 4,
},
},
false,
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
result, err := fn(s.resolver, s.args...)
hasErr := err != nil
if hasErr != s.expectErr {
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectErr, hasErr, err)
}
testCompareResults(t, s.result, result)
})
}
}
func TestTokenFunctionsGeoDistanceExec(t *testing.T) {
t.Parallel()
testDB, err := createTestDB()
if err != nil {
t.Fatal(err)
}
defer testDB.Close()
fn, ok := TokenFunctions["geoDistance"]
if !ok {
t.Error("Expected geoDistance token function to be registered.")
}
result, err := fn(
func(t fexpr.Token) (*ResolverResult, error) {
placeholder := "t" + security.PseudorandomString(5)
return &ResolverResult{Identifier: "{:" + placeholder + "}", Params: map[string]any{placeholder: t.Literal}}, nil
},
fexpr.Token{Literal: "23.23033854945808", Type: fexpr.TokenNumber},
fexpr.Token{Literal: "42.713146090563384", Type: fexpr.TokenNumber},
fexpr.Token{Literal: "23.44920680886216", Type: fexpr.TokenNumber},
fexpr.Token{Literal: "42.7078484153991", Type: fexpr.TokenNumber},
)
if err != nil {
t.Fatal(err)
}
column := []float64{}
err = testDB.NewQuery("select " + result.Identifier).Bind(result.Params).Column(&column)
if err != nil {
t.Fatal(err)
}
if len(column) != 1 {
t.Fatalf("Expected exactly 1 column value as result, got %v", column)
}
expected := "17.89"
distance := fmt.Sprintf("%.2f", column[0])
if distance != expected {
t.Fatalf("Expected distance value %s, got %s", expected, distance)
}
}
// -------------------------------------------------------------------
func testCompareResults(t *testing.T, a, b *ResolverResult) {
testDB, err := createTestDB()
if err != nil {
t.Fatal(err)
}
defer testDB.Close()
aIsNil := a == nil
bIsNil := b == nil
if aIsNil != bIsNil {
t.Fatalf("Expected aIsNil and bIsNil to be the same, got %v vs %v", aIsNil, bIsNil)
}
if aIsNil && bIsNil {
return
}
aHasAfterBuild := a.AfterBuild == nil
bHasAfterBuild := b.AfterBuild == nil
if aHasAfterBuild != bHasAfterBuild {
t.Fatalf("Expected aHasAfterBuild and bHasAfterBuild to be the same, got %v vs %v", aHasAfterBuild, bHasAfterBuild)
}
var aAfterBuild string
if a.AfterBuild != nil {
aAfterBuild = a.AfterBuild(dbx.NewExp("test")).Build(testDB.DB, a.Params)
}
var bAfterBuild string
if b.AfterBuild != nil {
bAfterBuild = b.AfterBuild(dbx.NewExp("test")).Build(testDB.DB, a.Params)
}
if aAfterBuild != bAfterBuild {
t.Fatalf("Expected bAfterBuild and bAfterBuild to be the same, got\n%s\nvs\n%s", aAfterBuild, bAfterBuild)
}
var aMultiMatchSubQuery string
if a.MultiMatchSubQuery != nil {
aMultiMatchSubQuery = a.MultiMatchSubQuery.Build(testDB.DB, a.Params)
}
var bMultiMatchSubQuery string
if b.MultiMatchSubQuery != nil {
bMultiMatchSubQuery = b.MultiMatchSubQuery.Build(testDB.DB, b.Params)
}
if aMultiMatchSubQuery != bMultiMatchSubQuery {
t.Fatalf("Expected bMultiMatchSubQuery and bMultiMatchSubQuery to be the same, got\n%s\nvs\n%s", aMultiMatchSubQuery, bMultiMatchSubQuery)
}
if a.NoCoalesce != b.NoCoalesce {
t.Fatalf("Expected NoCoalesce to match, got %v vs %v", a.NoCoalesce, b.NoCoalesce)
}
// loose placeholders replacement
var aResolved = a.Identifier
for k, v := range a.Params {
aResolved = strings.ReplaceAll(aResolved, "{:"+k+"}", fmt.Sprintf("%v", v))
}
var bResolved = b.Identifier
for k, v := range b.Params {
bResolved = strings.ReplaceAll(bResolved, "{:"+k+"}", fmt.Sprintf("%v", v))
}
if aResolved != bResolved {
t.Fatalf("Expected resolved identifiers to match, got\n%s\nvs\n%s", aResolved, bResolved)
}
}