mirror of
https://github.com/tinode/chat.git
synced 2026-05-07 20:12:42 +00:00
mysql tests added
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ...
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
Reference in New Issue
Block a user