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  }