Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

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)
})
}