This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package mongoext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
)
|
||||
|
||||
func TestFixTextSearchPipelineEmpty(t *testing.T) {
|
||||
pipeline := mongo.Pipeline{}
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
tst.AssertEqual(t, len(result), 0)
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineNoTextSearch(t *testing.T) {
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$match", Value: bson.M{"foo": "bar"}}},
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 2)
|
||||
tst.AssertEqual(t, result[0][0].Key, "$match")
|
||||
tst.AssertEqual(t, result[1][0].Key, "$sort")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineMovesTextSearchToFront(t *testing.T) {
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$match", Value: bson.M{"foo": "bar"}}},
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"$text": bson.M{"$search": "hello world"}}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 3)
|
||||
|
||||
// $text/$search should be at front
|
||||
first := result[0]
|
||||
matchVal, ok := first[0].Value.(bson.M)
|
||||
tst.AssertTrue(t, ok)
|
||||
textVal, ok := matchVal["$text"].(bson.M)
|
||||
tst.AssertTrue(t, ok)
|
||||
tst.AssertEqual(t, textVal["$search"].(string), "hello world")
|
||||
|
||||
// other entries should preserve order
|
||||
tst.AssertEqual(t, result[1][0].Key, "$match")
|
||||
tst.AssertEqual(t, result[2][0].Key, "$sort")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineMultipleTextSearches(t *testing.T) {
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"$text": bson.M{"$search": "first"}}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"foo": "bar"}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"$text": bson.M{"$search": "second"}}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 4)
|
||||
|
||||
// Last seen text-search should be prepended last, ending up at front.
|
||||
first := result[0][0].Value.(bson.M)
|
||||
tst.AssertEqual(t, first["$text"].(bson.M)["$search"].(string), "second")
|
||||
|
||||
second := result[1][0].Value.(bson.M)
|
||||
tst.AssertEqual(t, second["$text"].(bson.M)["$search"].(string), "first")
|
||||
|
||||
tst.AssertEqual(t, result[2][0].Key, "$sort")
|
||||
tst.AssertEqual(t, result[3][0].Key, "$match")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineMatchWithoutText(t *testing.T) {
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"name": "alice"}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 2)
|
||||
tst.AssertEqual(t, result[0][0].Key, "$sort")
|
||||
tst.AssertEqual(t, result[1][0].Key, "$match")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineTextWithoutSearch(t *testing.T) {
|
||||
// $text present but without $search key — should NOT be moved
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"$text": bson.M{"$language": "en"}}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 2)
|
||||
tst.AssertEqual(t, result[0][0].Key, "$sort")
|
||||
tst.AssertEqual(t, result[1][0].Key, "$match")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelineMatchValueWrongType(t *testing.T) {
|
||||
// $match with non-bson.M value — function should keep entry in place
|
||||
pipeline := mongo.Pipeline{
|
||||
bson.D{{Key: "$match", Value: "not a map"}},
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
}
|
||||
|
||||
result := FixTextSearchPipeline(pipeline)
|
||||
|
||||
tst.AssertEqual(t, len(result), 2)
|
||||
tst.AssertEqual(t, result[0][0].Key, "$match")
|
||||
tst.AssertEqual(t, result[1][0].Key, "$sort")
|
||||
}
|
||||
|
||||
func TestFixTextSearchPipelinePreservesOriginal(t *testing.T) {
|
||||
original := mongo.Pipeline{
|
||||
bson.D{{Key: "$sort", Value: bson.M{"baz": 1}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"$text": bson.M{"$search": "x"}}}},
|
||||
}
|
||||
originalLen := len(original)
|
||||
originalFirstKey := original[0][0].Key
|
||||
|
||||
_ = FixTextSearchPipeline(original)
|
||||
|
||||
tst.AssertEqual(t, len(original), originalLen)
|
||||
tst.AssertEqual(t, original[0][0].Key, originalFirstKey)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package mongoext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
)
|
||||
|
||||
func TestProjectionFromStructSimple(t *testing.T) {
|
||||
type model struct {
|
||||
ID string `bson:"_id"`
|
||||
Name string `bson:"name"`
|
||||
Age int `bson:"age"`
|
||||
}
|
||||
|
||||
res := ProjectionFromStruct(model{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 3)
|
||||
tst.AssertEqual(t, res["_id"], 1)
|
||||
tst.AssertEqual(t, res["name"], 1)
|
||||
tst.AssertEqual(t, res["age"], 1)
|
||||
}
|
||||
|
||||
func TestProjectionFromStructIgnoresUntagged(t *testing.T) {
|
||||
type model struct {
|
||||
Tagged string `bson:"tagged"`
|
||||
Untagged string
|
||||
Other int `json:"other"`
|
||||
}
|
||||
|
||||
res := ProjectionFromStruct(model{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 1)
|
||||
tst.AssertEqual(t, res["tagged"], 1)
|
||||
|
||||
if _, ok := res["Untagged"]; ok {
|
||||
t.Errorf("untagged field should not be in projection")
|
||||
}
|
||||
if _, ok := res["Other"]; ok {
|
||||
t.Errorf("non-bson-tagged field should not be in projection")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectionFromStructWithOptions(t *testing.T) {
|
||||
type model struct {
|
||||
ID string `bson:"_id,omitempty"`
|
||||
Name string `bson:"name,omitempty"`
|
||||
Slug string `bson:"slug,inline"`
|
||||
}
|
||||
|
||||
res := ProjectionFromStruct(model{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 3)
|
||||
tst.AssertEqual(t, res["_id"], 1)
|
||||
tst.AssertEqual(t, res["name"], 1)
|
||||
tst.AssertEqual(t, res["slug"], 1)
|
||||
}
|
||||
|
||||
func TestProjectionFromStructEmpty(t *testing.T) {
|
||||
type empty struct{}
|
||||
|
||||
res := ProjectionFromStruct(empty{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 0)
|
||||
}
|
||||
|
||||
func TestProjectionFromStructPointerValues(t *testing.T) {
|
||||
type model struct {
|
||||
Name *string `bson:"name"`
|
||||
Tags []int `bson:"tags"`
|
||||
}
|
||||
|
||||
res := ProjectionFromStruct(model{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 2)
|
||||
tst.AssertEqual(t, res["name"], 1)
|
||||
tst.AssertEqual(t, res["tags"], 1)
|
||||
}
|
||||
|
||||
func TestProjectionFromStructAllSkipped(t *testing.T) {
|
||||
type model struct {
|
||||
A string
|
||||
B int
|
||||
C bool
|
||||
}
|
||||
|
||||
res := ProjectionFromStruct(model{})
|
||||
|
||||
tst.AssertEqual(t, len(res), 0)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package mongoext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"git.blackforestbytes.com/BlackForestBytes/goext/tst"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
func TestCreateGoExtBsonRegistryNotNil(t *testing.T) {
|
||||
reg := CreateGoExtBsonRegistry()
|
||||
if reg == nil {
|
||||
t.Fatal("registry should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateGoExtBsonRegistryEmbeddedDocumentDecodesAsBsonM(t *testing.T) {
|
||||
reg := CreateGoExtBsonRegistry()
|
||||
|
||||
doc := bson.M{
|
||||
"name": "alice",
|
||||
"nested": bson.M{
|
||||
"key": "value",
|
||||
"num": int32(42),
|
||||
},
|
||||
}
|
||||
|
||||
raw, err := bson.Marshal(doc)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(raw)))
|
||||
dec.SetRegistry(reg)
|
||||
|
||||
var decoded map[string]any
|
||||
err = dec.Decode(&decoded)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
nested, ok := decoded["nested"].(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("expected nested to be bson.M, got %T", decoded["nested"])
|
||||
}
|
||||
|
||||
tst.AssertEqual(t, nested["key"].(string), "value")
|
||||
}
|
||||
|
||||
func TestCreateGoExtBsonRegistryStructFieldOfTypeAny(t *testing.T) {
|
||||
reg := CreateGoExtBsonRegistry()
|
||||
|
||||
type wrapper struct {
|
||||
Payload any `bson:"payload"`
|
||||
}
|
||||
|
||||
source := wrapper{Payload: bson.M{"x": "y"}}
|
||||
raw, err := bson.Marshal(source)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(raw)))
|
||||
dec.SetRegistry(reg)
|
||||
|
||||
var decoded wrapper
|
||||
err = dec.Decode(&decoded)
|
||||
tst.AssertNoErr(t, err)
|
||||
|
||||
payload, ok := decoded.Payload.(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("expected Payload to be bson.M, got %T", decoded.Payload)
|
||||
}
|
||||
tst.AssertEqual(t, payload["x"].(string), "y")
|
||||
}
|
||||
|
||||
func TestCreateGoExtBsonRegistryReturnsIndependentInstances(t *testing.T) {
|
||||
r1 := CreateGoExtBsonRegistry()
|
||||
r2 := CreateGoExtBsonRegistry()
|
||||
|
||||
if r1 == r2 {
|
||||
t.Error("expected each call to return a new registry instance")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user