mysql tests added

This commit is contained in:
or-else
2025-10-11 22:03:32 +03:00
parent a6f0b7f831
commit 3ea10e31c2
11 changed files with 1586 additions and 284 deletions
+5
View File
@@ -201,4 +201,9 @@ type Adapter interface {
PCacheDelete(key string) error
// PCacheExpire expires older entries with the specified key prefix.
PCacheExpire(keyPrefix string, olderThan time.Time) error
// Testing
// GetTestDB returns a currently open database connection.
GetTestDB() any
}
+10
View File
@@ -8,10 +8,20 @@ import (
"strings"
"time"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/store"
t "github.com/tinode/chat/server/store/types"
)
type AuthRecord struct {
Unique string `json:"unique" bson:"_id"`
UserId string `json:"userid"`
Scheme string `json:"scheme"`
AuthLvl auth.Level `json:"authLvl"`
Secret []byte `json:"secret"`
Expires time.Time `json:"expires"`
}
// SelectEarliestUpdatedSubs selects no more than the given number of subscriptions from the
// given slice satisfying the query. When the number of subscriptions is greater than the limit,
// the subscriptions with the earliest timestamp are selected.
@@ -1,33 +1,30 @@
package tests
package test_data
import (
"time"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/db/common"
"github.com/tinode/chat/server/store/types"
)
type authRecord struct {
Id string `bson:"_id"`
UserId string
Scheme string
AuthLvl auth.Level
Secret []byte
Expires time.Time
type TestData struct {
UGen *types.UidGenerator
Users []*types.User
Creds []*types.Credential
Recs []common.AuthRecord
Topics []*types.Topic
Subs []*types.Subscription
Msgs []*types.Message
Devs []*types.DeviceDef
Files []*types.FileDef
// Tags: add, remove, reset
Tags [][]string
Now time.Time
}
var uGen types.UidGenerator
var users []*types.User
var creds []*types.Credential
var recs []authRecord
var topics []*types.Topic
var subs []*types.Subscription
var msgs []*types.Message
var devs []*types.DeviceDef
var files []*types.FileDef
var now time.Time
func initUsers() {
func initUsers(now time.Time) []*types.User {
users := make([]*types.User, 0, 3)
users = append(users, &types.User{
ObjHeader: types.ObjHeader{
Id: "3ysxkod5hNM",
@@ -44,7 +41,7 @@ func initUsers() {
})
users = append(users, &types.User{
ObjHeader: types.ObjHeader{
Id: "xQLrX3WPS2o",
Id: "0QLrX3WPS2o",
},
UserAgent: "Tindroid v1.2.3",
Tags: []string{"carol"},
@@ -55,9 +52,11 @@ func initUsers() {
deletedAt := now.Add(10 * time.Minute)
users[2].State = types.StateDeleted
users[2].StateAt = &deletedAt
return users
}
func initCreds() {
func initCreds(now time.Time, users []*types.User) []*types.Credential {
creds := make([]*types.Credential, 0, 6)
creds = append(creds, &types.Credential{ // 0
User: users[0].Id,
Method: "email",
@@ -96,28 +95,32 @@ func initCreds() {
}
creds[3].CreatedAt = now.Add(-10 * time.Minute)
creds[3].UpdatedAt = now.Add(-10 * time.Minute)
return creds
}
func initAuthRecords() {
recs = append(recs, authRecord{
Id: "basic:alice",
func initAuthRecords(now time.Time, users []*types.User) []common.AuthRecord {
recs := make([]common.AuthRecord, 0, 2)
recs = append(recs, common.AuthRecord{
Unique: "basic:alice",
UserId: users[0].Id,
Scheme: "basic",
AuthLvl: auth.LevelAuth,
Secret: []byte{'a', 'l', 'i', 'c', 'e'},
Expires: now.Add(24 * time.Hour),
})
recs = append(recs, authRecord{
Id: "basic:bob",
recs = append(recs, common.AuthRecord{
Unique: "basic:bob",
UserId: users[1].Id,
Scheme: "basic",
AuthLvl: auth.LevelAuth,
Secret: []byte{'b', 'o', 'b'},
Expires: now.Add(24 * time.Hour),
})
return recs
}
func initTopics() {
func initTopics(now time.Time, users []*types.User) []*types.Topic {
topics := make([]*types.Topic, 0, 5)
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "grpgRXf0rU4uR4",
@@ -156,7 +159,6 @@ func initTopics() {
},
TouchedAt: now,
SeqId: 555,
Tags: []string{"qwer"},
})
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
@@ -166,10 +168,12 @@ func initTopics() {
},
TouchedAt: now,
SeqId: 333,
Tags: []string{"asdf"},
})
return topics
}
func initSubs() {
func initSubs(now time.Time, users []*types.User, topics []*types.Topic) []*types.Subscription {
subs := make([]*types.Subscription, 0, 6)
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now,
@@ -245,8 +249,11 @@ func initSubs() {
for _, sub := range subs {
sub.SetTouchedAt(now)
}
return subs
}
func initMessages() {
func initMessages(now time.Time, users []*types.User, topics []*types.Topic, uGen *types.UidGenerator) []*types.Message {
msgs := make([]*types.Message, 0, 6)
msgs = append(msgs, &types.Message{
SeqId: 1,
Topic: topics[0].Id,
@@ -291,8 +298,11 @@ func initMessages() {
msg.InitTimes()
msg.SetUid(uGen.Get())
}
return msgs
}
func initDevices() {
func initDevices(now time.Time) []*types.DeviceDef {
devs := make([]*types.DeviceDef, 0, 2)
devs = append(devs, &types.DeviceDef{
DeviceId: "2934ujfoviwj09ntf094",
Platform: "Android",
@@ -305,8 +315,11 @@ func initDevices() {
LastSeen: now,
Lang: "en_EN",
})
return devs
}
func initFileDefs() {
func initFileDefs(now time.Time, users []*types.User, uGen *types.UidGenerator) []*types.FileDef {
files := make([]*types.FileDef, 0, 2)
files = append(files, &types.FileDef{
ObjHeader: types.ObjHeader{
Id: uGen.GetStr(),
@@ -321,24 +334,44 @@ func initFileDefs() {
files = append(files, &types.FileDef{
ObjHeader: types.ObjHeader{
Id: uGen.GetStr(),
CreatedAt: now.Add(2 * time.Minute),
UpdatedAt: now.Add(2 * time.Minute),
CreatedAt: now.Add(60 * time.Minute),
UpdatedAt: now.Add(60 * time.Minute),
},
Status: types.UploadStarted,
User: users[0].Id,
Location: "uploads/asdf.txt",
})
return files
}
func initData() {
// Use fixed timestamp to make tests more predictable
now = time.Date(2021, time.June, 12, 11, 39, 24, 15, time.Local).UTC().Round(time.Millisecond)
initUsers()
initCreds()
initAuthRecords()
initTopics()
initSubs()
initMessages()
initDevices()
initFileDefs()
func initTags() [][]string {
// Tags must be lowercase and non-repeating.
addTags := []string{"tag1", "alice"}
removeTags := []string{"alice", "tag1", "tag2"}
resetTags := []string{"alice", "tag111", "tag333"}
return [][]string{addTags, removeTags, resetTags}
}
func InitTestData() *TestData {
// Use fixed timestamp to make tests more predictable
var now = time.Date(2021, time.June, 12, 11, 39, 24, 15, time.Local).UTC().Round(time.Millisecond)
var uGen = &types.UidGenerator{}
if err := uGen.Init(11, []byte("testtesttesttest")); err != nil {
return nil
}
var users = initUsers(now)
var topics = initTopics(now, users)
return &TestData{
UGen: uGen,
Users: users,
Creds: initCreds(now, users),
Recs: initAuthRecords(now, users),
Topics: topics,
Subs: initSubs(now, users, topics),
Msgs: initMessages(now, users, topics, uGen),
Devs: initDevices(now),
Files: initFileDefs(now, users, uGen),
Tags: initTags(),
Now: now,
}
}
+6 -5
View File
@@ -1168,7 +1168,7 @@ func (a *adapter) CredGetActive(uid t.Uid, method string) (*t.Credential, error)
if err := a.db.Collection("credentials").FindOne(a.ctx, filter).Decode(&cred); err != nil {
if err == mdb.ErrNoDocuments { // Cred not found
return nil, t.ErrNotFound
err = nil
}
return nil, err
}
@@ -2275,7 +2275,6 @@ func (a *adapter) Find(caller, prefPrefix string, req [][]string, opt []string,
}
} }
} } },
{ $match: { _id: { $ne: { $regex: "^p2p" } } } },
{ $match: { $expr: { $ne: [
{ $size: { $setIntersection: [ "$tags", [ "alias:alice", "basic:alice", "travel" ] ] } },
0
@@ -2350,9 +2349,6 @@ func (a *adapter) Find(caller, prefPrefix string, req [][]string, opt []string,
}}}},
}
// Keep group topics and users only.
pipeline = append(pipeline, b.M{"$match": b.M{"_id": b.M{"$not": b.M{"$regex": "^p2p"}}}})
// Ensure required tags are present.
for _, reqDisjunction := range req {
if len(reqDisjunction) == 0 {
@@ -3050,6 +3046,11 @@ func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) error {
return err
}
// GetTestDB returns a currently open database connection.
func (a *adapter) GetTestDB() any {
return a.db
}
func (a *adapter) isDbInitialized() bool {
var result map[string]int
File diff suppressed because it is too large Load Diff
+16 -6
View File
@@ -58,7 +58,7 @@ const (
type configType struct {
// DB connection settings.
// Please, see https://pkg.go.dev/github.com/go-sql-driver/mysql#Config
// See https://pkg.go.dev/github.com/go-sql-driver/mysql#Config
// for the full list of fields.
ms.Config
// Deprecated.
@@ -1099,6 +1099,9 @@ func (a *adapter) UserGet(uid t.Uid) (*t.User, error) {
func (a *adapter) UserGetAll(ids ...t.Uid) ([]t.User, error) {
uids := make([]any, len(ids))
for i, id := range ids {
if id.IsZero() {
continue
}
uids[i] = store.DecodeUid(id)
}
@@ -1236,10 +1239,12 @@ func (a *adapter) UserDelete(uid t.Uid, hard bool) error {
return err
}
// Disable all subscriptions to topics where the user is the owner.
sql, args, _ := sqlx.In("UPDATE subscriptions SET s.updatedat=?,s.deletedat=? WHERE topic IN (?)", now, now, ownTopics)
if _, err = tx.Exec(tx.Rebind(sql), args); err != nil {
return err
if len(ownTopics) > 0 {
// Disable all subscriptions to topics where the user is the owner.
sql, args, _ := sqlx.In("UPDATE subscriptions SET s.updatedat=?,s.deletedat=? WHERE topic IN (?)", now, now, ownTopics)
if _, err = tx.Exec(tx.Rebind(sql), args...); err != nil {
return err
}
}
// Disable group topics where the user is the owner.
@@ -2691,7 +2696,7 @@ func (a *adapter) MessageGetAll(topic string, forUser t.Uid, opts *t.QueryOpt) (
} else {
args = append(args, 0)
}
if opts.Before > 0 {
if opts.Before > 1 {
// MySQL BETWEEN is inclusive-inclusive, Tinode API requires inclusive-exclusive, thus -1
args = append(args, opts.Before-1)
} else {
@@ -3628,6 +3633,11 @@ func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) error {
return err
}
// GetTestDB returns a currently open database connection.
func (a *adapter) GetTestDB() any {
return a.db
}
// Helper functions
// Check if MySQL error is a Error Code: 1062. Duplicate entry ... for key ...
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
{
"reset_db_data": true,
"adapters": {
"mysql": {
"dsn": "root@tcp(localhost:3306)/tinode_test?parseTime=true&collation=utf8mb4_unicode_ci",
// Name of the main database.
"database": "tinode_test"
}
}
}
+5
View File
@@ -3537,6 +3537,11 @@ func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) error {
return err
}
// GetTestDB returns a currently open database connection.
func (a *adapter) GetTestDB() any {
return a.db
}
// Helper functions
// Check if MySQL error is a Error Code: 1062. Duplicate entry ... for key ...
+7 -11
View File
@@ -62,15 +62,6 @@ type configType struct {
HostDecayDuration int `json:"host_decay_duration,omitempty"`
}
type authRecord struct {
Unique string `json:"unique"`
UserId string `json:"userid"`
Scheme string `json:"scheme"`
AuthLvl auth.Level `json:"authLvl"`
Secret []byte `json:"secret"`
Expires time.Time `json:"expires"`
}
// Open initializes rethinkdb session
func (a *adapter) Open(jsonconfig json.RawMessage) error {
if a.conn != nil {
@@ -601,7 +592,7 @@ func (a *adapter) AuthAddRecord(uid t.Uid, scheme, unique string, authLvl auth.L
secret []byte, expires time.Time) error {
_, err := rdb.DB(a.dbName).Table("auth").Insert(
&authRecord{
&common.AuthRecord{
Unique: unique,
UserId: uid.String(),
Scheme: scheme,
@@ -655,7 +646,7 @@ func (a *adapter) AuthUpdRecord(uid t.Uid, scheme, unique string, authLvl auth.L
return t.ErrNotFound
}
var record authRecord
var record common.AuthRecord
if err = cursor.One(&record); err != nil {
return err
}
@@ -3025,6 +3016,11 @@ func (a *adapter) PCacheExpire(keyPrefix string, olderThan time.Time) error {
return err
}
// GetTestDB returns a currently open database connection.
func (a *adapter) GetTestDB() any {
return a.conn
}
// Checks if the given error is 'Database not found'.
func isMissingDb(err error) bool {
if err == nil {
+4
View File
@@ -1114,6 +1114,10 @@ func (pcacheMapper) Expire(keyPrefix string, olderThan time.Time) error {
return adp.PCacheExpire(keyPrefix, olderThan)
}
func SetTestUidGenerator(g types.UidGenerator) {
uGen = g
}
func init() {
Store = storeObj{}
Users = usersMapper{}