go.temporal.io/server@v1.23.0/common/archiver/s3store/visibility_archiver_test.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package s3store
    26  
    27  import (
    28  	"context"
    29  	"errors"
    30  	"fmt"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/aws/aws-sdk-go/aws"
    35  	"github.com/aws/aws-sdk-go/aws/awserr"
    36  	"github.com/aws/aws-sdk-go/aws/request"
    37  	"github.com/aws/aws-sdk-go/service/s3"
    38  	"github.com/golang/mock/gomock"
    39  	"github.com/stretchr/testify/require"
    40  	"github.com/stretchr/testify/suite"
    41  	enumspb "go.temporal.io/api/enums/v1"
    42  	"go.temporal.io/api/serviceerror"
    43  	"google.golang.org/protobuf/types/known/timestamppb"
    44  
    45  	"go.temporal.io/server/common/searchattribute"
    46  
    47  	archiverspb "go.temporal.io/server/api/archiver/v1"
    48  	"go.temporal.io/server/common/archiver"
    49  	"go.temporal.io/server/common/archiver/s3store/mocks"
    50  	"go.temporal.io/server/common/codec"
    51  	"go.temporal.io/server/common/convert"
    52  	"go.temporal.io/server/common/log"
    53  	"go.temporal.io/server/common/metrics"
    54  	"go.temporal.io/server/common/payload"
    55  	"go.temporal.io/server/common/primitives/timestamp"
    56  
    57  	commonpb "go.temporal.io/api/common/v1"
    58  	workflowpb "go.temporal.io/api/workflow/v1"
    59  )
    60  
    61  type visibilityArchiverSuite struct {
    62  	*require.Assertions
    63  	suite.Suite
    64  	s3cli *mocks.MockS3API
    65  
    66  	container         *archiver.VisibilityBootstrapContainer
    67  	visibilityRecords []*archiverspb.VisibilityRecord
    68  
    69  	controller      *gomock.Controller
    70  	testArchivalURI archiver.URI
    71  }
    72  
    73  func TestVisibilityArchiverSuite(t *testing.T) {
    74  	suite.Run(t, new(visibilityArchiverSuite))
    75  }
    76  
    77  func (s *visibilityArchiverSuite) TestValidateURI() {
    78  	testCases := []struct {
    79  		URI         string
    80  		expectedErr error
    81  	}{
    82  		{
    83  			URI:         "wrongscheme:///a/b/c",
    84  			expectedErr: archiver.ErrURISchemeMismatch,
    85  		},
    86  		{
    87  			URI:         "s3://",
    88  			expectedErr: errNoBucketSpecified,
    89  		},
    90  		{
    91  			URI:         "s3:///test",
    92  			expectedErr: errNoBucketSpecified,
    93  		},
    94  		{
    95  			URI:         "s3://bucket/a/b/c",
    96  			expectedErr: errBucketNotExists,
    97  		},
    98  		{
    99  			URI:         testBucketURI,
   100  			expectedErr: nil,
   101  		},
   102  	}
   103  
   104  	s.s3cli.EXPECT().HeadBucketWithContext(gomock.Any(), gomock.Any()).DoAndReturn(
   105  		func(ctx aws.Context, input *s3.HeadBucketInput, options ...request.Option) (*s3.HeadBucketOutput, error) {
   106  			if *input.Bucket != s.testArchivalURI.Hostname() {
   107  				return nil, awserr.New("NotFound", "", nil)
   108  			}
   109  
   110  			return &s3.HeadBucketOutput{}, nil
   111  		}).AnyTimes()
   112  
   113  	visibilityArchiver := s.newTestVisibilityArchiver()
   114  	for _, tc := range testCases {
   115  		URI, err := archiver.NewURI(tc.URI)
   116  		s.NoError(err)
   117  		s.Equal(tc.expectedErr, visibilityArchiver.ValidateURI(URI))
   118  	}
   119  }
   120  
   121  func (s *visibilityArchiverSuite) newTestVisibilityArchiver() *visibilityArchiver {
   122  	return &visibilityArchiver{
   123  		container:   s.container,
   124  		s3cli:       s.s3cli,
   125  		queryParser: NewQueryParser(),
   126  	}
   127  }
   128  
   129  const (
   130  	testWorkflowTypeName = "test-workflow-type"
   131  )
   132  
   133  func (s *visibilityArchiverSuite) SetupSuite() {
   134  	var err error
   135  
   136  	s.testArchivalURI, err = archiver.NewURI(testBucketURI)
   137  	s.Require().NoError(err)
   138  	s.container = &archiver.VisibilityBootstrapContainer{
   139  		Logger:         log.NewNoopLogger(),
   140  		MetricsHandler: metrics.NoopMetricsHandler,
   141  	}
   142  }
   143  
   144  func (s *visibilityArchiverSuite) TearDownSuite() {
   145  }
   146  
   147  func (s *visibilityArchiverSuite) SetupTest() {
   148  	s.Assertions = require.New(s.T())
   149  	s.controller = gomock.NewController(s.T())
   150  
   151  	s.s3cli = mocks.NewMockS3API(s.controller)
   152  	setupFsEmulation(s.s3cli)
   153  	s.setupVisibilityDirectory()
   154  }
   155  
   156  func (s *visibilityArchiverSuite) TearDownTest() {
   157  	s.controller.Finish()
   158  }
   159  
   160  func (s *visibilityArchiverSuite) TestArchive_Fail_InvalidURI() {
   161  	visibilityArchiver := s.newTestVisibilityArchiver()
   162  	URI, err := archiver.NewURI("wrongscheme://")
   163  	s.NoError(err)
   164  	request := &archiverspb.VisibilityRecord{
   165  		Namespace:        testNamespace,
   166  		NamespaceId:      testNamespaceID,
   167  		WorkflowId:       testWorkflowID,
   168  		RunId:            testRunID,
   169  		WorkflowTypeName: testWorkflowTypeName,
   170  		StartTime:        timestamp.TimeNowPtrUtc(),
   171  		ExecutionTime:    nil, // workflow without backoff
   172  		CloseTime:        timestamp.TimeNowPtrUtc(),
   173  		Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   174  		HistoryLength:    int64(101),
   175  	}
   176  	err = visibilityArchiver.Archive(context.Background(), URI, request)
   177  	s.Error(err)
   178  }
   179  
   180  func (s *visibilityArchiverSuite) TestArchive_Fail_InvalidRequest() {
   181  	visibilityArchiver := s.newTestVisibilityArchiver()
   182  	err := visibilityArchiver.Archive(context.Background(), s.testArchivalURI, &archiverspb.VisibilityRecord{})
   183  	s.Error(err)
   184  }
   185  
   186  func (s *visibilityArchiverSuite) TestArchive_Fail_NonRetryableErrorOption() {
   187  	visibilityArchiver := s.newTestVisibilityArchiver()
   188  	nonRetryableErr := errors.New("some non-retryable error")
   189  	err := visibilityArchiver.Archive(
   190  		context.Background(),
   191  		s.testArchivalURI,
   192  		&archiverspb.VisibilityRecord{
   193  			NamespaceId: testNamespaceID,
   194  		},
   195  		archiver.GetNonRetryableErrorOption(nonRetryableErr),
   196  	)
   197  	s.Equal(nonRetryableErr, err)
   198  }
   199  
   200  func (s *visibilityArchiverSuite) TestArchive_Success() {
   201  	visibilityArchiver := s.newTestVisibilityArchiver()
   202  	closeTimestamp := timestamp.TimeNowPtrUtc()
   203  	request := &archiverspb.VisibilityRecord{
   204  		NamespaceId:      testNamespaceID,
   205  		Namespace:        testNamespace,
   206  		WorkflowId:       testWorkflowID,
   207  		RunId:            testRunID,
   208  		WorkflowTypeName: testWorkflowTypeName,
   209  		StartTime:        timestamppb.New(closeTimestamp.AsTime().Add(-time.Hour)),
   210  		ExecutionTime:    nil, // workflow without backoff
   211  		CloseTime:        closeTimestamp,
   212  		Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   213  		HistoryLength:    int64(101),
   214  		Memo: &commonpb.Memo{
   215  			Fields: map[string]*commonpb.Payload{
   216  				"testFields": payload.EncodeBytes([]byte{1, 2, 3}),
   217  			},
   218  		},
   219  		SearchAttributes: map[string]string{
   220  			"testAttribute": "456",
   221  		},
   222  	}
   223  	URI, err := archiver.NewURI(testBucketURI + "/test-archive-success")
   224  	s.NoError(err)
   225  	err = visibilityArchiver.Archive(context.Background(), URI, request)
   226  	s.NoError(err)
   227  
   228  	expectedKey := constructTimestampIndex(URI.Path(), testNamespaceID, primaryIndexKeyWorkflowID, testWorkflowID, secondaryIndexKeyCloseTimeout, timestamp.TimeValue(closeTimestamp), testRunID)
   229  	data, err := Download(context.Background(), visibilityArchiver.s3cli, URI, expectedKey)
   230  	s.NoError(err, expectedKey)
   231  
   232  	archivedRecord := &archiverspb.VisibilityRecord{}
   233  	encoder := codec.NewJSONPBEncoder()
   234  	err = encoder.Decode(data, archivedRecord)
   235  	s.NoError(err)
   236  	s.Equal(request, archivedRecord)
   237  }
   238  
   239  func (s *visibilityArchiverSuite) TestQuery_Fail_InvalidURI() {
   240  	visibilityArchiver := s.newTestVisibilityArchiver()
   241  	URI, err := archiver.NewURI("wrongscheme://")
   242  	s.NoError(err)
   243  	request := &archiver.QueryVisibilityRequest{
   244  		NamespaceID: testNamespaceID,
   245  		PageSize:    1,
   246  	}
   247  	response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   248  	s.Error(err)
   249  	s.Nil(response)
   250  }
   251  
   252  func (s *visibilityArchiverSuite) TestQuery_Fail_InvalidRequest() {
   253  	visibilityArchiver := s.newTestVisibilityArchiver()
   254  	response, err := visibilityArchiver.Query(context.Background(), s.testArchivalURI, &archiver.QueryVisibilityRequest{}, searchattribute.TestNameTypeMap)
   255  	s.Error(err)
   256  	s.Nil(response)
   257  }
   258  
   259  func (s *visibilityArchiverSuite) TestQuery_Fail_InvalidQuery() {
   260  	visibilityArchiver := s.newTestVisibilityArchiver()
   261  	mockParser := NewMockQueryParser(s.controller)
   262  	mockParser.EXPECT().Parse(gomock.Any()).Return(nil, errors.New("invalid query"))
   263  	visibilityArchiver.queryParser = mockParser
   264  	response, err := visibilityArchiver.Query(context.Background(), s.testArchivalURI, &archiver.QueryVisibilityRequest{
   265  		NamespaceID: "some random namespaceID",
   266  		PageSize:    10,
   267  		Query:       "some invalid query",
   268  	}, searchattribute.TestNameTypeMap)
   269  	s.Error(err)
   270  	s.Nil(response)
   271  }
   272  
   273  func (s *visibilityArchiverSuite) TestQuery_Success_DirectoryNotExist() {
   274  	visibilityArchiver := s.newTestVisibilityArchiver()
   275  	mockParser := NewMockQueryParser(s.controller)
   276  	mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   277  		workflowID:      convert.StringPtr(testWorkflowID),
   278  		closeTime:       &time.Time{},
   279  		searchPrecision: convert.StringPtr(PrecisionSecond),
   280  	}, nil)
   281  	visibilityArchiver.queryParser = mockParser
   282  	request := &archiver.QueryVisibilityRequest{
   283  		NamespaceID: testNamespaceID,
   284  		Query:       "parsed by mockParser",
   285  		PageSize:    1,
   286  	}
   287  	response, err := visibilityArchiver.Query(context.Background(), s.testArchivalURI, request, searchattribute.TestNameTypeMap)
   288  	s.NoError(err)
   289  	s.NotNil(response)
   290  	s.Empty(response.Executions)
   291  	s.Empty(response.NextPageToken)
   292  }
   293  
   294  func (s *visibilityArchiverSuite) TestQuery_Success_NoNextPageToken() {
   295  	visibilityArchiver := s.newTestVisibilityArchiver()
   296  	mockParser := NewMockQueryParser(s.controller)
   297  	mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   298  		closeTime:       ptr(time.Unix(0, int64(1*time.Hour)).UTC()),
   299  		searchPrecision: convert.StringPtr(PrecisionHour),
   300  		workflowID:      convert.StringPtr(testWorkflowID),
   301  	}, nil)
   302  	visibilityArchiver.queryParser = mockParser
   303  	request := &archiver.QueryVisibilityRequest{
   304  		NamespaceID: testNamespaceID,
   305  		PageSize:    10,
   306  		Query:       "parsed by mockParser",
   307  	}
   308  	URI, err := archiver.NewURI(testBucketURI)
   309  	s.NoError(err)
   310  	response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   311  	s.NoError(err)
   312  	s.NotNil(response)
   313  	s.Nil(response.NextPageToken)
   314  	s.Len(response.Executions, 2)
   315  	ei, err := convertToExecutionInfo(s.visibilityRecords[0], searchattribute.TestNameTypeMap)
   316  	s.NoError(err)
   317  	s.Equal(response.Executions[0], ei)
   318  }
   319  
   320  func (s *visibilityArchiverSuite) TestQuery_Success_SmallPageSize() {
   321  	visibilityArchiver := s.newTestVisibilityArchiver()
   322  	mockParser := NewMockQueryParser(s.controller)
   323  	mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   324  		closeTime:       ptr(time.Unix(0, 0).UTC()),
   325  		searchPrecision: convert.StringPtr(PrecisionDay),
   326  		workflowID:      convert.StringPtr(testWorkflowID),
   327  	}, nil).AnyTimes()
   328  	visibilityArchiver.queryParser = mockParser
   329  	request := &archiver.QueryVisibilityRequest{
   330  		NamespaceID: testNamespaceID,
   331  		PageSize:    2,
   332  		Query:       "parsed by mockParser",
   333  	}
   334  	URI, err := archiver.NewURI(testBucketURI)
   335  	s.NoError(err)
   336  	response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   337  	s.NoError(err)
   338  	s.NotNil(response)
   339  	s.NotNil(response.NextPageToken)
   340  	s.Len(response.Executions, 2)
   341  	ei, err := convertToExecutionInfo(s.visibilityRecords[0], searchattribute.TestNameTypeMap)
   342  	s.NoError(err)
   343  	s.Equal(ei, response.Executions[0])
   344  	ei, err = convertToExecutionInfo(s.visibilityRecords[1], searchattribute.TestNameTypeMap)
   345  	s.NoError(err)
   346  	s.Equal(ei, response.Executions[1])
   347  
   348  	request.NextPageToken = response.NextPageToken
   349  	response, err = visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   350  	s.NoError(err)
   351  	s.NotNil(response)
   352  	s.Nil(response.NextPageToken)
   353  	s.Len(response.Executions, 1)
   354  	ei, err = convertToExecutionInfo(s.visibilityRecords[2], searchattribute.TestNameTypeMap)
   355  	s.NoError(err)
   356  	s.Equal(ei, response.Executions[0])
   357  }
   358  
   359  func (s *visibilityArchiverSuite) TestQuery_EmptyQuery_InvalidNamespace() {
   360  	arc := archiver.VisibilityArchiver(s.newTestVisibilityArchiver())
   361  	uri, err := archiver.NewURI(testBucketURI)
   362  	s.NoError(err)
   363  	req := &archiver.QueryVisibilityRequest{
   364  		NamespaceID:   "",
   365  		PageSize:      1,
   366  		NextPageToken: nil,
   367  		Query:         "",
   368  	}
   369  	_, err = arc.Query(context.Background(), uri, req, searchattribute.TestNameTypeMap)
   370  
   371  	var svcErr *serviceerror.InvalidArgument
   372  
   373  	s.ErrorAs(err, &svcErr)
   374  }
   375  
   376  func (s *visibilityArchiverSuite) TestQuery_EmptyQuery_ZeroPageSize() {
   377  	arc := archiver.VisibilityArchiver(s.newTestVisibilityArchiver())
   378  
   379  	uri, err := archiver.NewURI(testBucketURI)
   380  	s.NoError(err)
   381  
   382  	req := &archiver.QueryVisibilityRequest{
   383  		NamespaceID:   testNamespaceID,
   384  		PageSize:      0,
   385  		NextPageToken: nil,
   386  		Query:         "",
   387  	}
   388  	_, err = arc.Query(context.Background(), uri, req, searchattribute.TestNameTypeMap)
   389  
   390  	var svcErr *serviceerror.InvalidArgument
   391  
   392  	s.ErrorAs(err, &svcErr)
   393  }
   394  
   395  func (s *visibilityArchiverSuite) TestQuery_EmptyQuery_Pagination() {
   396  	arc := archiver.VisibilityArchiver(s.newTestVisibilityArchiver())
   397  	uri, err := archiver.NewURI(testBucketURI)
   398  	s.NoError(err)
   399  
   400  	executions := make(map[string]*workflowpb.WorkflowExecutionInfo, len(s.visibilityRecords))
   401  	var nextPageToken []byte
   402  
   403  	for {
   404  		req := &archiver.QueryVisibilityRequest{
   405  			NamespaceID:   testNamespaceID,
   406  			PageSize:      1,
   407  			NextPageToken: nextPageToken,
   408  			Query:         "",
   409  		}
   410  		response, err := arc.Query(context.Background(), uri, req, searchattribute.TestNameTypeMap)
   411  		s.NoError(err)
   412  		s.NotNil(response)
   413  		nextPageToken = response.NextPageToken
   414  		for _, execution := range response.Executions {
   415  			key := execution.Execution.GetWorkflowId() +
   416  				"/" + execution.Execution.GetRunId() +
   417  				"/" + execution.CloseTime.String()
   418  			if executions[key] != nil {
   419  				s.Fail("duplicate key", key)
   420  			}
   421  			executions[key] = execution
   422  		}
   423  		if len(nextPageToken) == 0 {
   424  			break
   425  		}
   426  	}
   427  	s.Len(executions, len(s.visibilityRecords))
   428  }
   429  
   430  type precisionTest struct {
   431  	day       int
   432  	hour      int
   433  	minute    int
   434  	second    int
   435  	precision string
   436  }
   437  
   438  func (s *visibilityArchiverSuite) TestArchiveAndQueryPrecisions() {
   439  	precisionTests := []*precisionTest{
   440  		{
   441  			day:       1,
   442  			hour:      0,
   443  			minute:    0,
   444  			second:    0,
   445  			precision: PrecisionDay,
   446  		},
   447  		{
   448  			day:       1,
   449  			hour:      1,
   450  			minute:    0,
   451  			second:    0,
   452  			precision: PrecisionDay,
   453  		},
   454  		{
   455  			day:       2,
   456  			hour:      1,
   457  			minute:    0,
   458  			second:    0,
   459  			precision: PrecisionHour,
   460  		},
   461  		{
   462  			day:       2,
   463  			hour:      1,
   464  			minute:    30,
   465  			second:    0,
   466  			precision: PrecisionHour,
   467  		},
   468  		{
   469  			day:       3,
   470  			hour:      2,
   471  			minute:    1,
   472  			second:    0,
   473  			precision: PrecisionMinute,
   474  		},
   475  		{
   476  			day:       3,
   477  			hour:      2,
   478  			minute:    1,
   479  			second:    30,
   480  			precision: PrecisionMinute,
   481  		},
   482  		{
   483  			day:       4,
   484  			hour:      3,
   485  			minute:    2,
   486  			second:    1,
   487  			precision: PrecisionSecond,
   488  		},
   489  		{
   490  			day:       4,
   491  			hour:      3,
   492  			minute:    2,
   493  			second:    1,
   494  			precision: PrecisionSecond,
   495  		},
   496  		{
   497  			day:       4,
   498  			hour:      3,
   499  			minute:    2,
   500  			second:    2,
   501  			precision: PrecisionSecond,
   502  		},
   503  		{
   504  			day:       4,
   505  			hour:      3,
   506  			minute:    2,
   507  			second:    2,
   508  			precision: PrecisionSecond,
   509  		},
   510  	}
   511  	visibilityArchiver := s.newTestVisibilityArchiver()
   512  	URI, err := archiver.NewURI(testBucketURI + "/archive-and-query-precision")
   513  	s.NoError(err)
   514  
   515  	for i, testData := range precisionTests {
   516  		record := archiverspb.VisibilityRecord{
   517  			NamespaceId:      testNamespaceID,
   518  			Namespace:        testNamespace,
   519  			WorkflowId:       testWorkflowID,
   520  			RunId:            fmt.Sprintf("%s-%d", testRunID, i),
   521  			WorkflowTypeName: testWorkflowTypeName,
   522  			StartTime:        timestamppb.New(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   523  			CloseTime:        timestamppb.New(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   524  			Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   525  			HistoryLength:    101,
   526  		}
   527  		err := visibilityArchiver.Archive(context.Background(), URI, &record)
   528  		s.NoError(err, "case %d", i)
   529  	}
   530  
   531  	request := &archiver.QueryVisibilityRequest{
   532  		NamespaceID: testNamespaceID,
   533  		PageSize:    100,
   534  		Query:       "parsed by mockParser",
   535  	}
   536  
   537  	for i, testData := range precisionTests {
   538  		mockParser := NewMockQueryParser(s.controller)
   539  		mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   540  			closeTime:       ptr(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   541  			searchPrecision: convert.StringPtr(testData.precision),
   542  			workflowID:      convert.StringPtr(testWorkflowID),
   543  		}, nil).AnyTimes()
   544  		visibilityArchiver.queryParser = mockParser
   545  
   546  		response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   547  		s.NoError(err)
   548  		s.NotNil(response)
   549  		s.Len(response.Executions, 2, "Iteration ", i)
   550  
   551  		mockParser = NewMockQueryParser(s.controller)
   552  		mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   553  			startTime:       ptr(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   554  			searchPrecision: convert.StringPtr(testData.precision),
   555  			workflowID:      convert.StringPtr(testWorkflowID),
   556  		}, nil).AnyTimes()
   557  		visibilityArchiver.queryParser = mockParser
   558  
   559  		response, err = visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   560  		s.NoError(err)
   561  		s.NotNil(response)
   562  		s.Len(response.Executions, 2, "Iteration ", i)
   563  
   564  		mockParser = NewMockQueryParser(s.controller)
   565  		mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   566  			closeTime:        ptr(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   567  			searchPrecision:  convert.StringPtr(testData.precision),
   568  			workflowTypeName: convert.StringPtr(testWorkflowTypeName),
   569  		}, nil).AnyTimes()
   570  		visibilityArchiver.queryParser = mockParser
   571  
   572  		response, err = visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   573  		s.NoError(err)
   574  		s.NotNil(response)
   575  		s.Len(response.Executions, 2, "Iteration ", i)
   576  
   577  		mockParser = NewMockQueryParser(s.controller)
   578  		mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   579  			startTime:        ptr(time.Date(2000, 1, testData.day, testData.hour, testData.minute, testData.second, 0, time.UTC)),
   580  			searchPrecision:  convert.StringPtr(testData.precision),
   581  			workflowTypeName: convert.StringPtr(testWorkflowTypeName),
   582  		}, nil).AnyTimes()
   583  		visibilityArchiver.queryParser = mockParser
   584  
   585  		response, err = visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   586  		s.NoError(err)
   587  		s.NotNil(response)
   588  		s.Len(response.Executions, 2, "Iteration ", i)
   589  	}
   590  }
   591  
   592  func (s *visibilityArchiverSuite) TestArchiveAndQuery() {
   593  	visibilityArchiver := s.newTestVisibilityArchiver()
   594  	URI, err := archiver.NewURI(testBucketURI + "/archive-and-query")
   595  	s.NoError(err)
   596  	for _, record := range s.visibilityRecords {
   597  		err := visibilityArchiver.Archive(context.Background(), URI, (*archiverspb.VisibilityRecord)(record))
   598  		s.NoError(err)
   599  	}
   600  
   601  	mockParser := NewMockQueryParser(s.controller)
   602  	mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   603  		workflowID: convert.StringPtr(testWorkflowID),
   604  	}, nil).AnyTimes()
   605  	visibilityArchiver.queryParser = mockParser
   606  	request := &archiver.QueryVisibilityRequest{
   607  		NamespaceID: testNamespaceID,
   608  		PageSize:    1,
   609  		Query:       "parsed by mockParser",
   610  	}
   611  	executions := []*workflowpb.WorkflowExecutionInfo{}
   612  	first := true
   613  	for first || request.NextPageToken != nil {
   614  		response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   615  		s.NoError(err)
   616  		s.NotNil(response)
   617  		executions = append(executions, response.Executions...)
   618  		request.NextPageToken = response.NextPageToken
   619  		first = false
   620  	}
   621  	s.Len(executions, 3)
   622  	ei, err := convertToExecutionInfo(s.visibilityRecords[0], searchattribute.TestNameTypeMap)
   623  	s.NoError(err)
   624  	s.Equal(ei, executions[0])
   625  	ei, err = convertToExecutionInfo(s.visibilityRecords[1], searchattribute.TestNameTypeMap)
   626  	s.NoError(err)
   627  	s.Equal(ei, executions[1])
   628  	ei, err = convertToExecutionInfo(s.visibilityRecords[2], searchattribute.TestNameTypeMap)
   629  	s.NoError(err)
   630  	s.Equal(ei, executions[2])
   631  
   632  	mockParser = NewMockQueryParser(s.controller)
   633  	mockParser.EXPECT().Parse(gomock.Any()).Return(&parsedQuery{
   634  		workflowTypeName: convert.StringPtr(testWorkflowTypeName),
   635  	}, nil).AnyTimes()
   636  	visibilityArchiver.queryParser = mockParser
   637  	request = &archiver.QueryVisibilityRequest{
   638  		NamespaceID: testNamespaceID,
   639  		PageSize:    1,
   640  		Query:       "parsed by mockParser",
   641  	}
   642  	executions = []*workflowpb.WorkflowExecutionInfo{}
   643  	first = true
   644  	for first || request.NextPageToken != nil {
   645  		response, err := visibilityArchiver.Query(context.Background(), URI, request, searchattribute.TestNameTypeMap)
   646  		s.NoError(err)
   647  		s.NotNil(response)
   648  		executions = append(executions, response.Executions...)
   649  		request.NextPageToken = response.NextPageToken
   650  		first = false
   651  	}
   652  	s.Len(executions, 3)
   653  	ei, err = convertToExecutionInfo(s.visibilityRecords[0], searchattribute.TestNameTypeMap)
   654  	s.NoError(err)
   655  	s.Equal(ei, executions[0])
   656  	ei, err = convertToExecutionInfo(s.visibilityRecords[1], searchattribute.TestNameTypeMap)
   657  	s.NoError(err)
   658  	s.Equal(ei, executions[1])
   659  	ei, err = convertToExecutionInfo(s.visibilityRecords[2], searchattribute.TestNameTypeMap)
   660  	s.NoError(err)
   661  	s.Equal(ei, executions[2])
   662  }
   663  
   664  func (s *visibilityArchiverSuite) setupVisibilityDirectory() {
   665  	s.visibilityRecords = []*archiverspb.VisibilityRecord{
   666  		{
   667  			NamespaceId:      testNamespaceID,
   668  			Namespace:        testNamespace,
   669  			WorkflowId:       testWorkflowID,
   670  			RunId:            testRunID,
   671  			WorkflowTypeName: testWorkflowTypeName,
   672  			StartTime:        timestamp.UnixOrZeroTimePtr(1),
   673  			CloseTime:        timestamp.UnixOrZeroTimePtr(int64(time.Hour)),
   674  			Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   675  			HistoryLength:    101,
   676  		},
   677  		{
   678  			NamespaceId:      testNamespaceID,
   679  			Namespace:        testNamespace,
   680  			WorkflowId:       testWorkflowID,
   681  			RunId:            testRunID + "1",
   682  			WorkflowTypeName: testWorkflowTypeName,
   683  			StartTime:        timestamp.UnixOrZeroTimePtr(1),
   684  			CloseTime:        timestamp.UnixOrZeroTimePtr(int64(time.Hour + 30*time.Minute)),
   685  			Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   686  			HistoryLength:    101,
   687  		},
   688  		{
   689  			NamespaceId:      testNamespaceID,
   690  			Namespace:        testNamespace,
   691  			WorkflowId:       testWorkflowID,
   692  			RunId:            testRunID + "1",
   693  			WorkflowTypeName: testWorkflowTypeName,
   694  			StartTime:        timestamp.UnixOrZeroTimePtr(1),
   695  			CloseTime:        timestamp.UnixOrZeroTimePtr(int64(3 * time.Hour)),
   696  			Status:           enumspb.WORKFLOW_EXECUTION_STATUS_FAILED,
   697  			HistoryLength:    101,
   698  		},
   699  	}
   700  	visibilityArchiver := s.newTestVisibilityArchiver()
   701  	for _, record := range s.visibilityRecords {
   702  		s.writeVisibilityRecordForQueryTest(visibilityArchiver, record)
   703  	}
   704  }
   705  
   706  func (s *visibilityArchiverSuite) writeVisibilityRecordForQueryTest(visibilityArchiver *visibilityArchiver, record *archiverspb.VisibilityRecord) {
   707  	err := visibilityArchiver.Archive(context.Background(), s.testArchivalURI, record)
   708  	s.Require().NoError(err)
   709  }