mattermost-community-enterp.../enterprise/elasticsearch/opensearch/aggregation_job_test.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

225 lines
8.0 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.enterprise for license information.
package opensearch
import (
"bytes"
"context"
"encoding/json"
"os"
"testing"
"time"
"github.com/opensearch-project/opensearch-go/v4/opensearchapi"
"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/request"
"github.com/mattermost/mattermost/server/v8/channels/api4"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/mattermost/mattermost/server/v8/enterprise/elasticsearch/common"
)
func TestElasticsearchAggregation(t *testing.T) {
if os.Getenv("IS_CI") == "true" {
os.Setenv("MM_ELASTICSEARCHSETTINGS_CONNECTIONURL", "http://opensearch:9201")
os.Setenv("MM_ELASTICSEARCHSETTINGS_BACKEND", "opensearch")
}
defer func() {
if os.Getenv("IS_CI") == "true" {
os.Setenv("MM_ELASTICSEARCHSETTINGS_CONNECTIONURL", "http://elasticsearch:9201")
os.Unsetenv("MM_ELASTICSEARCHSETTINGS_BACKEND")
}
}()
th := api4.SetupEnterpriseWithStoreMock(t)
rctx := request.TestContext(t)
mockUserStore := mocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
mockUserStore.On("GetAllProfiles", mock.Anything).Return(nil, nil)
mockPostStore := mocks.PostStore{}
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
mockSystemStore := mocks.SystemStore{}
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
mockJobStore := mocks.JobStore{}
mockJobStore.On("Save", mock.AnythingOfType("*model.Job")).Return(&model.Job{}, nil)
mockJobStore.On("UpdateStatus", mock.AnythingOfType("string"), model.JobStatusSuccess).Return(&model.Job{}, nil)
mockJobStore.On("Get", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("string")).Return(&model.Job{
Status: model.JobStatusSuccess,
}, nil)
mockJobStore.On("UpdateStatusOptimistically",
mock.AnythingOfType("string"),
model.JobStatusPending,
model.JobStatusInProgress).
Return(&model.Job{}, nil)
mockJobStore.On("GetAllByType", mock.AnythingOfType("string")).Return([]*model.Job{{
Id: "abcxyz123",
Type: "EnterpriseElasticsearchIndexer",
Status: model.JobStatusCanceled,
}}, nil)
mockStore := th.App.Srv().Platform().Store.(*mocks.Store)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("Job").Return(&mockJobStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
aggImpl := OpensearchAggregatorInterfaceImpl{Server: th.Server}
// Register search engine
th.App.SearchEngine().RegisterElasticsearchEngine(&OpensearchInterfaceImpl{
Platform: th.Server.Platform(),
})
// Set up the state for the tests.
th.App.UpdateConfig(func(cfg *model.Config) {
if os.Getenv("IS_CI") == "true" {
*cfg.ElasticsearchSettings.ConnectionURL = "http://opensearch:9201"
} else {
*cfg.ElasticsearchSettings.ConnectionURL = "http://localhost:9201"
}
*cfg.ElasticsearchSettings.Backend = model.ElasticsearchSettingsOSBackend
*cfg.ElasticsearchSettings.EnableIndexing = true
*cfg.ElasticsearchSettings.EnableSearching = true
*cfg.ElasticsearchSettings.EnableAutocomplete = true
*cfg.ElasticsearchSettings.LiveIndexingBatchSize = 1
*cfg.ElasticsearchSettings.AggregatePostsAfterDays = 1
*cfg.SqlSettings.DisableDatabaseSearch = true
})
esImpl := th.App.SearchEngine().ElasticsearchEngine
appErr := esImpl.Start()
if appErr != nil && appErr.Id != "ent.elasticsearch.start.already_started.app_error" {
require.Fail(t, "failed to start elasticsearch", appErr)
}
require.Nil(t, esImpl.PurgeIndexes(rctx))
post := &model.Post{
Id: model.NewId(),
ChannelId: "channel",
Message: "hi",
}
for i := range indexDeletionBatchSize + 1 {
indexPost(t, th, esImpl.(*OpensearchInterfaceImpl),
post,
time.Now().Add(-time.Duration(4+i)*24*time.Hour))
}
job := &model.Job{
Id: model.NewId(),
Type: model.JobTypeElasticsearchPostAggregation,
Status: model.JobStatusPending,
}
_, err := th.Server.Store().Job().Save(job)
require.NoError(t, err)
worker := aggImpl.MakeWorker().(*OpensearchAggregatorWorker)
worker.client = createTestClient(t, th.Context, th.App.Config(), th.App.FileBackend())
worker.jobServer.Store = mockStore
indexingImpl := OpensearchIndexerInterfaceImpl{
Server: th.App.Srv(),
}
th.Server.Jobs.RegisterJobType(model.JobTypeElasticsearchPostIndexing, indexingImpl.MakeWorker(), nil)
worker.DoJob(job)
// We assert the minimum number of calls to verify that
// batching is working correctly. Because job().Get() will happen
// in each iteration.
numCalls := 0
for _, call := range mockJobStore.Calls {
if call.Method == "Get" {
numCalls++
}
}
assert.GreaterOrEqual(t, numCalls, 8, "Unexpected number of Jobstore.Get calls")
}
func TestElasticsearchAggregationSkipDuringBulkIndexing(t *testing.T) {
th := api4.SetupEnterpriseWithStoreMock(t)
mockUserStore := mocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(10), nil)
mockPostStore := mocks.PostStore{}
mockPostStore.On("GetMaxPostSize").Return(65535, nil)
mockSystemStore := mocks.SystemStore{}
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
mockJobStore := mocks.JobStore{}
mockStore := th.App.Srv().Platform().Store.(*mocks.Store)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("Job").Return(&mockJobStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
aggImpl := OpensearchAggregatorInterfaceImpl{Server: th.Server}
aggImpl.Server.Jobs.Store = mockStore
// Register search engine
th.App.SearchEngine().RegisterElasticsearchEngine(&OpensearchInterfaceImpl{
Platform: th.Server.Platform(),
})
// Set up the state for the tests.
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ElasticsearchSettings.EnableIndexing = true
*cfg.ElasticsearchSettings.EnableSearching = true
*cfg.ElasticsearchSettings.EnableAutocomplete = true
*cfg.ElasticsearchSettings.LiveIndexingBatchSize = 1
*cfg.ElasticsearchSettings.AggregatePostsAfterDays = 1
*cfg.SqlSettings.DisableDatabaseSearch = true
})
sched := aggImpl.MakeScheduler()
// Pass pending jobs as true
job, appErr := sched.ScheduleJob(th.Context, th.App.Config(), true, nil)
require.Nil(t, job)
require.Nil(t, appErr)
mockJobStore.AssertNotCalled(t, "GetCountByStatusAndType")
}
func indexPost(t *testing.T, th *api4.TestHelper, esImpl *OpensearchInterfaceImpl, post *model.Post, createTime time.Time) { //nolint:unused
t.Helper()
indexName := common.BuildPostIndexName(*th.Server.Config().ElasticsearchSettings.AggregatePostsAfterDays,
common.IndexBasePosts,
common.IndexBasePosts_MONTH,
createTime.Add(-1*24*time.Hour),
model.GetMillisForTime(createTime),
)
searchPost, err := common.ESPostFromPost(post, "teamID")
require.NoError(t, err)
ctx, cancel := context.WithTimeout(context.Background(),
time.Duration(*esImpl.Platform.Config().ElasticsearchSettings.RequestTimeoutSeconds)*time.Second)
defer cancel()
buf, err := json.Marshal(searchPost)
require.NoError(t, err)
_, err = esImpl.client.Index(ctx, opensearchapi.IndexReq{
Index: indexName,
DocumentID: post.Id,
Body: bytes.NewReader(buf),
})
require.NoError(t, err)
}