mirror of
https://github.com/hanzoai/team.git
synced 2026-11-01 07:55:31 +00:00
* MM-68149: upgrade to Go 1.26.2 Update go directive in go.mod and .go-version. * MM-68149: replace pointer helpers with Go 1.26 new() Go 1.26 extends the built-in new() to accept an initial value expression, making typed-pointer helpers like model.NewPointer(x), bToP(x), and boolPtr(x) redundant. Replace every call site with new(x) and remove the now-unused helper functions and their //go:fix inline directives. * MM-68149: apply go fix for reflect API and format-string changes - reflect.Ptr → reflect.Pointer (renamed in Go 1.18, deprecated alias removed in 1.26) - reflect range-over-struct: for i := 0; i < t.NumField(); i++ → for field := range t.Fields() and the equivalent for Methods() and interface types - Fix format-string concatenation and variadic-arg mismatches flagged by go vet * MM-68149: update JPEG fixtures and test infrastructure for Go 1.26 encoder Go 1.26 ships a new image/jpeg encoder that produces slightly different output. Regenerate all JPEG fixture files and switch the comparison helpers from byte-equality to pixel-level comparison with a small per-channel tolerance, so minor encoder drift across patch versions is handled automatically. Add -update-fixtures flag to make it easy to regenerate fixtures after future major Go upgrades. Document the update procedure in tests/README.md. * MM-68149: CI check that go fix ./... produces no changes * Fix real bugs flagged by CodeRabbit review - group.go: set newGroup.MemberCount not group.MemberCount (member count was populated on the wrong variable and lost before publish/return) - file_test.go: guard compareImage(GetFilePreview) on the preview slice length, not the thumbnail slice length (copy-paste error) - config_test.go: remove duplicate MinimumLength assignment * fixup! Fix real bugs flagged by CodeRabbit review
297 lines
8.8 KiB
Go
297 lines
8.8 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.enterprise for license information.
|
|
|
|
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"maps"
|
|
"strconv"
|
|
"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/v8/channels/jobs"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
)
|
|
|
|
func TestBulkIndexChannelsWithDeletedChannels(t *testing.T) {
|
|
// Create test channels - one active, one deleted
|
|
activeChannel := &model.Channel{
|
|
Id: "ch1",
|
|
Type: model.ChannelTypeOpen,
|
|
DeleteAt: 0,
|
|
}
|
|
deletedChannel := &model.Channel{
|
|
Id: "ch2",
|
|
Type: model.ChannelTypeOpen,
|
|
DeleteAt: 123456,
|
|
}
|
|
channels := []*model.Channel{activeChannel, deletedChannel}
|
|
|
|
// Mock store
|
|
mockStore := &mocks.Store{}
|
|
mockChannelStore := &mocks.ChannelStore{}
|
|
mockStore.On("Channel").Return(mockChannelStore)
|
|
defer mockStore.AssertExpectations(t)
|
|
|
|
// Since these are open channels, GetAllChannelMemberIdsByChannelId won't be called
|
|
// But GetTeamMembersForChannel will be called for both channels
|
|
mockChannelStore.On("GetTeamMembersForChannel", mock.AnythingOfType("*request.Context"), "ch1").Return([]string{"team1"}, nil)
|
|
mockChannelStore.On("GetTeamMembersForChannel", mock.AnythingOfType("*request.Context"), "ch2").Return([]string{"team1"}, nil)
|
|
|
|
// Track which channels were actually indexed
|
|
indexedChannels := make(map[string]bool)
|
|
|
|
// Mock bulk processor function
|
|
addItemToBulkProcessorFn := func(_, op, id string, _ io.ReadSeeker) error {
|
|
assert.Equal(t, indexOp, op) // Should always be index, not delete
|
|
indexedChannels[id] = true
|
|
return nil
|
|
}
|
|
|
|
config := &model.Config{}
|
|
config.ElasticsearchSettings.IndexPrefix = new("test_")
|
|
|
|
// Call the function
|
|
lastChannel, appErr := BulkIndexChannels(config, mockStore, mlog.CreateConsoleTestLogger(t), addItemToBulkProcessorFn, channels, IndexingProgress{})
|
|
|
|
// Verify results
|
|
require.Nil(t, appErr)
|
|
assert.Equal(t, deletedChannel, lastChannel)
|
|
assert.True(t, indexedChannels["ch1"], "Active channel should be indexed")
|
|
assert.True(t, indexedChannels["ch2"], "Deleted channel should also be indexed")
|
|
}
|
|
|
|
func TestCurrentProgressCapsAt100(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
progress IndexingProgress
|
|
expected int64
|
|
}{
|
|
{
|
|
name: "normal progress",
|
|
progress: IndexingProgress{
|
|
DonePostsCount: 50,
|
|
TotalPostsCount: 100,
|
|
DoneChannelsCount: 0,
|
|
TotalChannelsCount: 100,
|
|
DoneUsersCount: 0,
|
|
TotalUsersCount: 100,
|
|
DoneFilesCount: 0,
|
|
TotalFilesCount: 100,
|
|
},
|
|
expected: 12,
|
|
},
|
|
{
|
|
name: "exactly 100%",
|
|
progress: IndexingProgress{
|
|
DonePostsCount: 100,
|
|
TotalPostsCount: 100,
|
|
DoneChannelsCount: 50,
|
|
TotalChannelsCount: 50,
|
|
DoneUsersCount: 10,
|
|
TotalUsersCount: 10,
|
|
DoneFilesCount: 5,
|
|
TotalFilesCount: 5,
|
|
},
|
|
expected: 100,
|
|
},
|
|
{
|
|
name: "all totals zero returns 100",
|
|
progress: IndexingProgress{},
|
|
expected: 100,
|
|
},
|
|
{
|
|
name: "done exceeds total, caps at 100",
|
|
progress: IndexingProgress{
|
|
DonePostsCount: 20000000,
|
|
TotalPostsCount: 10000000,
|
|
DoneChannelsCount: 0,
|
|
TotalChannelsCount: 100000,
|
|
DoneUsersCount: 0,
|
|
TotalUsersCount: 10000,
|
|
DoneFilesCount: 0,
|
|
TotalFilesCount: 100000,
|
|
},
|
|
expected: 100,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, tt.progress.CurrentProgress())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEntityCountFallback(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
jobData model.StringMap
|
|
dataKey string
|
|
estimate int64
|
|
doneCount int64
|
|
expected int64
|
|
}{
|
|
{
|
|
name: "no stored value, uses estimate",
|
|
jobData: model.StringMap{},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 0,
|
|
expected: 10000000,
|
|
},
|
|
{
|
|
name: "stored value preferred over estimate",
|
|
jobData: model.StringMap{"total_posts_count": "5000000"},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 0,
|
|
expected: 5000000,
|
|
},
|
|
{
|
|
name: "doneCount exceeds stored value",
|
|
jobData: model.StringMap{"total_posts_count": "5000000"},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 7000000,
|
|
expected: 7000000,
|
|
},
|
|
{
|
|
name: "doneCount exceeds estimate when no stored value",
|
|
jobData: model.StringMap{},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 15000000,
|
|
expected: 15000000,
|
|
},
|
|
{
|
|
name: "invalid stored value falls back to estimate",
|
|
jobData: model.StringMap{"total_posts_count": "not-a-number"},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 0,
|
|
expected: 10000000,
|
|
},
|
|
{
|
|
name: "zero stored value falls back to estimate",
|
|
jobData: model.StringMap{"total_posts_count": "0"},
|
|
dataKey: "total_posts_count",
|
|
estimate: 10000000,
|
|
doneCount: 0,
|
|
expected: 10000000,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
job := &model.Job{Data: tt.jobData}
|
|
result := entityCountFallback(job, tt.dataKey, tt.estimate, tt.doneCount)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetEntityCount(t *testing.T) {
|
|
type entityCountMocks struct {
|
|
store *mocks.Store
|
|
post *mocks.PostStore
|
|
channel *mocks.ChannelStore
|
|
user *mocks.UserStore
|
|
fileInfo *mocks.FileInfoStore
|
|
}
|
|
|
|
setupEntityCountMocks := func() entityCountMocks {
|
|
m := entityCountMocks{
|
|
store: &mocks.Store{},
|
|
post: &mocks.PostStore{},
|
|
channel: &mocks.ChannelStore{},
|
|
user: &mocks.UserStore{},
|
|
fileInfo: &mocks.FileInfoStore{},
|
|
}
|
|
m.store.On("Post").Return(m.post)
|
|
m.store.On("Channel").Return(m.channel)
|
|
m.store.On("User").Return(m.user)
|
|
m.store.On("FileInfo").Return(m.fileInfo)
|
|
return m
|
|
}
|
|
|
|
allEntitiesEnabled := model.StringMap{
|
|
"index_posts": "true",
|
|
"index_channels": "true",
|
|
"index_users": "true",
|
|
"index_files": "true",
|
|
}
|
|
|
|
t.Run("stores counts on success", func(t *testing.T) {
|
|
m := setupEntityCountMocks()
|
|
m.post.On("AnalyticsPostCount", mock.Anything).Return(int64(500), nil)
|
|
m.channel.On("AnalyticsTypeCount", "", model.ChannelType("")).Return(int64(200), nil)
|
|
m.user.On("Count", mock.Anything).Return(int64(50), nil)
|
|
m.fileInfo.On("CountAll").Return(int64(300), nil)
|
|
|
|
job := &model.Job{Data: maps.Clone(allEntitiesEnabled)}
|
|
progress := setEntityCount(mlog.CreateConsoleTestLogger(t), &jobs.JobServer{Store: m.store}, IndexingProgress{}, job)
|
|
|
|
assert.Equal(t, int64(500), progress.TotalPostsCount)
|
|
assert.Equal(t, int64(200), progress.TotalChannelsCount)
|
|
assert.Equal(t, int64(50), progress.TotalUsersCount)
|
|
assert.Equal(t, int64(300), progress.TotalFilesCount)
|
|
|
|
assert.Equal(t, "500", job.Data["total_posts_count"])
|
|
assert.Equal(t, "200", job.Data["total_channels_count"])
|
|
assert.Equal(t, "50", job.Data["total_users_count"])
|
|
assert.Equal(t, "300", job.Data["total_files_count"])
|
|
})
|
|
|
|
t.Run("falls back to job data on query failure", func(t *testing.T) {
|
|
m := setupEntityCountMocks()
|
|
m.post.On("AnalyticsPostCount", mock.Anything).Return(int64(0), fmt.Errorf("timeout"))
|
|
m.channel.On("AnalyticsTypeCount", "", model.ChannelType("")).Return(int64(0), fmt.Errorf("timeout"))
|
|
m.user.On("Count", mock.Anything).Return(int64(0), fmt.Errorf("timeout"))
|
|
m.fileInfo.On("CountAll").Return(int64(0), fmt.Errorf("timeout"))
|
|
|
|
jobData := maps.Clone(allEntitiesEnabled)
|
|
jobData["total_posts_count"] = "8000000"
|
|
jobData["total_channels_count"] = "50000"
|
|
jobData["total_users_count"] = "5000"
|
|
jobData["total_files_count"] = "75000"
|
|
job := &model.Job{Data: jobData}
|
|
|
|
progress := setEntityCount(mlog.CreateConsoleTestLogger(t), &jobs.JobServer{Store: m.store}, IndexingProgress{}, job)
|
|
|
|
assert.Equal(t, int64(8000000), progress.TotalPostsCount)
|
|
assert.Equal(t, int64(50000), progress.TotalChannelsCount)
|
|
assert.Equal(t, int64(5000), progress.TotalUsersCount)
|
|
assert.Equal(t, int64(75000), progress.TotalFilesCount)
|
|
})
|
|
|
|
t.Run("uses max of fallback and done count", func(t *testing.T) {
|
|
m := setupEntityCountMocks()
|
|
m.post.On("AnalyticsPostCount", mock.Anything).Return(int64(0), fmt.Errorf("timeout"))
|
|
|
|
job := &model.Job{
|
|
Data: model.StringMap{
|
|
"index_posts": "true",
|
|
"index_channels": "false",
|
|
"index_users": "false",
|
|
"index_files": "false",
|
|
"total_posts_count": strconv.FormatInt(estimatedPostCount, 10),
|
|
},
|
|
}
|
|
inputProgress := IndexingProgress{
|
|
DonePostsCount: estimatedPostCount + 5000000,
|
|
}
|
|
|
|
progress := setEntityCount(mlog.CreateConsoleTestLogger(t), &jobs.JobServer{Store: m.store}, inputProgress, job)
|
|
|
|
assert.Equal(t, int64(estimatedPostCount+5000000), progress.TotalPostsCount)
|
|
})
|
|
}
|