多租户更改

This commit is contained in:
richjohnrambo
2026-08-11 15:59:57 +08:00
parent 6c800de67d
commit c2076721fc
453 changed files with 102349 additions and 47 deletions
+185
View File
@@ -0,0 +1,185 @@
// Package common contains utility methods used by all adapters.
package common
import (
"encoding/json"
"sort"
"strconv"
"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.
func SelectEarliestUpdatedSubs(subs []t.Subscription, opts *t.QueryOpt, maxResults int) []t.Subscription {
limit := maxResults
ims := time.Time{}
if opts != nil {
if opts.Limit > 0 && opts.Limit < limit {
limit = opts.Limit
}
if opts.IfModifiedSince != nil {
ims = *opts.IfModifiedSince
}
}
// No cache management and the number of results is below the limit: return all.
if ims.IsZero() && len(subs) <= limit {
return subs
}
// Now that we fetched potentially more subscriptions than needed, we got to take those with the oldest modifications.
// Sorting in ascending order by modification time.
sort.Slice(subs, func(i, j int) bool {
return subs[i].LastModified().Before(subs[j].LastModified())
})
if !ims.IsZero() {
// Keep only those subscriptions which are newer than ims.
at := sort.Search(len(subs), func(i int) bool {
return subs[i].LastModified().After(ims)
})
subs = subs[at:]
}
// Trim slice at the limit.
if len(subs) > limit {
subs = subs[:limit]
}
return subs
}
// SelectLatestTime picks the latest update timestamp out of the two.
func SelectLatestTime(t1, t2 time.Time) time.Time {
if t1.Before(t2) {
// Subscription has not changed recently, use user's update timestamp.
return t2
}
return t1
}
// RangesToSql converts a slice of ranges to SQL BETWEEN or IN() constraint and arguments.
func RangesToSql(in []t.Range) (string, []any) {
if len(in) > 1 || in[0].Hi == 0 {
var args []any
for _, r := range in {
if r.Hi == 0 {
args = append(args, r.Low)
} else {
for i := r.Low; i < r.Hi; i++ {
args = append(args, i)
}
}
}
return "IN (?" + strings.Repeat(",?", len(args)-1) + ")", args
}
// Optimizing for a special case of single range low..hi.
// SQL's BETWEEN is inclusive-inclusive thus decrement Hi by 1.
return "BETWEEN ? AND ?", []any{in[0].Low, in[0].Hi - 1}
}
// DisjunctionSql converts a slice of disjunctions to SQL HAVING clause and arguments.
func DisjunctionSql(req [][]string, fieldName string) (string, []any) {
var args []any
counts := make([]string, 0, len(req))
for _, reqDisjunction := range req {
// At least one of the tags must be present.
if len(reqDisjunction) == 0 {
continue
}
counts = append(counts, "COUNT("+fieldName+" IN (?"+strings.Repeat(",?", len(reqDisjunction)-1)+") OR NULL)>=1")
for _, tag := range reqDisjunction {
args = append(args, tag)
}
}
return "HAVING " + strings.Join(counts, " AND ") + " ", args
}
// FilterFoundTags keeps only those tags in setTags that are present in the index.
func FilterFoundTags(setTags t.StringSlice, index map[string]struct{}) []string {
foundTags := make([]string, 0, 1)
for _, tag := range setTags {
if _, ok := index[tag]; ok {
foundTags = append(foundTags, tag)
}
}
return foundTags
}
// Convert to JSON before storing to JSON field.
func ToJSON(src any) []byte {
if src == nil {
return nil
}
jval, _ := json.Marshal(src)
return jval
}
// Deserialize JSON data from DB.
func FromJSON(src any) any {
if src == nil {
return nil
}
if bb, ok := src.([]byte); ok {
var out any
json.Unmarshal(bb, &out)
return out
}
return nil
}
// Convert update to a list of columns and arguments.
func UpdateByMap(update map[string]any) (cols []string, args []any) {
for col, arg := range update {
col = strings.ToLower(col)
if col == "public" || col == "trusted" || col == "private" || col == "aux" {
arg = ToJSON(arg)
}
cols = append(cols, col+"=?")
args = append(args, arg)
}
return
}
// If Tags field is updated, get the tags so tags table cab be updated too.
func ExtractTags(update map[string]any) []string {
var tags []string
if val := update["Tags"]; val != nil {
tags, _ = val.(t.StringSlice)
}
return []string(tags)
}
// EncodeUidString takes decoded string representation of int64, produce UID.
// UIDs are stored as decoded int64 values.
func EncodeUidString(str string) t.Uid {
unum, _ := strconv.ParseInt(str, 10, 64)
return store.EncodeUid(unum)
}
// DecodeUidString takes UID as string, converts it to int64 representation.
// UIDs are stored as decoded int64 values.
func DecodeUidString(str string) int64 {
uid := t.ParseUid(str)
return store.DecodeUid(uid)
}
+395
View File
@@ -0,0 +1,395 @@
package common
import (
"encoding/json"
"reflect"
"strings"
"testing"
"time"
"github.com/tinode/chat/server/store/types"
)
func genTestData() []types.Subscription {
var testData = []types.Subscription{
{ObjHeader: types.ObjHeader{Id: "1", UpdatedAt: time.Date(2021, time.June, 1, 1, 11, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "2", UpdatedAt: time.Date(2021, time.June, 2, 2, 12, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "3", UpdatedAt: time.Date(2021, time.June, 3, 3, 13, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "4", UpdatedAt: time.Date(2021, time.June, 4, 4, 14, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "5", UpdatedAt: time.Date(2021, time.June, 5, 5, 15, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "6", UpdatedAt: time.Date(2021, time.June, 6, 6, 16, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "7", UpdatedAt: time.Date(2021, time.June, 7, 7, 17, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "8", UpdatedAt: time.Date(2021, time.June, 8, 8, 18, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "9", UpdatedAt: time.Date(2021, time.June, 9, 9, 19, 0, 0, time.Local)}},
{ObjHeader: types.ObjHeader{Id: "10", UpdatedAt: time.Date(2021, time.June, 10, 10, 20, 0, 0, time.Local)}},
}
// TouchedAt is either greater or equal to UpdatedAt.
testData[0].SetTouchedAt(time.Date(2021, time.June, 1, 1, 11, 0, 0, time.Local)) // 1
testData[1].SetTouchedAt(time.Date(2021, time.June, 4, 4, 12, 0, 0, time.Local)) // 3
testData[2].SetTouchedAt(time.Date(2021, time.June, 4, 2, 13, 0, 0, time.Local)) // 2
testData[3].SetTouchedAt(time.Date(2021, time.June, 4, 4, 14, 0, 0, time.Local)) // 4
testData[4].SetTouchedAt(time.Date(2021, time.June, 7, 5, 15, 0, 0, time.Local)) // 6
testData[5].SetTouchedAt(time.Date(2021, time.June, 6, 6, 16, 0, 0, time.Local)) // 5
testData[6].SetTouchedAt(time.Date(2021, time.June, 7, 7, 17, 0, 0, time.Local)) // 7
testData[7].SetTouchedAt(time.Date(2021, time.June, 9, 8, 18, 0, 0, time.Local)) // 8
testData[8].SetTouchedAt(time.Date(2021, time.June, 10, 11, 19, 0, 0, time.Local)) // 10
testData[9].SetTouchedAt(time.Date(2021, time.June, 10, 10, 20, 0, 0, time.Local)) // 9
return testData
}
func TestSelectEarliestUpdatedSubs(t *testing.T) {
getOrder := func(subs []types.Subscription) string {
var order []string
for i := range subs {
order = append(order, subs[i].Id)
}
return strings.Join(order, ",")
}
subs := SelectEarliestUpdatedSubs(genTestData(), nil, 100)
// No sorting when returning the full set.
expectedOrder := "1,2,3,4,5,6,7,8,9,10"
sortOrder := getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Wrong results returned. Expected:", expectedOrder, "; Got:", sortOrder)
}
// Sorted, oldest 9 results.
subs = SelectEarliestUpdatedSubs(genTestData(), nil, 9)
expectedOrder = "1,3,2,4,6,5,7,8,10"
sortOrder = getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Limited query returned wrong results. Expected:", expectedOrder, "; Got:", sortOrder)
}
// Sorted, oldest 9 results.
subs = SelectEarliestUpdatedSubs(genTestData(), &types.QueryOpt{Limit: 20}, 9)
expectedOrder = "1,3,2,4,6,5,7,8,10"
sortOrder = getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Limited query (2) returned wrong results. Expected:", expectedOrder, "; Got:", sortOrder)
}
// Sorted, oldest 9 results.
subs = SelectEarliestUpdatedSubs(genTestData(), &types.QueryOpt{Limit: 9}, 20)
expectedOrder = "1,3,2,4,6,5,7,8,10"
sortOrder = getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Limited query (3) returned wrong results. Expected:", expectedOrder, "; Got:", sortOrder)
}
ims := time.Date(2021, time.June, 7, 8, 16, 15, 0, time.Local)
subs = SelectEarliestUpdatedSubs(genTestData(), &types.QueryOpt{Limit: 6, IfModifiedSince: &ims}, 20)
expectedOrder = "8,10,9"
sortOrder = getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Date & count limited query returned wrong results. Expected:", expectedOrder, "; Got:", sortOrder)
}
ims = time.Date(2021, time.June, 4, 4, 13, 15, 0, time.Local)
subs = SelectEarliestUpdatedSubs(genTestData(), &types.QueryOpt{Limit: 3, IfModifiedSince: &ims}, 20)
expectedOrder = "4,6,5"
sortOrder = getOrder(subs)
if sortOrder != expectedOrder {
t.Error("Count & date limited query returned wrong results. Expected:", expectedOrder, "; Got:", sortOrder)
}
}
func TestSelectLatestTime(t *testing.T) {
t1 := time.Date(2021, time.June, 1, 10, 0, 0, 0, time.UTC)
t2 := time.Date(2021, time.June, 2, 10, 0, 0, 0, time.UTC)
// t1 is before t2, should return t2
result := SelectLatestTime(t1, t2)
if !result.Equal(t2) {
t.Errorf("Expected %v, got %v", t2, result)
}
// t2 is after t1, should return t2
result = SelectLatestTime(t2, t1)
if !result.Equal(t2) {
t.Errorf("Expected %v, got %v", t2, result)
}
// Equal times, should return either one (in this case t1)
result = SelectLatestTime(t1, t1)
if !result.Equal(t1) {
t.Errorf("Expected %v, got %v", t1, result)
}
}
func TestRangesToSql(t *testing.T) {
// Test single range with Hi = 0 (IN clause)
ranges := []types.Range{{Low: 5, Hi: 0}}
sql, args := RangesToSql(ranges)
expectedSql := "IN (?)"
expectedArgs := []any{5}
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
// Test single range with Hi > 0 (BETWEEN clause)
ranges = []types.Range{{Low: 5, Hi: 8}}
sql, args = RangesToSql(ranges)
expectedSql = "BETWEEN ? AND ?"
expectedArgs = []any{5, 7} // Hi-1 for BETWEEN
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
// Test multiple ranges (IN clause)
ranges = []types.Range{{Low: 1, Hi: 3}, {Low: 5, Hi: 0}, {Low: 8, Hi: 10}}
sql, args = RangesToSql(ranges)
expectedSql = "IN (?,?,?,?,?)"
expectedArgs = []any{1, 2, 5, 8, 9}
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
}
func TestDisjunctionSql(t *testing.T) {
// Test single disjunction
req := [][]string{{"tag1", "tag2", "tag3"}}
sql, args := DisjunctionSql(req, "tagname")
expectedSql := "HAVING COUNT(tagname IN (?,?,?) OR NULL)>=1 "
expectedArgs := []any{"tag1", "tag2", "tag3"}
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
// Test multiple disjunctions
req = [][]string{{"tag1", "tag2"}, {"tag3"}, {"tag4", "tag5"}}
sql, args = DisjunctionSql(req, "fieldname")
expectedSql = "HAVING COUNT(fieldname IN (?,?) OR NULL)>=1 AND COUNT(fieldname IN (?) OR NULL)>=1 AND COUNT(fieldname IN (?,?) OR NULL)>=1 "
expectedArgs = []any{"tag1", "tag2", "tag3", "tag4", "tag5"}
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
// Test empty disjunctions (should be skipped)
req = [][]string{{"tag1"}, {}, {"tag2"}}
sql, args = DisjunctionSql(req, "fieldname")
expectedSql = "HAVING COUNT(fieldname IN (?) OR NULL)>=1 AND COUNT(fieldname IN (?) OR NULL)>=1 "
expectedArgs = []any{"tag1", "tag2"}
if sql != expectedSql {
t.Errorf("Expected SQL '%s', got '%s'", expectedSql, sql)
}
if !reflect.DeepEqual(args, expectedArgs) {
t.Errorf("Expected args %v, got %v", expectedArgs, args)
}
}
func TestFilterFoundTags(t *testing.T) {
setTags := types.StringSlice{"tag1", "tag2", "tag3", "tag4", "tag5"}
index := map[string]struct{}{
"tag1": {},
"tag3": {},
"tag5": {},
"tag6": {}, // Not in setTags
}
result := FilterFoundTags(setTags, index)
expected := []string{"tag1", "tag3", "tag5"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
// Test with empty index
emptyIndex := map[string]struct{}{}
result = FilterFoundTags(setTags, emptyIndex)
expected = []string{}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
// Test with empty setTags
emptyTags := types.StringSlice{}
result = FilterFoundTags(emptyTags, index)
expected = []string{}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestToJSON(t *testing.T) {
// Test with nil
result := ToJSON(nil)
if result != nil {
t.Errorf("Expected nil, got %v", result)
}
// Test with string
input := "test string"
result = ToJSON(input)
expected := []byte(`"test string"`)
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
// Test with map
input2 := map[string]any{"key": "value", "number": 42}
result = ToJSON(input2)
// Parse back to verify
var parsed map[string]any
if err := json.Unmarshal(result, &parsed); err != nil {
t.Errorf("Failed to unmarshal result: %v", err)
}
if parsed["key"] != "value" || parsed["number"] != float64(42) {
t.Errorf("JSON conversion failed, got %v", parsed)
}
}
func TestFromJSON(t *testing.T) {
// Test with nil
result := FromJSON(nil)
if result != nil {
t.Errorf("Expected nil, got %v", result)
}
// Test with valid JSON bytes
input := []byte(`{"key": "value", "number": 42}`)
result = FromJSON(input)
if resultMap, ok := result.(map[string]any); ok {
if resultMap["key"] != "value" || resultMap["number"] != float64(42) {
t.Errorf("JSON deserialization failed, got %v", resultMap)
}
} else {
t.Errorf("Expected map[string]any, got %T", result)
}
// Test with invalid JSON bytes
invalidInput := []byte(`{invalid json}`)
result = FromJSON(invalidInput)
if result != nil {
t.Errorf("Expected nil for invalid JSON, got %v", result)
}
// Test with non-byte slice
stringInput := "not bytes"
result = FromJSON(stringInput)
if result != nil {
t.Errorf("Expected nil for non-byte input, got %v", result)
}
}
func TestUpdateByMap(t *testing.T) {
update := map[string]any{
"Name": "John Doe",
"Age": 30,
"Public": map[string]string{"avatar": "url"},
"Private": map[string]string{"email": "john@example.com"},
"Trusted": map[string]bool{"verified": true},
"UpdatedAt": time.Now(),
}
cols, args := UpdateByMap(update)
// Check that we have the right number of columns and args
if len(cols) != len(args) || len(cols) != len(update) {
t.Errorf("Expected %d columns and args, got %d cols and %d args", len(update), len(cols), len(args))
}
// Verify column format
for _, col := range cols {
if !strings.Contains(col, "=?") {
t.Errorf("Column should contain '=?', got %s", col)
}
}
// Check that JSON fields are properly handled
foundPublic := false
foundPrivate := false
foundTrusted := false
for i, col := range cols {
if strings.HasPrefix(col, "public=?") {
foundPublic = true
// Should be JSON bytes
if _, ok := args[i].([]byte); !ok {
t.Errorf("Public field should be []byte, got %T", args[i])
}
}
if strings.HasPrefix(col, "private=?") {
foundPrivate = true
if _, ok := args[i].([]byte); !ok {
t.Errorf("Private field should be []byte, got %T", args[i])
}
}
if strings.HasPrefix(col, "trusted=?") {
foundTrusted = true
if _, ok := args[i].([]byte); !ok {
t.Errorf("Trusted field should be []byte, got %T", args[i])
}
}
}
if !foundPublic || !foundPrivate || !foundTrusted {
t.Error("Missing JSON fields in output")
}
}
func TestExtractTags(t *testing.T) {
// Test with Tags field present
update := map[string]any{
"Name": "John",
"Tags": types.StringSlice{"tag1", "tag2", "tag3"},
"Age": 30,
}
tags := ExtractTags(update)
expected := []string{"tag1", "tag2", "tag3"}
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Expected %v, got %v", expected, tags)
}
// Test with no Tags field
update = map[string]any{
"Name": "John",
"Age": 30,
}
tags = ExtractTags(update)
expected = nil
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Expected %+v, got %+v", expected, tags)
}
// Test with nil Tags field
update = map[string]any{
"Name": "John",
"Tags": nil,
"Age": 30,
}
tags = ExtractTags(update)
expected = nil
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Expected %+v, got %+v", expected, tags)
}
// Test with wrong type for Tags field
update = map[string]any{
"Name": "John",
"Tags": "not a slice",
"Age": 30,
}
tags = ExtractTags(update)
expected = nil
if !reflect.DeepEqual(tags, expected) {
t.Errorf("Expected %+v, got %+v", expected, tags)
}
}
+382
View File
@@ -0,0 +1,382 @@
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 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
}
func initUsers(now time.Time) []*types.User {
users := make([]*types.User, 0, 3)
users = append(users, &types.User{
ObjHeader: types.ObjHeader{
Id: "3ysxkod5hNM",
},
UserAgent: "SomeAgent v1.2.3",
Tags: []string{"alice"},
})
users = append(users, &types.User{
ObjHeader: types.ObjHeader{
Id: "9AVDamaNCRY",
},
UserAgent: "Tinode Web v111.222.333",
Tags: []string{"bob"},
})
users = append(users, &types.User{
ObjHeader: types.ObjHeader{
Id: "0QLrX3WPS2o",
},
UserAgent: "Tindroid v1.2.3",
Tags: []string{"carol"},
})
for _, user := range users {
// Initialize timestamps.
user.InitTimes()
// Assign user.id from user.Id.
user.Uid()
}
deletedAt := now.Add(10 * time.Minute)
users[2].State = types.StateDeleted
users[2].StateAt = &deletedAt
return users
}
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",
Value: "alice@test.example.com",
Done: true,
})
creds = append(creds, &types.Credential{ // 1
User: users[1].Id,
Method: "email",
Value: "bob@test.example.com",
Done: true,
})
creds = append(creds, &types.Credential{ // 2
User: users[1].Id,
Method: "email",
Value: "bob@test.example.com",
})
creds = append(creds, &types.Credential{ // 3
User: users[2].Id,
Method: "tel",
Value: "+998991112233",
})
creds = append(creds, &types.Credential{ // 4
User: users[2].Id,
Method: "tel",
Value: "+998993332211",
Done: true,
})
creds = append(creds, &types.Credential{ // 5
User: users[2].Id,
Method: "email",
Value: "asdf@example.com",
})
for _, cred := range creds {
cred.InitTimes()
}
creds[3].CreatedAt = now.Add(-10 * time.Minute)
creds[3].UpdatedAt = now.Add(-10 * time.Minute)
return creds
}
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, 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(now time.Time, users []*types.User) []*types.Topic {
topics := make([]*types.Topic, 0, 5)
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "grpgRXf0rU4uR4",
CreatedAt: now.Add(10 * time.Minute),
UpdatedAt: now,
},
TouchedAt: now,
Owner: users[0].Id,
SeqId: 111,
Tags: []string{"travel", "zxcv"},
SubCnt: 2,
})
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "p2p9AVDamaNCRbfKzGSh3mE0w",
CreatedAt: now,
UpdatedAt: now,
},
TouchedAt: now,
SeqId: 12,
})
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "p2pxQLrX3WPS2rfKzGSh3mE0w",
CreatedAt: now,
UpdatedAt: now,
},
TouchedAt: now,
SeqId: 15,
})
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "p2pE1iE7I9JN5ESv44HiLbj1A",
CreatedAt: now,
UpdatedAt: now,
},
TouchedAt: now,
SeqId: 555,
})
topics = append(topics, &types.Topic{
ObjHeader: types.ObjHeader{
Id: "p2pQvr1xwKU01LfKzGSh3mE0w",
CreatedAt: now,
UpdatedAt: now,
},
TouchedAt: now,
SeqId: 333,
})
return topics
}
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,
UpdatedAt: now.Add(10 * time.Minute),
},
User: users[0].Id,
Topic: topics[0].Id,
RecvSeqId: 5,
ReadSeqId: 1,
ModeWant: 255,
ModeGiven: 255,
})
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now,
UpdatedAt: now.Add(15 * time.Minute),
},
User: users[1].Id,
Topic: topics[0].Id,
RecvSeqId: 6,
ReadSeqId: 3,
ModeWant: 47,
ModeGiven: 47,
})
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now.Add(-10 * time.Hour),
UpdatedAt: now.Add(-10 * time.Hour),
},
User: users[0].Id,
Topic: topics[1].Id,
RecvSeqId: 9,
ReadSeqId: 5,
ModeWant: 47,
ModeGiven: 47,
})
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now,
UpdatedAt: now.Add(20 * time.Minute),
},
User: users[1].Id,
Topic: topics[1].Id,
RecvSeqId: 9,
ReadSeqId: 5,
ModeWant: 47,
ModeGiven: 47,
})
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now,
UpdatedAt: now.Add(30 * time.Minute),
},
User: users[2].Id,
Topic: topics[2].Id,
RecvSeqId: 0,
ReadSeqId: 0,
ModeWant: 47,
ModeGiven: 47,
})
subs = append(subs, &types.Subscription{
ObjHeader: types.ObjHeader{
CreatedAt: now,
UpdatedAt: now.Add(40 * time.Minute),
},
User: users[2].Id,
Topic: topics[3].Id,
RecvSeqId: 555,
ReadSeqId: 455,
ModeWant: 47,
ModeGiven: 47,
})
for _, sub := range subs {
sub.SetTouchedAt(now)
}
return subs
}
func initMessages(users []*types.User, topics []*types.Topic) []*types.Message {
msgs := make([]*types.Message, 0, 6)
msgs = append(msgs, &types.Message{
SeqId: 1,
Topic: topics[0].Id,
From: users[0].Id,
Content: "msg1",
})
msgs = append(msgs, &types.Message{
SeqId: 2,
Topic: topics[0].Id,
From: users[2].Id,
Content: "msg2",
DeletedFor: []types.SoftDelete{{
User: users[0].Id,
DelId: 1}},
})
msgs = append(msgs, &types.Message{
SeqId: 3,
Topic: topics[0].Id,
From: users[0].Id,
Content: "msg31",
})
msgs = append(msgs, &types.Message{
SeqId: 1,
Topic: topics[1].Id,
From: users[1].Id,
Content: "msg1",
})
msgs = append(msgs, &types.Message{
SeqId: 5,
Topic: topics[1].Id,
From: users[1].Id,
Content: "msg2",
})
msgs = append(msgs, &types.Message{
SeqId: 11,
Topic: topics[1].Id,
From: users[0].Id,
Content: "msg3",
})
for i, msg := range msgs {
msg.InitTimes()
msg.SetUid(types.Uid(i + 1))
}
return msgs
}
func initDevices(now time.Time) []*types.DeviceDef {
devs := make([]*types.DeviceDef, 0, 2)
devs = append(devs, &types.DeviceDef{
DeviceId: "2934ujfoviwj09ntf094",
Platform: "Android",
LastSeen: now,
Lang: "en_EN",
})
devs = append(devs, &types.DeviceDef{
DeviceId: "pogpjb023b09gfdmp",
Platform: "iOS",
LastSeen: now,
Lang: "en_EN",
})
return devs
}
func initFileDefs(now time.Time, users []*types.User) []*types.FileDef {
files := make([]*types.FileDef, 0, 2)
files = append(files, &types.FileDef{
ObjHeader: types.ObjHeader{
CreatedAt: now,
UpdatedAt: now,
},
Status: types.UploadStarted,
User: users[0].Id,
MimeType: "application/pdf",
Location: "uploads/qwerty.pdf",
Size: 123456,
})
files = append(files, &types.FileDef{
ObjHeader: types.ObjHeader{
CreatedAt: now.Add(60 * time.Minute),
UpdatedAt: now.Add(60 * time.Minute),
},
Status: types.UploadStarted,
User: users[0].Id,
Location: "uploads/asdf.txt",
Size: 654321,
})
files[0].SetUid(types.Uid(1001))
files[1].SetUid(types.Uid(1002))
return files
}
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(users, topics),
Devs: initDevices(now),
Files: initFileDefs(now, users),
Tags: initTags(),
Now: now,
}
}