1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2// See LICENSE.txt for license information. 3 4package localcachelayer 5 6import ( 7 "context" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/mattermost/mattermost-server/v6/model" 14 "github.com/mattermost/mattermost-server/v6/plugin/plugintest/mock" 15 "github.com/mattermost/mattermost-server/v6/store" 16 "github.com/mattermost/mattermost-server/v6/store/storetest" 17 "github.com/mattermost/mattermost-server/v6/store/storetest/mocks" 18) 19 20func TestUserStore(t *testing.T) { 21 StoreTestWithSqlStore(t, storetest.TestUserStore) 22} 23 24func TestUserStoreCache(t *testing.T) { 25 fakeUserIds := []string{"123"} 26 fakeUser := []*model.User{{ 27 Id: "123", 28 AuthData: model.NewString("authData"), 29 AuthService: "authService", 30 }} 31 32 t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { 33 mockStore := getMockStore() 34 mockCacheProvider := getMockCacheProvider() 35 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 36 require.NoError(t, err) 37 38 gotUser, err := cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 39 require.NoError(t, err) 40 assert.Equal(t, fakeUser, gotUser) 41 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1) 42 43 _, _ = cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 44 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1) 45 }) 46 47 t.Run("first call not cached, second force not cached", func(t *testing.T) { 48 mockStore := getMockStore() 49 mockCacheProvider := getMockCacheProvider() 50 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 51 require.NoError(t, err) 52 53 gotUser, err := cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 54 require.NoError(t, err) 55 assert.Equal(t, fakeUser, gotUser) 56 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 1) 57 58 _, _ = cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, false) 59 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 2) 60 }) 61 62 t.Run("first call not cached, invalidate, and then not cached again", func(t *testing.T) { 63 mockStore := getMockStore() 64 mockCacheProvider := getMockCacheProvider() 65 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 66 require.NoError(t, err) 67 68 gotUser, err := cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 69 require.NoError(t, err) 70 assert.Equal(t, fakeUser, gotUser) 71 72 cachedStore.User().InvalidateProfileCacheForUser("123") 73 74 _, _ = cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 75 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetProfileByIds", 2) 76 }) 77 78 t.Run("should always return a copy of the stored data", func(t *testing.T) { 79 mockStore := getMockStore() 80 mockCacheProvider := getMockCacheProvider() 81 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 82 require.NoError(t, err) 83 84 storedUsers, err := mockStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, false) 85 require.NoError(t, err) 86 87 originalProps := make([]model.StringMap, len(storedUsers)) 88 89 for i := 0; i < len(storedUsers); i++ { 90 originalProps[i] = storedUsers[i].NotifyProps 91 storedUsers[i].NotifyProps = map[string]string{} 92 storedUsers[i].NotifyProps["key"] = "somevalue" 93 } 94 95 cachedUsers, err := cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 96 require.NoError(t, err) 97 98 for i := 0; i < len(storedUsers); i++ { 99 assert.Equal(t, storedUsers[i].Id, cachedUsers[i].Id) 100 } 101 102 cachedUsers, err = cachedStore.User().GetProfileByIds(context.Background(), fakeUserIds, &store.UserGetByIdsOpts{}, true) 103 require.NoError(t, err) 104 for i := 0; i < len(storedUsers); i++ { 105 storedUsers[i].Props = model.StringMap{} 106 storedUsers[i].Timezone = model.StringMap{} 107 assert.Equal(t, storedUsers[i], cachedUsers[i]) 108 if storedUsers[i] == cachedUsers[i] { 109 assert.Fail(t, "should be different pointers") 110 } 111 cachedUsers[i].NotifyProps["key"] = "othervalue" 112 assert.NotEqual(t, storedUsers[i], cachedUsers[i]) 113 } 114 115 for i := 0; i < len(storedUsers); i++ { 116 storedUsers[i].NotifyProps = originalProps[i] 117 } 118 }) 119} 120 121func TestUserStoreProfilesInChannelCache(t *testing.T) { 122 fakeChannelId := "123" 123 fakeUserId := "456" 124 fakeMap := map[string]*model.User{ 125 fakeUserId: {Id: "456"}, 126 } 127 128 t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { 129 mockStore := getMockStore() 130 mockCacheProvider := getMockCacheProvider() 131 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 132 require.NoError(t, err) 133 134 gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 135 require.NoError(t, err) 136 assert.Equal(t, fakeMap, gotMap) 137 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1) 138 139 _, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 140 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1) 141 }) 142 143 t.Run("first call not cached, second force not cached", func(t *testing.T) { 144 mockStore := getMockStore() 145 mockCacheProvider := getMockCacheProvider() 146 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 147 require.NoError(t, err) 148 149 gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 150 require.NoError(t, err) 151 assert.Equal(t, fakeMap, gotMap) 152 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1) 153 154 _, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, false) 155 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2) 156 }) 157 158 t.Run("first call not cached, invalidate by channel, and then not cached again", func(t *testing.T) { 159 mockStore := getMockStore() 160 mockCacheProvider := getMockCacheProvider() 161 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 162 require.NoError(t, err) 163 164 gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 165 require.NoError(t, err) 166 assert.Equal(t, fakeMap, gotMap) 167 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1) 168 169 cachedStore.User().InvalidateProfilesInChannelCache("123") 170 171 _, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 172 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2) 173 }) 174 175 t.Run("first call not cached, invalidate by user, and then not cached again", func(t *testing.T) { 176 mockStore := getMockStore() 177 mockCacheProvider := getMockCacheProvider() 178 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 179 require.NoError(t, err) 180 181 gotMap, err := cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 182 require.NoError(t, err) 183 assert.Equal(t, fakeMap, gotMap) 184 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 1) 185 186 cachedStore.User().InvalidateProfilesInChannelCacheByUser("456") 187 188 _, _ = cachedStore.User().GetAllProfilesInChannel(context.Background(), fakeChannelId, true) 189 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetAllProfilesInChannel", 2) 190 }) 191} 192 193func TestUserStoreGetCache(t *testing.T) { 194 fakeUserId := "123" 195 fakeUser := &model.User{ 196 Id: "123", 197 AuthData: model.NewString("authData"), 198 AuthService: "authService", 199 } 200 t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { 201 mockStore := getMockStore() 202 mockCacheProvider := getMockCacheProvider() 203 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 204 require.NoError(t, err) 205 206 gotUser, err := cachedStore.User().Get(context.Background(), fakeUserId) 207 require.NoError(t, err) 208 assert.Equal(t, fakeUser, gotUser) 209 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1) 210 211 _, _ = cachedStore.User().Get(context.Background(), fakeUserId) 212 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1) 213 }) 214 215 t.Run("first call not cached, invalidate, and then not cached again", func(t *testing.T) { 216 mockStore := getMockStore() 217 mockCacheProvider := getMockCacheProvider() 218 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 219 require.NoError(t, err) 220 221 gotUser, err := cachedStore.User().Get(context.Background(), fakeUserId) 222 require.NoError(t, err) 223 assert.Equal(t, fakeUser, gotUser) 224 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 1) 225 226 cachedStore.User().InvalidateProfileCacheForUser("123") 227 228 _, _ = cachedStore.User().Get(context.Background(), fakeUserId) 229 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "Get", 2) 230 }) 231 232 t.Run("should always return a copy of the stored data", func(t *testing.T) { 233 mockStore := getMockStore() 234 mockCacheProvider := getMockCacheProvider() 235 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 236 require.NoError(t, err) 237 238 storedUser, err := mockStore.User().Get(context.Background(), fakeUserId) 239 require.NoError(t, err) 240 originalProps := storedUser.NotifyProps 241 242 storedUser.NotifyProps = map[string]string{} 243 storedUser.NotifyProps["key"] = "somevalue" 244 245 cachedUser, err := cachedStore.User().Get(context.Background(), fakeUserId) 246 require.NoError(t, err) 247 assert.Equal(t, storedUser, cachedUser) 248 249 storedUser.Props = model.StringMap{} 250 storedUser.Timezone = model.StringMap{} 251 cachedUser, err = cachedStore.User().Get(context.Background(), fakeUserId) 252 require.NoError(t, err) 253 assert.Equal(t, storedUser, cachedUser) 254 if storedUser == cachedUser { 255 assert.Fail(t, "should be different pointers") 256 } 257 cachedUser.NotifyProps["key"] = "othervalue" 258 assert.NotEqual(t, storedUser, cachedUser) 259 260 storedUser.NotifyProps = originalProps 261 }) 262} 263 264func TestUserStoreGetManyCache(t *testing.T) { 265 fakeUser := &model.User{ 266 Id: "123", 267 AuthData: model.NewString("authData"), 268 AuthService: "authService", 269 } 270 otherFakeUser := &model.User{ 271 Id: "456", 272 AuthData: model.NewString("authData"), 273 AuthService: "authService", 274 } 275 t.Run("first call not cached, second cached and returning same data", func(t *testing.T) { 276 mockStore := getMockStore() 277 mockCacheProvider := getMockCacheProvider() 278 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 279 require.NoError(t, err) 280 281 gotUsers, err := cachedStore.User().GetMany(context.Background(), []string{fakeUser.Id, otherFakeUser.Id}) 282 require.NoError(t, err) 283 assert.Len(t, gotUsers, 2) 284 assert.Contains(t, gotUsers, fakeUser) 285 assert.Contains(t, gotUsers, otherFakeUser) 286 287 gotUsers, err = cachedStore.User().GetMany(context.Background(), []string{fakeUser.Id, otherFakeUser.Id}) 288 require.NoError(t, err) 289 assert.Len(t, gotUsers, 2) 290 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetMany", 1) 291 }) 292 293 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) { 294 mockStore := getMockStore() 295 mockCacheProvider := getMockCacheProvider() 296 cachedStore, err := NewLocalCacheLayer(mockStore, nil, nil, mockCacheProvider) 297 require.NoError(t, err) 298 299 gotUsers, err := cachedStore.User().GetMany(context.Background(), []string{fakeUser.Id, otherFakeUser.Id}) 300 require.NoError(t, err) 301 assert.Len(t, gotUsers, 2) 302 assert.Contains(t, gotUsers, fakeUser) 303 assert.Contains(t, gotUsers, otherFakeUser) 304 305 cachedStore.User().InvalidateProfileCacheForUser("123") 306 307 gotUsers, err = cachedStore.User().GetMany(context.Background(), []string{fakeUser.Id, otherFakeUser.Id}) 308 require.NoError(t, err) 309 assert.Len(t, gotUsers, 2) 310 mockStore.User().(*mocks.UserStore).AssertCalled(t, "GetMany", mock.Anything, []string{"123"}) 311 mockStore.User().(*mocks.UserStore).AssertNumberOfCalls(t, "GetMany", 2) 312 }) 313} 314