mirror of
https://github.com/hanzoai/tasks.git
synced 2026-11-03 03:56:04 +00:00
## What changed? Split `searchattribute.TestNameTypeMap` in two: one for ES setup, and one for SQL DB setup. ## Why? `searchattribute.TestNameTypeMap` is used in tests as the predefined list of custom search attributes. However, it mixes how custom search attributes work differently in Elasticsearch vs SQL databases. This PR splits the variable in two (one for each) so the tests will be a better representation of the actual system. ## How did you test it? - [x] built - [ ] run locally and tested manually - [x] covered by existing tests - [ ] added new unit test(s) - [ ] added new functional test(s) ## Potential risks
257 lines
8.2 KiB
Go
257 lines
8.2 KiB
Go
package archival
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
carchiver "go.temporal.io/server/common/archiver"
|
|
"go.temporal.io/server/common/archiver/provider"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/metrics"
|
|
"go.temporal.io/server/common/payload"
|
|
"go.temporal.io/server/common/persistence/visibility/manager"
|
|
"go.temporal.io/server/common/quotas"
|
|
"go.temporal.io/server/common/sdk"
|
|
"go.temporal.io/server/common/searchattribute"
|
|
"go.temporal.io/server/common/testing/mocksdk"
|
|
"go.temporal.io/server/service/history/configs"
|
|
"go.uber.org/fx"
|
|
"go.uber.org/mock/gomock"
|
|
"go.uber.org/multierr"
|
|
)
|
|
|
|
func TestArchiver(t *testing.T) {
|
|
for _, c := range []struct {
|
|
Name string
|
|
ArchiveHistoryErr error
|
|
ArchiveVisibilityErr error
|
|
RateLimiterWaitErr error
|
|
Targets []Target
|
|
SearchAttributes *commonpb.SearchAttributes
|
|
SearchAttributesErr error
|
|
NameTypeMap searchattribute.NameTypeMap
|
|
NameTypeMapErr error
|
|
NilHistoryUri bool
|
|
|
|
ExpectArchiveHistory bool
|
|
ExpectArchiveVisibility bool
|
|
ExpectedReturnErrors []string
|
|
}{
|
|
{
|
|
Name: "No targets",
|
|
Targets: []Target{},
|
|
},
|
|
{
|
|
Name: "Invalid target",
|
|
Targets: []Target{Target("oops")},
|
|
|
|
ExpectedReturnErrors: []string{
|
|
"unknown archival target: oops",
|
|
},
|
|
},
|
|
{
|
|
Name: "History archival succeeds",
|
|
Targets: []Target{TargetHistory},
|
|
ArchiveHistoryErr: nil,
|
|
|
|
ExpectArchiveHistory: true,
|
|
},
|
|
{
|
|
Name: "History archival fails",
|
|
Targets: []Target{TargetHistory},
|
|
ArchiveHistoryErr: errors.New("example archive history error"),
|
|
|
|
ExpectArchiveHistory: true,
|
|
ExpectedReturnErrors: []string{"example archive history err"},
|
|
},
|
|
{
|
|
Name: "Visibility archival succeeds",
|
|
Targets: []Target{TargetVisibility},
|
|
|
|
ExpectArchiveVisibility: true,
|
|
},
|
|
{
|
|
Name: "Visibility archival succeeds with nil HistoryURI",
|
|
Targets: []Target{TargetVisibility},
|
|
NilHistoryUri: true,
|
|
|
|
ExpectArchiveVisibility: true,
|
|
},
|
|
{
|
|
Name: "History succeeds but visibility fails",
|
|
Targets: []Target{TargetHistory, TargetVisibility},
|
|
ArchiveVisibilityErr: errors.New("example archive visibility error"),
|
|
|
|
ExpectArchiveHistory: true,
|
|
ExpectArchiveVisibility: true,
|
|
ExpectedReturnErrors: []string{"example archive visibility error"},
|
|
},
|
|
{
|
|
Name: "Visibility succeeds but history fails",
|
|
Targets: []Target{TargetHistory, TargetVisibility},
|
|
ArchiveHistoryErr: errors.New("example archive history error"),
|
|
|
|
ExpectArchiveHistory: true,
|
|
ExpectArchiveVisibility: true,
|
|
ExpectedReturnErrors: []string{"example archive history error"},
|
|
},
|
|
{
|
|
Name: "Both targets succeed",
|
|
Targets: []Target{TargetHistory, TargetVisibility},
|
|
|
|
ExpectArchiveHistory: true,
|
|
ExpectArchiveVisibility: true,
|
|
},
|
|
{
|
|
Name: "Rate limit hit",
|
|
Targets: []Target{TargetHistory, TargetVisibility},
|
|
RateLimiterWaitErr: errors.New("didn't acquire rate limiter tokens in time"),
|
|
|
|
ExpectedReturnErrors: []string{
|
|
"archival rate limited: didn't acquire rate limiter tokens in time",
|
|
},
|
|
},
|
|
{
|
|
Name: "Search attribute with no embedded type information",
|
|
Targets: []Target{TargetVisibility},
|
|
SearchAttributes: &commonpb.SearchAttributes{IndexedFields: map[string]*commonpb.Payload{
|
|
"Text01": payload.EncodeString("value"),
|
|
}},
|
|
NameTypeMap: searchattribute.TestNameTypeMap(),
|
|
|
|
ExpectArchiveVisibility: true,
|
|
},
|
|
{
|
|
Name: "Search attribute missing in type map",
|
|
Targets: []Target{TargetVisibility},
|
|
SearchAttributes: &commonpb.SearchAttributes{IndexedFields: map[string]*commonpb.Payload{
|
|
"Text01": payload.EncodeString("value"),
|
|
}},
|
|
NameTypeMap: searchattribute.NameTypeMap{},
|
|
|
|
ExpectedReturnErrors: []string{
|
|
"invalid search attribute type: Unspecified",
|
|
},
|
|
},
|
|
{
|
|
Name: "Error getting name type map from search attribute provider",
|
|
Targets: []Target{TargetVisibility},
|
|
SearchAttributes: &commonpb.SearchAttributes{IndexedFields: map[string]*commonpb.Payload{
|
|
"Text01": payload.EncodeString("value"),
|
|
}},
|
|
NameTypeMapErr: errors.New("name-type-map-err"),
|
|
|
|
ExpectedReturnErrors: []string{
|
|
"name-type-map-err",
|
|
},
|
|
},
|
|
} {
|
|
c := c // capture range variable
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx := context.Background()
|
|
controller := gomock.NewController(t)
|
|
archiverProvider := provider.NewMockArchiverProvider(controller)
|
|
historyArchiver := carchiver.NewMockHistoryArchiver(controller)
|
|
visibilityArchiver := carchiver.NewMockVisibilityArchiver(controller)
|
|
metricsHandler := metrics.NoopMetricsHandler
|
|
sdkClient := mocksdk.NewMockClient(controller)
|
|
sdkClientFactory := sdk.NewMockClientFactory(controller)
|
|
sdkClientFactory.EXPECT().GetSystemClient().Return(sdkClient).AnyTimes()
|
|
|
|
var historyURI carchiver.URI
|
|
var err error
|
|
if !c.NilHistoryUri {
|
|
historyURI, err = carchiver.NewURI("test:///history/archival")
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
if c.ExpectArchiveHistory {
|
|
archiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(historyArchiver, nil)
|
|
historyArchiver.EXPECT().Archive(gomock.Any(), historyURI, gomock.Any()).Return(c.ArchiveHistoryErr)
|
|
}
|
|
|
|
visibilityURI, err := carchiver.NewURI("test:///visibility/archival")
|
|
require.NoError(t, err)
|
|
archiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).
|
|
Return(visibilityArchiver, nil).AnyTimes()
|
|
|
|
if c.ExpectArchiveVisibility {
|
|
visibilityArchiver.EXPECT().Archive(gomock.Any(), visibilityURI, gomock.Any()).
|
|
Return(c.ArchiveVisibilityErr)
|
|
}
|
|
|
|
rateLimiter := quotas.NewMockRateLimiter(controller)
|
|
rateLimiter.EXPECT().WaitN(gomock.Any(), len(c.Targets)).Return(c.RateLimiterWaitErr)
|
|
|
|
searchAttributeProvider := searchattribute.NewMockProvider(controller)
|
|
searchAttributeProvider.EXPECT().GetSearchAttributes(gomock.Any(), gomock.Any()).Return(
|
|
c.NameTypeMap, c.NameTypeMapErr,
|
|
).AnyTimes()
|
|
|
|
visibilityManager := manager.NewMockVisibilityManager(controller)
|
|
visibilityManager.EXPECT().GetIndexName().Return("index-name").AnyTimes()
|
|
|
|
// we need this channel to get the Archiver which is created asynchronously
|
|
archivers := make(chan Archiver, 1)
|
|
// we make an app here so that we can test that the Module is working as intended
|
|
app := fx.New(
|
|
fx.Supply(fx.Annotate(archiverProvider, fx.As(new(provider.ArchiverProvider)))),
|
|
fx.Supply(fx.Annotate(log.NewNoopLogger(), fx.As(new(log.Logger)))),
|
|
fx.Supply(fx.Annotate(metricsHandler, fx.As(new(metrics.Handler)))),
|
|
fx.Supply(fx.Annotate(searchAttributeProvider, fx.As(new(searchattribute.Provider)))),
|
|
fx.Supply(fx.Annotate(visibilityManager, fx.As(new(manager.VisibilityManager)))),
|
|
fx.Supply(&configs.Config{
|
|
ArchivalBackendMaxRPS: func() float64 {
|
|
return 42.0
|
|
},
|
|
}),
|
|
Module,
|
|
fx.Decorate(func(rl quotas.RateLimiter) quotas.RateLimiter {
|
|
// we need to decorate the rate limiter so that we can use the mock
|
|
// we also verify that the rate being used is equal to the one in the config
|
|
assert.Equal(t, 42.0, rl.Rate())
|
|
return rateLimiter
|
|
}),
|
|
fx.Invoke(func(a Archiver) {
|
|
// after all parameters are provided, we get the Archiver and put it in the channel
|
|
// so that we can use it in the test
|
|
archivers <- a
|
|
}),
|
|
)
|
|
require.NoError(t, app.Err())
|
|
// we need to start the app for fx.Invoke to be called, so that we can get the Archiver
|
|
require.NoError(t, app.Start(ctx))
|
|
|
|
defer func() {
|
|
require.NoError(t, app.Stop(ctx))
|
|
}()
|
|
|
|
archiver := <-archivers
|
|
searchAttributes := c.SearchAttributes
|
|
_, err = archiver.Archive(ctx, &Request{
|
|
HistoryURI: historyURI,
|
|
VisibilityURI: visibilityURI,
|
|
Targets: c.Targets,
|
|
SearchAttributes: searchAttributes,
|
|
})
|
|
|
|
if len(c.ExpectedReturnErrors) > 0 {
|
|
require.Error(t, err)
|
|
assert.Len(t, multierr.Errors(err), len(c.ExpectedReturnErrors))
|
|
|
|
for _, e := range c.ExpectedReturnErrors {
|
|
assert.Contains(t, err.Error(), e)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|