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