Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
399 lines
15 KiB
Go
399 lines
15 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package localcachelayer
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
cmocks "github.com/mattermost/mattermost/server/v8/platform/services/cache/mocks"
|
|
)
|
|
|
|
func TestUserStore(t *testing.T) {
|
|
StoreTestWithSqlStore(t, storetest.TestUserStore)
|
|
}
|
|
|
|
func TestUserStoreCache(t *testing.T) {
|
|
rctx := request.TestContext(t)
|
|
|
|
fakeUserIds := []string{"123"}
|
|
fakeUser := []*model.User{{
|
|
Id: "123",
|
|
AuthData: model.NewPointer("authData"),
|
|
AuthService: "authService",
|
|
}}
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
|
|
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUser, err := cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeUser, gotUser)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1)
|
|
|
|
_, _ = cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1)
|
|
})
|
|
|
|
t.Run("first call not cached, second force not cached", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUser, err := cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeUser, gotUser)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1)
|
|
|
|
_, _ = cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, false)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 2)
|
|
})
|
|
|
|
t.Run("first call not cached, invalidate, and then not cached again", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUser, err := cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeUser, gotUser)
|
|
|
|
cachedStore.User().InvalidateProfileCacheForUser("123")
|
|
|
|
_, _ = cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 2)
|
|
})
|
|
|
|
t.Run("should always return a copy of the stored data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
storedUsers, err := mockStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, false)
|
|
require.NoError(t, err)
|
|
|
|
originalProps := make([]model.StringMap, len(storedUsers))
|
|
|
|
for i := range storedUsers {
|
|
originalProps[i] = storedUsers[i].NotifyProps
|
|
storedUsers[i].NotifyProps = map[string]string{}
|
|
storedUsers[i].NotifyProps["key"] = "somevalue"
|
|
}
|
|
|
|
cachedUsers, err := cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
|
|
for i := range storedUsers {
|
|
assert.Equal(t, storedUsers[i].Id, cachedUsers[i].Id)
|
|
}
|
|
|
|
cachedUsers, err = cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
for i := range storedUsers {
|
|
storedUsers[i].Props = model.StringMap{}
|
|
storedUsers[i].Timezone = model.StringMap{}
|
|
assert.Equal(t, storedUsers[i], cachedUsers[i])
|
|
if storedUsers[i] == cachedUsers[i] {
|
|
assert.Fail(t, "should be different pointers")
|
|
}
|
|
cachedUsers[i].NotifyProps["key"] = "othervalue"
|
|
assert.NotEqual(t, storedUsers[i], cachedUsers[i])
|
|
}
|
|
|
|
for i := range storedUsers {
|
|
storedUsers[i].NotifyProps = originalProps[i]
|
|
}
|
|
})
|
|
|
|
t.Run("assert **model.User not passed", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
cmock := cmocks.NewCache(t)
|
|
cmock.On("GetMulti", []string{"123"}, mock.MatchedBy(func(values []any) bool {
|
|
if len(values) != 1 {
|
|
return false
|
|
}
|
|
_, ok := values[0].(*model.User)
|
|
return ok
|
|
})).Return(nil)
|
|
|
|
cachedStore.user.rootStore.userProfileByIdsCache = cmock
|
|
|
|
_, err = cachedStore.User().GetProfileByIds(rctx, fakeUserIds, &store.UserGetByIdsOpts{}, true)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestUserStoreGetAllProfiles(t *testing.T) {
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
users, err := cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 0, PerPage: 100})
|
|
require.NoError(t, err)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 1)
|
|
|
|
users2, _ := cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 0, PerPage: 100})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 1)
|
|
assert.Equal(t, users, users2)
|
|
|
|
_, _ = cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 2, PerPage: 1})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 2)
|
|
assert.Equal(t, users, users2)
|
|
})
|
|
|
|
t.Run("different page sizes aren't cached", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
_, _ = cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 0, PerPage: 100})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 1)
|
|
|
|
_, _ = cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 2, PerPage: 1})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 2)
|
|
|
|
_, _ = cachedStore.User().GetAllProfiles(&model.UserGetOptions{Page: 1, PerPage: 2})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfiles", 3)
|
|
})
|
|
}
|
|
|
|
func TestUserStoreProfilesInChannelCache(t *testing.T) {
|
|
fakeChannelId := "123"
|
|
fakeUserId := "456"
|
|
fakeMap := map[string]*model.User{
|
|
fakeUserId: {Id: "456"},
|
|
}
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
|
|
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeMap, gotMap)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1)
|
|
|
|
_, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1)
|
|
})
|
|
|
|
t.Run("first call not cached, second force not cached", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeMap, gotMap)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1)
|
|
|
|
_, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, false)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2)
|
|
})
|
|
|
|
t.Run("first call not cached, invalidate by channel, and then not cached again", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeMap, gotMap)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1)
|
|
|
|
cachedStore.User().InvalidateProfilesInChannelCache("123")
|
|
|
|
_, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2)
|
|
})
|
|
|
|
t.Run("first call not cached, invalidate by user, and then not cached again", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeMap, gotMap)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1)
|
|
|
|
cachedStore.User().InvalidateProfilesInChannelCacheByUser("456")
|
|
|
|
_, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2)
|
|
})
|
|
}
|
|
|
|
func TestUserStoreGetCache(t *testing.T) {
|
|
fakeUserId := "123"
|
|
fakeUser := &model.User{
|
|
Id: "123",
|
|
AuthData: model.NewPointer("authData"),
|
|
AuthService: "authService",
|
|
}
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUser, err := cachedStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeUser, gotUser)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1)
|
|
|
|
_, _ = cachedStore.User().Get(context.Background(), fakeUserId)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1)
|
|
})
|
|
|
|
t.Run("first call not cached, invalidate, and then not cached again", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUser, err := cachedStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, fakeUser, gotUser)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1)
|
|
|
|
cachedStore.User().InvalidateProfileCacheForUser("123")
|
|
|
|
_, _ = cachedStore.User().Get(context.Background(), fakeUserId)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 2)
|
|
})
|
|
|
|
t.Run("should always return a copy of the stored data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
storedUser, err := mockStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
originalProps := storedUser.NotifyProps
|
|
|
|
storedUser.NotifyProps = map[string]string{}
|
|
storedUser.NotifyProps["key"] = "somevalue"
|
|
|
|
cachedUser, err := cachedStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, storedUser, cachedUser)
|
|
|
|
storedUser.Props = model.StringMap{}
|
|
storedUser.Timezone = model.StringMap{}
|
|
cachedUser, err = cachedStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, storedUser, cachedUser)
|
|
if storedUser == cachedUser {
|
|
assert.Fail(t, "should be different pointers")
|
|
}
|
|
cachedUser.NotifyProps["key"] = "othervalue"
|
|
assert.NotEqual(t, storedUser, cachedUser)
|
|
|
|
storedUser.NotifyProps = originalProps
|
|
})
|
|
|
|
t.Run("assert **model.User not passed", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
cmock := cmocks.NewCache(t)
|
|
cmock.On("Get", "123", mock.AnythingOfType("*model.User")).Return(nil)
|
|
|
|
cachedStore.user.rootStore.userProfileByIdsCache = cmock
|
|
|
|
_, err = cachedStore.User().Get(context.Background(), fakeUserId)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestUserStoreGetManyCache(t *testing.T) {
|
|
rctx := request.TestContext(t)
|
|
|
|
fakeUser := &model.User{
|
|
Id: "123",
|
|
AuthData: model.NewPointer("authData"),
|
|
AuthService: "authService",
|
|
}
|
|
otherFakeUser := &model.User{
|
|
Id: "456",
|
|
AuthData: model.NewPointer("authData"),
|
|
AuthService: "authService",
|
|
}
|
|
logger := mlog.CreateConsoleTestLogger(t)
|
|
t.Run("first call not cached, second cached and returning same data", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUsers, err := cachedStore.User().GetMany(rctx, []string{fakeUser.Id, otherFakeUser.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, gotUsers, 2)
|
|
assert.Contains(t, gotUsers, fakeUser)
|
|
assert.Contains(t, gotUsers, otherFakeUser)
|
|
|
|
gotUsers, err = cachedStore.User().GetMany(rctx, []string{fakeUser.Id, otherFakeUser.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, gotUsers, 2)
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetMany", 1)
|
|
})
|
|
|
|
t.Run("first call not cached, invalidate one user, and then check that one is cached and one is fetched from db", func(t *testing.T) {
|
|
mockStore := getMockStore(t)
|
|
mockCacheProvider := getMockCacheProvider()
|
|
cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider, logger)
|
|
require.NoError(t, err)
|
|
|
|
gotUsers, err := cachedStore.User().GetMany(rctx, []string{fakeUser.Id, otherFakeUser.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, gotUsers, 2)
|
|
assert.Contains(t, gotUsers, fakeUser)
|
|
assert.Contains(t, gotUsers, otherFakeUser)
|
|
|
|
cachedStore.User().InvalidateProfileCacheForUser("123")
|
|
|
|
gotUsers, err = cachedStore.User().GetMany(rctx, []string{fakeUser.Id, otherFakeUser.Id})
|
|
require.NoError(t, err)
|
|
assert.Len(t, gotUsers, 2)
|
|
mockStore.User().(*mocks.UserStore).AssertCalled(t, "GetMany", mock.Anything, []string{"123"})
|
|
mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetMany", 2)
|
|
})
|
|
}
|