github.com/mattermost/mattermost-server/v5@v5.39.3/store/sqlstore/upgrade_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package sqlstore 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/mattermost/mattermost-server/v5/model" 11 "github.com/stretchr/testify/assert" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/mattermost/mattermost-server/v5/store" 16 ) 17 18 func TestStoreUpgradeDotRelease(t *testing.T) { 19 StoreTest(t, func(t *testing.T, ss store.Store) { 20 sqlStore := ss.(*SqlStore) 21 saveSchemaVersion(sqlStore, "5.33.1") 22 err := upgradeDatabase(sqlStore, CurrentSchemaVersion) 23 require.NoError(t, err) 24 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 25 }) 26 } 27 28 func TestStoreUpgrade(t *testing.T) { 29 StoreTest(t, func(t *testing.T, ss store.Store) { 30 sqlStore := ss.(*SqlStore) 31 32 t.Run("invalid currentModelVersion", func(t *testing.T) { 33 err := upgradeDatabase(sqlStore, "notaversion") 34 require.EqualError(t, err, "failed to parse current model version notaversion: No Major.Minor.Patch elements found") 35 }) 36 37 t.Run("upgrade from invalid version", func(t *testing.T) { 38 saveSchemaVersion(sqlStore, "invalid") 39 err := upgradeDatabase(sqlStore, "5.8.0") 40 require.EqualError(t, err, "failed to parse database schema version invalid: No Major.Minor.Patch elements found") 41 require.Equal(t, "invalid", sqlStore.GetCurrentSchemaVersion()) 42 }) 43 44 t.Run("upgrade from unsupported version", func(t *testing.T) { 45 saveSchemaVersion(sqlStore, "2.0.0") 46 err := upgradeDatabase(sqlStore, "5.8.0") 47 require.EqualError(t, err, "Database schema version 2.0.0 is no longer supported. This Mattermost server supports automatic upgrades from schema version 3.0.0 through schema version 5.8.0. Please manually upgrade to at least version 3.0.0 before continuing.") 48 require.Equal(t, "2.0.0", sqlStore.GetCurrentSchemaVersion()) 49 }) 50 51 t.Run("upgrade from earliest supported version", func(t *testing.T) { 52 saveSchemaVersion(sqlStore, Version300) 53 err := upgradeDatabase(sqlStore, CurrentSchemaVersion) 54 require.NoError(t, err) 55 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 56 }) 57 58 t.Run("upgrade from no existing version", func(t *testing.T) { 59 saveSchemaVersion(sqlStore, "") 60 err := upgradeDatabase(sqlStore, CurrentSchemaVersion) 61 require.NoError(t, err) 62 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 63 }) 64 65 t.Run("upgrade schema running earlier minor version", func(t *testing.T) { 66 saveSchemaVersion(sqlStore, "5.1.0") 67 err := upgradeDatabase(sqlStore, "5.8.0") 68 require.NoError(t, err) 69 // Assert CurrentSchemaVersion, not 5.8.0, since the migrations will move 70 // past 5.8.0 regardless of the input parameter. 71 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 72 }) 73 74 t.Run("upgrade schema running later minor version", func(t *testing.T) { 75 saveSchemaVersion(sqlStore, "5.99.0") 76 err := upgradeDatabase(sqlStore, "5.8.0") 77 require.NoError(t, err) 78 require.Equal(t, "5.99.0", sqlStore.GetCurrentSchemaVersion()) 79 }) 80 81 t.Run("upgrade schema running earlier major version", func(t *testing.T) { 82 saveSchemaVersion(sqlStore, "4.1.0") 83 err := upgradeDatabase(sqlStore, CurrentSchemaVersion) 84 require.NoError(t, err) 85 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 86 }) 87 88 t.Run("upgrade schema running later major version", func(t *testing.T) { 89 saveSchemaVersion(sqlStore, "6.0.0") 90 err := upgradeDatabase(sqlStore, "5.8.0") 91 require.EqualError(t, err, "Database schema version 6.0.0 is not supported. This Mattermost server supports only >=5.8.0, <6.0.0. Please upgrade to at least version 6.0.0 before continuing.") 92 require.Equal(t, "6.0.0", sqlStore.GetCurrentSchemaVersion()) 93 }) 94 }) 95 } 96 97 func TestSaveSchemaVersion(t *testing.T) { 98 StoreTest(t, func(t *testing.T, ss store.Store) { 99 sqlStore := ss.(*SqlStore) 100 101 t.Run("set earliest version", func(t *testing.T) { 102 saveSchemaVersion(sqlStore, Version300) 103 props, err := ss.System().Get() 104 require.NoError(t, err) 105 106 require.Equal(t, Version300, props["Version"]) 107 require.Equal(t, Version300, sqlStore.GetCurrentSchemaVersion()) 108 }) 109 110 t.Run("set current version", func(t *testing.T) { 111 saveSchemaVersion(sqlStore, CurrentSchemaVersion) 112 props, err := ss.System().Get() 113 require.NoError(t, err) 114 115 require.Equal(t, CurrentSchemaVersion, props["Version"]) 116 require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion()) 117 }) 118 }) 119 } 120 121 func createChannelMemberWithLastViewAt(ss store.Store, channelId, userId string, lastViewAt int64) *model.ChannelMember { 122 m := model.ChannelMember{} 123 m.ChannelId = channelId 124 m.UserId = userId 125 m.LastViewedAt = lastViewAt 126 m.NotifyProps = model.GetDefaultChannelNotifyProps() 127 cm, _ := ss.Channel().SaveMember(&m) 128 return cm 129 } 130 func createPostWithTimestamp(ss store.Store, channelId, userId, rootId, parentId string, timestamp int64) *model.Post { 131 m := model.Post{} 132 m.CreateAt = timestamp 133 m.ChannelId = channelId 134 m.UserId = userId 135 m.RootId = rootId 136 m.ParentId = parentId 137 m.Message = "zz" + model.NewId() + "b" 138 p, _ := ss.Post().Save(&m) 139 return p 140 } 141 142 func createChannelWithLastPostAt(ss store.Store, teamId, creatorId string, lastPostAt, msgCount, rootCount int64) (*model.Channel, error) { 143 m := model.Channel{} 144 m.TeamId = teamId 145 m.TotalMsgCount = msgCount 146 m.TotalMsgCountRoot = rootCount 147 m.LastPostAt = lastPostAt 148 m.CreatorId = creatorId 149 m.DisplayName = "Name" 150 m.Name = "zz" + model.NewId() + "b" 151 m.Type = model.CHANNEL_OPEN 152 return ss.Channel().Save(&m, -1) 153 } 154 155 func TestMsgCountRootMigration(t *testing.T) { 156 type TestCaseChannel struct { 157 Name string 158 PostTimes []int64 159 ReplyTimes []int64 160 MembershipsLastViewAt []int64 161 ExpectedMembershipMsgCountRoot []int64 162 } 163 type TestTableEntry struct { 164 name string 165 data []TestCaseChannel 166 } 167 testTable := []TestTableEntry{ 168 { 169 name: "test1", 170 data: []TestCaseChannel{ 171 { 172 Name: "channel with one post", 173 PostTimes: []int64{1000}, 174 ReplyTimes: []int64{0}, 175 MembershipsLastViewAt: []int64{1}, 176 ExpectedMembershipMsgCountRoot: []int64{0}, 177 }, 178 { 179 Name: "channel with one post, read", 180 PostTimes: []int64{1000}, 181 ReplyTimes: []int64{0}, 182 MembershipsLastViewAt: []int64{1000}, 183 ExpectedMembershipMsgCountRoot: []int64{1}, 184 }, 185 { 186 Name: "with one reply, viewed after 2nd root", 187 PostTimes: []int64{1000, 2000, 3000, 4000}, 188 ReplyTimes: []int64{1001, 0, 0, 0}, 189 MembershipsLastViewAt: []int64{2001}, 190 ExpectedMembershipMsgCountRoot: []int64{0}, 191 }, 192 { 193 Name: "two replies, 3 memberships", 194 PostTimes: []int64{1000, 2000, 3000}, 195 ReplyTimes: []int64{1001, 2001, 0}, 196 MembershipsLastViewAt: []int64{2000, 5000, 0}, 197 ExpectedMembershipMsgCountRoot: []int64{0, 3, 0}, 198 }, 199 }, 200 }, 201 } 202 for _, testCase := range testTable { 203 t.Run(testCase.name, func(t *testing.T) { 204 StoreTest(t, func(t *testing.T, ss store.Store) { 205 sqlStore := ss.(*SqlStore) 206 team := createTeam(ss) 207 for _, testChannel := range testCase.data { 208 t.Run(testChannel.Name, func(t *testing.T) { 209 lastPostAt := int64(0) 210 for i := range testChannel.PostTimes { 211 if testChannel.PostTimes[i] > lastPostAt { 212 lastPostAt = testChannel.PostTimes[i] 213 } 214 if testChannel.ReplyTimes[i] > lastPostAt { 215 lastPostAt = testChannel.ReplyTimes[i] 216 } 217 } 218 channel, err := createChannelWithLastPostAt(ss, team.Id, model.NewId(), lastPostAt, int64(len(testChannel.PostTimes)+len(testChannel.ReplyTimes)), int64(len(testChannel.PostTimes))) 219 require.NoError(t, err) 220 var userIds []string 221 for _, md := range testChannel.MembershipsLastViewAt { 222 user := createUser(ss) 223 userIds = append(userIds, user.Id) 224 require.NotNil(t, user) 225 cm := createChannelMemberWithLastViewAt(ss, channel.Id, user.Id, md) 226 require.NotNil(t, cm) 227 } 228 for i, pt := range testChannel.PostTimes { 229 rt := testChannel.ReplyTimes[i] 230 post := createPostWithTimestamp(ss, channel.Id, model.NewId(), "", "", pt) 231 require.NotNil(t, post) 232 if rt > 0 { 233 reply := createPostWithTimestamp(ss, channel.Id, model.NewId(), post.Id, post.Id, rt) 234 require.NotNil(t, reply) 235 } 236 } 237 238 _, err = sqlStore.GetMaster().Exec(`ALTER TABLE Channels DROP COLUMN TotalMsgCountRoot`) 239 require.NoError(t, err) 240 _, err = sqlStore.GetMaster().Exec(`ALTER TABLE ChannelMembers DROP COLUMN MsgCountRoot`) 241 require.NoError(t, err) 242 rootCountMigration(sqlStore) 243 244 members, err := ss.Channel().GetMembersByIds(channel.Id, userIds) 245 require.NoError(t, err) 246 247 for _, m := range *members { 248 for i, uid := range userIds { 249 if m.UserId == uid { 250 assert.Equal(t, testChannel.ExpectedMembershipMsgCountRoot[i], m.MsgCountRoot) 251 break 252 } 253 } 254 } 255 256 }) 257 } 258 }) 259 }) 260 } 261 } 262 263 func TestFixCRTCountsAndUnreads(t *testing.T) { 264 StoreTest(t, func(t *testing.T, ss store.Store) { 265 sqlStore := ss.(*SqlStore) 266 267 team := createTeam(ss) 268 uId1 := model.NewId() 269 uId2 := model.NewId() 270 c1, err := createChannelWithLastPostAt(ss, team.Id, uId1, 0, 0, 0) 271 require.NoError(t, err) 272 createChannelMemberWithLastViewAt(ss, c1.Id, uId1, 0) 273 createChannelMemberWithLastViewAt(ss, c1.Id, uId2, 0) 274 275 // Create a thread 276 // user2: root post 1 277 // - user1: reply 1 to root post 1 278 // - user2: reply 2 to root post 1 279 // - user1: reply 3 to root post 1 280 // - user2: reply 4 to root post 1 281 rootPost1 := createPostWithTimestamp(ss, c1.Id, uId2, "", "", 1) 282 lastReplyAt := int64(40) 283 _ = createPostWithTimestamp(ss, c1.Id, uId1, rootPost1.Id, rootPost1.Id, 10) 284 _ = createPostWithTimestamp(ss, c1.Id, uId2, rootPost1.Id, rootPost1.Id, 20) 285 _ = createPostWithTimestamp(ss, c1.Id, uId1, rootPost1.Id, rootPost1.Id, 30) 286 _ = createPostWithTimestamp(ss, c1.Id, uId2, rootPost1.Id, rootPost1.Id, lastReplyAt) 287 288 // Check created thread is good 289 goodThread1, err := ss.Thread().Get(rootPost1.Id) 290 require.NoError(t, err) 291 require.EqualValues(t, 4, goodThread1.ReplyCount) 292 require.EqualValues(t, lastReplyAt, goodThread1.LastReplyAt) 293 require.ElementsMatch(t, model.StringArray{uId1, uId2}, goodThread1.Participants) 294 295 // Create ThreadMembership 296 goodThreadMembership1, err := ss.Thread().SaveMembership(&model.ThreadMembership{ 297 PostId: rootPost1.Id, 298 UserId: uId1, 299 Following: true, 300 LastViewed: lastReplyAt + 1, 301 LastUpdated: lastReplyAt + 1, 302 UnreadMentions: 0, 303 }) 304 require.NoError(t, err) 305 goodThreadMembership2, err := ss.Thread().SaveMembership(&model.ThreadMembership{ 306 PostId: rootPost1.Id, 307 UserId: uId2, 308 Following: true, 309 LastViewed: lastReplyAt + 1, 310 LastUpdated: lastReplyAt + 1, 311 UnreadMentions: 0, 312 }) 313 require.NoError(t, err) 314 315 // Update channel last viewed at 316 // set channel as fully read for user1 317 _, err = ss.Channel().UpdateLastViewedAt([]string{c1.Id}, uId1, true) 318 require.NoError(t, err) 319 // for user2 set channel as read before the last post in thread 320 // user2's threadmembership wont change 321 cm2, err := ss.Channel().GetMember(context.Background(), c1.Id, uId2) 322 require.NoError(t, err) 323 cm2.LastViewedAt = lastReplyAt - 10 324 cm2, err = ss.Channel().UpdateMember(cm2) 325 require.NoError(t, err) 326 327 // Update ThreadMembership with bad data, as we might expect because 328 // of previous bugs or changed behaviour 329 badThreadMembership1 := *goodThreadMembership1 330 badThreadMembership1.LastViewed = 30 331 badThreadMembership1.UnreadMentions = 4 332 _, err = ss.Thread().UpdateMembership(&badThreadMembership1) 333 require.NoError(t, err) 334 335 // Run migration to fix threads and memberships 336 ss.System().PermanentDeleteByName("CRTThreadCountsAndUnreadsMigrationComplete") 337 fixCRTThreadCountsAndUnreads(sqlStore) 338 339 // Check bad threadMemberships is fixed 340 fixedThreadMembership1, err := ss.Thread().GetMembershipForUser(uId1, rootPost1.Id) 341 require.NoError(t, err) 342 require.EqualValues(t, lastReplyAt+1, fixedThreadMembership1.LastViewed) 343 require.EqualValues(t, int64(0), fixedThreadMembership1.UnreadMentions) 344 require.NotEqual(t, goodThreadMembership1.LastUpdated, fixedThreadMembership1.LastUpdated) 345 346 // check good threadMembership is unchanged 347 fixedThreadMembership2, err := ss.Thread().GetMembershipForUser(uId2, rootPost1.Id) 348 require.NoError(t, err) 349 require.Equal(t, goodThreadMembership2, fixedThreadMembership2) 350 }) 351 } 352 353 func TestFixCRTChannelUnreads(t *testing.T) { 354 StoreTest(t, func(t *testing.T, ss store.Store) { 355 sqlStore := ss.(*SqlStore) 356 357 team := createTeam(ss) 358 uId1 := model.NewId() 359 uId2 := model.NewId() 360 channelLastPostAt := int64(100) 361 channelMsgCount := int64(200) 362 channelMsgCountRoot := int64(100) 363 c1, err := createChannelWithLastPostAt(ss, team.Id, uId1, channelLastPostAt, channelMsgCount, channelMsgCountRoot) 364 require.NoError(t, err) 365 366 // Make a membership entry 367 cm1, err := ss.Channel().SaveMember(&model.ChannelMember{ 368 ChannelId: c1.Id, 369 UserId: uId1, 370 LastViewedAt: channelLastPostAt - 50, 371 NotifyProps: model.GetDefaultChannelNotifyProps(), 372 MsgCount: 80, 373 MsgCountRoot: 40, 374 MentionCount: 5, 375 MentionCountRoot: 5, 376 }) 377 require.NoError(t, err) 378 379 // make a bad membership entry 380 // LastViewed at newer than channel LastPostAt and with unreads and mentions 381 cm2, err := ss.Channel().SaveMember(&model.ChannelMember{ 382 ChannelId: c1.Id, 383 UserId: uId2, 384 LastViewedAt: channelLastPostAt + 50, 385 NotifyProps: model.GetDefaultChannelNotifyProps(), 386 MsgCount: 80, 387 MsgCountRoot: 40, 388 MentionCount: 5, 389 MentionCountRoot: 5, 390 }) 391 require.NoError(t, err) 392 393 ss.System().PermanentDeleteByName("CRTChannelMembershipCountsMigrationComplete") 394 fixCRTChannelMembershipCounts(sqlStore) 395 396 cm1AfterFix, err := ss.Channel().GetMember(context.Background(), c1.Id, uId1) 397 require.NoError(t, err) 398 // Migration should not affect this channelmembership 399 require.Equal(t, *cm1, *cm1AfterFix) 400 401 cm2AfterFix, err := ss.Channel().GetMember(context.Background(), c1.Id, uId2) 402 require.NoError(t, err) 403 // Check that the channelmembership is fixed 404 require.NotEqual(t, *cm2, *cm2AfterFix) 405 require.EqualValues(t, 0, cm2AfterFix.MentionCount) 406 require.EqualValues(t, 0, cm2AfterFix.MentionCountRoot) 407 require.Equal(t, channelMsgCount, cm2AfterFix.MsgCount) 408 require.Equal(t, channelMsgCountRoot, cm2AfterFix.MsgCountRoot) 409 require.NotEqual(t, cm2.LastUpdateAt, cm2AfterFix.LastUpdateAt) 410 }) 411 }