Skip to content

Commit ae9b515

Browse files
Merge pull request #40 from elcengine/test/increase-coverage
fix: increased test coverage and improved filter query perf
2 parents e4baf34 + 2bc7c3a commit ae9b515

16 files changed

Lines changed: 190 additions & 87 deletions

core/cluster.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build nocover
2+
13
package elemental
24

35
import (
@@ -105,3 +107,8 @@ func (c ClusterOp[T]) Exec() any {
105107
}
106108
return **c.result
107109
}
110+
111+
// This feature is still experimental and not fully implemented.
112+
func (m Model[T]) UseCluster(connection *string) ClusterOp[T] {
113+
return Cluster(&m, connection)
114+
}

core/cluster_utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build nocover
2+
13
package elemental
24

35
import (

core/model.go

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package elemental
22

33
import (
44
"context"
5-
"github.com/elcengine/elemental/utils"
6-
"github.com/spf13/cast"
75
"reflect"
86
"strings"
97

8+
"github.com/elcengine/elemental/utils"
9+
"github.com/spf13/cast"
10+
1011
"github.com/gertd/go-pluralize"
1112
"github.com/samber/lo"
1213
"go.mongodb.org/mongo-driver/bson"
@@ -234,30 +235,27 @@ func (m Model[T]) Sort(args ...any) Model[T] {
234235
// Extends the query with a projection stage.
235236
// The projection stage is used to specify which fields to include or exclude from the results.
236237
func (m Model[T]) Select(fields ...any) Model[T] {
238+
inputType := reflect.TypeOf(fields[0]).Kind()
239+
if inputType == reflect.Map {
240+
m.pipeline = append(m.pipeline, bson.D{{Key: "$project", Value: fields[0]}})
241+
return m
242+
}
237243
var selection []string
238244
switch {
239-
case len(fields) == 1 && reflect.TypeOf(fields[0]).Kind() == reflect.String:
245+
case inputType == reflect.Slice:
246+
selection = fields[0].([]string)
247+
case len(fields) == 1 && inputType == reflect.String:
240248
selection = strings.FieldsFunc(fields[0].(string), func(r rune) bool {
241249
return r == ',' || r == ' '
242250
})
243251
case len(fields) > 1:
244-
selection = utils.CastSlice[string](fields)
245-
case reflect.TypeOf(fields[0]).Kind() == reflect.Slice:
246-
selection = fields[0].([]string)
252+
selection = cast.ToStringSlice(fields)
247253
}
248-
249-
switch {
250-
case len(selection) > 0:
251-
for _, field := range selection {
252-
if strings.HasPrefix(field, "-") {
253-
m = m.addToPipeline("$project", field[1:], 0)
254-
} else {
255-
m = m.addToPipeline("$project", field, 1)
256-
}
257-
}
258-
case reflect.TypeOf(fields[0]).Kind() == reflect.Map:
259-
for field, value := range utils.Cast[primitive.M](fields[0]) {
260-
m = m.addToPipeline("$project", field, value)
254+
for _, field := range selection {
255+
if strings.HasPrefix(field, "-") {
256+
m = m.addToPipeline("$project", field[1:], 0)
257+
} else {
258+
m = m.addToPipeline("$project", field, 1)
261259
}
262260
}
263261
return m
@@ -286,8 +284,3 @@ func (m Model[T]) Clone() Model[T] {
286284
deletedAtFieldName: m.deletedAtFieldName,
287285
}
288286
}
289-
290-
// This feature is still experimental and not fully implemented.
291-
func (m Model[T]) UseCluster(connection *string) ClusterOp[T] {
292-
return Cluster(&m, connection)
293-
}

core/model_query_delete.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,16 @@ func (m Model[T]) DeleteMany(query ...primitive.M) Model[T] {
121121
}
122122

123123
// Enables soft delete for the model.
124-
func (m Model[T]) EnableSoftDelete() Model[T] {
124+
func (m *Model[T]) EnableSoftDelete() {
125+
m.deletedAtFieldName = "deleted_at"
125126
m.softDeleteEnabled = true
126-
return m
127127
}
128128

129129
// Disables soft delete for the model.
130-
func (m Model[T]) DisableSoftDelete() Model[T] {
130+
func (m *Model[T]) DisableSoftDelete() {
131131
m.softDeleteEnabled = false
132-
return m
133132
}
134133

135134
func (m Model[T]) softDeletePayload() primitive.M {
136-
m.deletedAtFieldName = "deleted_at"
137135
return primitive.M{m.deletedAtFieldName: time.Now().Format(time.RFC3339)}
138136
}

plugins/filterquery/filterquery.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func Parse(qs string) Result {
3636
}
3737
pair := strings.Split(query, "=")
3838
key := pair[0]
39-
value := pair[1]
39+
value := strings.Join(pair[1:], "=")
4040
if strings.Contains(key, "filter") {
4141
if filterKey := extractFieldName(key); filterKey != "" {
4242
result.Filters[filterKey] = value

plugins/filterquery/util.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,20 @@ func replaceOperator(value string, operator string) string {
2525
}
2626

2727
func parseOperatorValue(value any, operator string) any {
28+
strVal := cast.ToString(value)
2829
if operator != "" {
29-
value = replaceOperator(cast.ToString(value), operator)
30+
strVal = replaceOperator(strVal, operator)
3031
}
31-
if regexp.MustCompile(`^[0-9]+$`).MatchString(cast.ToString(value)) {
32-
value = cast.ToFloat64(value)
33-
} else {
34-
time, err := cast.ToTimeE(value)
35-
switch {
36-
case err == nil:
37-
value = time
38-
case regexp.MustCompile(`^[0-9a-fA-F]{24}$`).MatchString(cast.ToString(value)):
39-
value, err = primitive.ObjectIDFromHex(cast.ToString(value))
40-
if err != nil {
41-
value = cast.ToString(value)
42-
}
43-
default:
44-
value = cast.ToString(value)
45-
}
32+
if f, err := cast.ToFloat64E(strVal); err == nil {
33+
return f
34+
}
35+
if oid, err := primitive.ObjectIDFromHex(strVal); err == nil {
36+
return oid
37+
}
38+
if t, err := cast.ToTimeE(strVal); err == nil {
39+
return t
4640
}
47-
return value
41+
return strVal
4842
}
4943

5044
func mapValue(value any) any {

tests/core_delete_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tests
22

33
import (
4+
"context"
45
"testing"
56

67
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -52,6 +53,18 @@ func TestCoreDelete(t *testing.T) {
5253
UserModel.DeleteByID(user.ID).Exec()
5354
So(UserModel.FindByID(user.ID).Exec(), ShouldBeNil)
5455
})
56+
Convey("Soft delete a user", func() {
57+
UserModel.EnableSoftDelete()
58+
defer UserModel.DisableSoftDelete()
59+
60+
user := UserModel.FindOne(primitive.M{"name": mocks.Vesemir.Name}).ExecPtr()
61+
UserModel.DeleteByID(user.ID).Exec()
62+
So(UserModel.FindByID(user.ID).Exec(), ShouldBeNil)
63+
64+
rawUser := map[string]any{}
65+
UserModel.Collection().FindOne(context.Background(), primitive.M{"_id": user.ID}).Decode(&rawUser)
66+
So(rawUser["deleted_at"], ShouldNotBeNil)
67+
})
5568
Convey("Delete all remaining users", func() {
5669
UserModel.DeleteMany().Exec()
5770
So(UserModel.Find().Exec(), ShouldBeEmpty)

tests/core_read_ops_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ func TestCoreReadOps(t *testing.T) {
6666
users := UserModel.Where("age").GreaterThanOrEquals(90).Where("age").LessThanOrEquals(110).ExecTT()
6767
So(len(users), ShouldEqual, 2)
6868
So(users[0].Name, ShouldEqual, mocks.Geralt.Name)
69+
70+
users = UserModel.Where("age").GreaterThan(89).Where("age").LessThan(111).ExecTT()
71+
So(len(users), ShouldEqual, 2)
72+
So(users[0].Name, ShouldEqual, mocks.Geralt.Name)
6973
})
7074
Convey("In conjuntion with between", func() {
7175
users := UserModel.Where("age").Between(90, 110).ExecTT()
@@ -132,6 +136,12 @@ func TestCoreReadOps(t *testing.T) {
132136
So(users[0].Name, ShouldEqual, mocks.Geralt.Name)
133137
So(users[1].Name, ShouldEqual, mocks.Imlerith.Name)
134138
})
139+
Convey("In conjuntion with has", func() {
140+
users := UserModel.Where("weapons").Has("Battle Axe").ExecTT()
141+
So(len(users), ShouldEqual, 2)
142+
So(users[0].Name, ShouldEqual, mocks.Geralt.Name)
143+
So(users[1].Name, ShouldEqual, mocks.Imlerith.Name)
144+
})
135145
})
136146
Convey(fmt.Sprintf("Find where weapon count is %d", len(mocks.Geralt.Weapons)), func() {
137147
users := UserModel.Where("weapons").Size(len(mocks.Geralt.Weapons)).ExecTT()

tests/core_read_select_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func TestCoreReadSelect(t *testing.T) {
6767
users := UserModel.Find().Select("name, -_id").Limit(limit).ExecTT()
6868
assert(users)
6969
})
70+
Convey("In conjunction with variadic arguments", func() {
71+
users := UserModel.Find().Select("name", "-_id").Limit(limit).ExecTT()
72+
assert(users)
73+
})
7074
})
7175
Convey(fmt.Sprintf("%d user names and ages", limit), func() {
7276
assert := func(users []User) {

tests/core_read_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ func TestCoreRead(t *testing.T) {
9595
So(userById, ShouldNotBeNil)
9696
So(userById.Name, ShouldEqual, mocks.Ciri.Name)
9797
})
98+
Convey("Find user by ID (Object ID pointer)", func() {
99+
userById := UserModel.FindByID(&user.ID).ExecPtr()
100+
So(userById, ShouldNotBeNil)
101+
So(userById.Name, ShouldEqual, mocks.Ciri.Name)
102+
})
98103
})
99104
Convey("Count users", func() {
100105
count := UserModel.CountDocuments().ExecInt()

0 commit comments

Comments
 (0)