mydb/adapter/mongo/mongo_test.go

755 lines
15 KiB
Go
Raw Normal View History

2023-09-18 15:15:42 +08:00
// Tests for the mongodb adapter.
package mongo
import (
"fmt"
"log"
"math/rand"
"strings"
"testing"
"time"
"git.hexq.cn/tiglog/mydb"
"git.hexq.cn/tiglog/mydb/internal/testsuite"
"github.com/stretchr/testify/suite"
"gopkg.in/mgo.v2/bson"
)
type artistType struct {
ID bson.ObjectId `bson:"_id,omitempty"`
Name string `bson:"name"`
}
// Structure for testing conversions and datatypes.
type testValuesStruct struct {
Uint uint `bson:"_uint"`
Uint8 uint8 `bson:"_uint8"`
Uint16 uint16 `bson:"_uint16"`
Uint32 uint32 `bson:"_uint32"`
Uint64 uint64 `bson:"_uint64"`
Int int `bson:"_int"`
Int8 int8 `bson:"_int8"`
Int16 int16 `bson:"_int16"`
Int32 int32 `bson:"_int32"`
Int64 int64 `bson:"_int64"`
Float32 float32 `bson:"_float32"`
Float64 float64 `bson:"_float64"`
Bool bool `bson:"_bool"`
String string `bson:"_string"`
Date time.Time `bson:"_date"`
DateN *time.Time `bson:"_nildate"`
DateP *time.Time `bson:"_ptrdate"`
Time time.Duration `bson:"_time"`
}
var testValues testValuesStruct
func init() {
t := time.Date(2012, 7, 28, 1, 2, 3, 0, time.Local)
testValues = testValuesStruct{
1, 1, 1, 1, 1,
-1, -1, -1, -1, -1,
1.337, 1.337,
true,
"Hello world!",
t,
nil,
&t,
time.Second * time.Duration(7331),
}
}
type AdapterTests struct {
testsuite.Suite
}
func (s *AdapterTests) SetupSuite() {
s.Helper = &Helper{}
}
func (s *AdapterTests) TestOpenWithWrongData() {
var err error
var rightSettings, wrongSettings ConnectionURL
// Attempt to open with safe settings.
rightSettings = ConnectionURL{
Database: settings.Database,
Host: settings.Host,
User: settings.User,
Password: settings.Password,
}
// Attempt to open an empty database.
_, err = Open(rightSettings)
s.NoError(err)
// Attempt to open with wrong password.
wrongSettings = ConnectionURL{
Database: settings.Database,
Host: settings.Host,
User: settings.User,
Password: "fail",
}
_, err = Open(wrongSettings)
s.Error(err)
// Attempt to open with wrong database.
wrongSettings = ConnectionURL{
Database: "fail",
Host: settings.Host,
User: settings.User,
Password: settings.Password,
}
_, err = Open(wrongSettings)
s.Error(err)
// Attempt to open with wrong username.
wrongSettings = ConnectionURL{
Database: settings.Database,
Host: settings.Host,
User: "fail",
Password: settings.Password,
}
_, err = Open(wrongSettings)
s.Error(err)
}
func (s *AdapterTests) TestTruncate() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a list of all collections in this database.
collections, err := sess.Collections()
s.NoError(err)
for _, col := range collections {
// The collection may ot may not exists.
if ok, _ := col.Exists(); ok {
// Truncating the structure, if exists.
err = col.Truncate()
s.NoError(err)
}
}
}
func (s *AdapterTests) TestInsert() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
_ = artist.Truncate()
// Inserting a map.
record, err := artist.Insert(map[string]string{
"name": "Ozzie",
})
s.NoError(err)
id := record.ID()
s.NotZero(record.ID())
_, ok := id.(bson.ObjectId)
s.True(ok)
s.True(id.(bson.ObjectId).Valid())
// Inserting a struct.
record, err = artist.Insert(struct {
Name string
}{
"Flea",
})
s.NoError(err)
id = record.ID()
s.NotZero(id)
_, ok = id.(bson.ObjectId)
s.True(ok)
s.True(id.(bson.ObjectId).Valid())
// Inserting a struct (using tags to specify the field name).
record, err = artist.Insert(struct {
ArtistName string `bson:"name"`
}{
"Slash",
})
s.NoError(err)
id = record.ID()
s.NotNil(id)
_, ok = id.(bson.ObjectId)
s.True(ok)
s.True(id.(bson.ObjectId).Valid())
// Inserting a pointer to a struct
record, err = artist.Insert(&struct {
ArtistName string `bson:"name"`
}{
"Metallica",
})
s.NoError(err)
id = record.ID()
s.NotNil(id)
_, ok = id.(bson.ObjectId)
s.True(ok)
s.True(id.(bson.ObjectId).Valid())
// Inserting a pointer to a map
record, err = artist.Insert(&map[string]string{
"name": "Freddie",
})
s.NoError(err)
s.NotZero(id)
_, ok = id.(bson.ObjectId)
s.True(ok)
id = record.ID()
s.NotNil(id)
s.True(id.(bson.ObjectId).Valid())
// Counting elements, must be exactly 6 elements.
total, err := artist.Find().Count()
s.NoError(err)
s.Equal(uint64(5), total)
}
func (s *AdapterTests) TestGetNonExistentRow_Issue426() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
defer sess.Close()
artist := sess.Collection("artist")
var one artistType
err = artist.Find(mydb.Cond{"name": "nothing"}).One(&one)
s.NotZero(err)
s.Equal(mydb.ErrNoMoreRows, err)
var all []artistType
err = artist.Find(mydb.Cond{"name": "nothing"}).All(&all)
s.Zero(err, "All should not return mgo.ErrNotFound")
s.Equal(0, len(all))
}
func (s *AdapterTests) TestResultCount() {
var err error
var res mydb.Result
// Opening database.
sess, err := Open(settings)
s.NoError(err)
defer sess.Close()
// We should close the database when it's no longer in use.
artist := sess.Collection("artist")
res = artist.Find()
// Counting all the matching rows.
total, err := res.Count()
s.NoError(err)
s.NotZero(total)
}
func (s *AdapterTests) TestGroup() {
var stats mydb.Collection
sess, err := Open(settings)
s.NoError(err)
type statsT struct {
Numeric int `db:"numeric" bson:"numeric"`
Value int `db:"value" bson:"value"`
}
defer sess.Close()
stats = sess.Collection("statsTest")
// Truncating table.
_ = stats.Truncate()
// Adding row append.
for i := 0; i < 1000; i++ {
numeric, value := rand.Intn(10), rand.Intn(100)
_, err = stats.Insert(statsT{numeric, value})
s.NoError(err)
}
// mydb.statsTest.group({key: {numeric: true}, initial: {sum: 0}, reduce: function(doc, prev) { prev.sum += 1}});
// Testing GROUP BY
res := stats.Find().GroupBy(bson.M{
"key": bson.M{"numeric": true},
"initial": bson.M{"sum": 0},
"reduce": `function(doc, prev) { prev.sum += 1}`,
})
var results []map[string]interface{}
err = res.All(&results)
s.Equal(mydb.ErrUnsupported, err)
}
func (s *AdapterTests) TestResultNonExistentCount() {
sess, err := Open(settings)
s.NoError(err)
defer sess.Close()
total, err := sess.Collection("notartist").Find().Count()
s.NoError(err)
s.Zero(total)
}
func (s *AdapterTests) TestResultFetch() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
artist := sess.Collection("artist")
// Testing map
res := artist.Find()
rowM := map[string]interface{}{}
for res.Next(&rowM) {
s.NotZero(rowM["_id"])
_, ok := rowM["_id"].(bson.ObjectId)
s.True(ok)
s.True(rowM["_id"].(bson.ObjectId).Valid())
name, ok := rowM["name"].(string)
s.True(ok)
s.NotZero(name)
}
err = res.Close()
s.NoError(err)
// Testing struct
rowS := struct {
ID bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
}{}
res = artist.Find()
for res.Next(&rowS) {
s.True(rowS.ID.Valid())
s.NotZero(rowS.Name)
}
err = res.Close()
s.NoError(err)
// Testing tagged struct
rowT := struct {
Value1 bson.ObjectId `bson:"_id"`
Value2 string `bson:"name"`
}{}
res = artist.Find()
for res.Next(&rowT) {
s.True(rowT.Value1.Valid())
s.NotZero(rowT.Value2)
}
err = res.Close()
s.NoError(err)
// Testing Result.All() with a slice of maps.
res = artist.Find()
allRowsM := []map[string]interface{}{}
err = res.All(&allRowsM)
s.NoError(err)
for _, singleRowM := range allRowsM {
s.NotZero(singleRowM["_id"])
}
// Testing Result.All() with a slice of structs.
res = artist.Find()
allRowsS := []struct {
ID bson.ObjectId `bson:"_id"`
Name string
}{}
err = res.All(&allRowsS)
s.NoError(err)
for _, singleRowS := range allRowsS {
s.True(singleRowS.ID.Valid())
}
// Testing Result.All() with a slice of tagged structs.
res = artist.Find()
allRowsT := []struct {
Value1 bson.ObjectId `bson:"_id"`
Value2 string `bson:"name"`
}{}
err = res.All(&allRowsT)
s.NoError(err)
for _, singleRowT := range allRowsT {
s.True(singleRowT.Value1.Valid())
}
}
func (s *AdapterTests) TestUpdate() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
// Value
value := struct {
ID bson.ObjectId `bson:"_id"`
Name string
}{}
// Getting the first artist.
res := artist.Find(mydb.Cond{"_id": mydb.NotEq(nil)}).Limit(1)
err = res.One(&value)
s.NoError(err)
// Updating with a map
rowM := map[string]interface{}{
"name": strings.ToUpper(value.Name),
}
err = res.Update(rowM)
s.NoError(err)
err = res.One(&value)
s.NoError(err)
s.Equal(value.Name, rowM["name"])
// Updating with a struct
rowS := struct {
Name string
}{strings.ToLower(value.Name)}
err = res.Update(rowS)
s.NoError(err)
err = res.One(&value)
s.NoError(err)
s.Equal(value.Name, rowS.Name)
// Updating with a tagged struct
rowT := struct {
Value1 string `bson:"name"`
}{strings.Replace(value.Name, "z", "Z", -1)}
err = res.Update(rowT)
s.NoError(err)
err = res.One(&value)
s.NoError(err)
s.Equal(value.Name, rowT.Value1)
}
func (s *AdapterTests) TestOperators() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
rowS := struct {
ID uint64
Name string
}{}
res := artist.Find(mydb.Cond{"_id": mydb.NotIn(0, -1)})
err = res.One(&rowS)
s.NoError(err)
err = res.Close()
s.NoError(err)
}
func (s *AdapterTests) TestDelete() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
// Getting the first artist.
res := artist.Find(mydb.Cond{"_id": mydb.NotEq(nil)}).Limit(1)
var first struct {
ID bson.ObjectId `bson:"_id"`
}
err = res.One(&first)
s.NoError(err)
res = artist.Find(mydb.Cond{"_id": mydb.Eq(first.ID)})
// Trying to remove the row.
err = res.Delete()
s.NoError(err)
}
func (s *AdapterTests) TestDataTypes() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "data_types" collection.
dataTypes := sess.Collection("data_types")
// Inserting our test subject.
record, err := dataTypes.Insert(testValues)
s.NoError(err)
id := record.ID()
s.NotZero(id)
// Trying to get the same subject we added.
res := dataTypes.Find(mydb.Cond{"_id": mydb.Eq(id)})
exists, err := res.Count()
s.NoError(err)
s.NotZero(exists)
// Trying to dump the subject into an empty structure of the same type.
var item testValuesStruct
err = res.One(&item)
s.NoError(err)
// The original value and the test subject must match.
s.Equal(testValues, item)
}
func (s *AdapterTests) TestPaginator() {
// Opening database.
sess, err := Open(settings)
s.NoError(err)
// We should close the database when it's no longer in use.
defer sess.Close()
// Getting a pointer to the "artist" collection.
artist := sess.Collection("artist")
err = artist.Truncate()
s.NoError(err)
for i := 0; i < 999; i++ {
_, err = artist.Insert(artistType{
Name: fmt.Sprintf("artist-%d", i),
})
s.NoError(err)
}
q := sess.Collection("artist").Find().Paginate(15)
paginator := q.Paginate(13)
var zerothPage []artistType
err = paginator.Page(0).All(&zerothPage)
s.NoError(err)
s.Equal(13, len(zerothPage))
var secondPage []artistType
err = paginator.Page(2).All(&secondPage)
s.NoError(err)
s.Equal(13, len(secondPage))
tp, err := paginator.TotalPages()
s.NoError(err)
s.NotZero(tp)
s.Equal(uint(77), tp)
ti, err := paginator.TotalEntries()
s.NoError(err)
s.NotZero(ti)
s.Equal(uint64(999), ti)
var seventySixthPage []artistType
err = paginator.Page(76).All(&seventySixthPage)
s.NoError(err)
s.Equal(11, len(seventySixthPage))
var seventySeventhPage []artistType
err = paginator.Page(77).All(&seventySeventhPage)
s.NoError(err)
s.Equal(0, len(seventySeventhPage))
var hundredthPage []artistType
err = paginator.Page(100).All(&hundredthPage)
s.NoError(err)
s.Equal(0, len(hundredthPage))
for i := uint(0); i < tp; i++ {
current := paginator.Page(i)
var items []artistType
err := current.All(&items)
s.NoError(err)
if len(items) < 1 {
break
}
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", int64(13*int(i)+j)), items[j].Name)
}
}
paginator = paginator.Cursor("_id")
{
current := paginator.Page(0)
for i := 0; ; i++ {
var items []artistType
err := current.All(&items)
s.NoError(err)
if len(items) < 1 {
break
}
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", int64(13*int(i)+j)), items[j].Name)
}
current = current.NextPage(items[len(items)-1].ID)
}
}
{
log.Printf("Page 76")
current := paginator.Page(76)
for i := 76; ; i-- {
var items []artistType
err := current.All(&items)
s.NoError(err)
if len(items) < 1 {
s.Equal(0, len(items))
break
}
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", 13*int(i)+j), items[j].Name)
}
current = current.PrevPage(items[0].ID)
}
}
{
resultPaginator := sess.Collection("artist").Find().Paginate(15)
count, err := resultPaginator.TotalPages()
s.Equal(uint(67), count)
s.NoError(err)
var items []artistType
err = resultPaginator.Page(5).All(&items)
s.NoError(err)
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", 15*5+j), items[j].Name)
}
resultPaginator = resultPaginator.Cursor("_id").Page(0)
for i := 0; ; i++ {
var items []artistType
err = resultPaginator.All(&items)
s.NoError(err)
if len(items) < 1 {
break
}
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", 15*i+j), items[j].Name)
}
resultPaginator = resultPaginator.NextPage(items[len(items)-1].ID)
}
resultPaginator = resultPaginator.Cursor("_id").Page(66)
for i := 66; ; i-- {
var items []artistType
err = resultPaginator.All(&items)
s.NoError(err)
if len(items) < 1 {
break
}
for j := 0; j < len(items); j++ {
s.Equal(fmt.Sprintf("artist-%d", 15*i+j), items[j].Name)
}
resultPaginator = resultPaginator.PrevPage(items[0].ID)
}
}
}
func TestAdapter(t *testing.T) {
suite.Run(t, &AdapterTests{})
}