// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package indexers import ( "errors" "testing" "github.com/hashicorp/consul/internal/controller/cache/indexers/indexersmock" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource/resourcetest" pbdemo "github.com/hashicorp/consul/proto/private/pbdemo/v1" "github.com/hashicorp/consul/proto/private/prototest" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) var ( fakeTestError = errors.New("fake test error") ) func TestSingleIndexer(t *testing.T) { suite.Run(t, &decodedSingleIndexerSuite{}) } type decodedSingleIndexerSuite struct { suite.Suite indexer *indexersmock.SingleIndexer[*pbdemo.Album] args *indexersmock.FromArgs index *singleIndexer[*pbdemo.Album] } func (suite *decodedSingleIndexerSuite) SetupTest() { suite.indexer = indexersmock.NewSingleIndexer[*pbdemo.Album](suite.T()) suite.args = indexersmock.NewFromArgs(suite.T()) suite.index = &singleIndexer[*pbdemo.Album]{ indexArgs: suite.args.Execute, decodedIndexer: suite.indexer.Execute, } } func (suite *decodedSingleIndexerSuite) TestFromArgs() { suite.args.EXPECT(). Execute("blah", 1, true). Return([]byte("foo"), nil). Once() val, err := suite.index.FromArgs("blah", 1, true) require.NoError(suite.T(), err) require.Equal(suite.T(), []byte("foo"), val) } func (suite *decodedSingleIndexerSuite) TestFromArgs_Error() { suite.args.EXPECT(). Execute("blah", 1, true). Return(nil, fakeTestError). Once() val, err := suite.index.FromArgs("blah", 1, true) require.ErrorIs(suite.T(), err, fakeTestError) require.Nil(suite.T(), val) } func (suite *decodedSingleIndexerSuite) TestFromResource() { res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) suite.indexer.EXPECT(). Execute(dec). Return(true, []byte{1, 2, 3}, nil). Once() indexed, val, err := suite.index.FromResource(res) require.True(suite.T(), indexed) require.NoError(suite.T(), err) require.Equal(suite.T(), []byte{1, 2, 3}, val) } func (suite *decodedSingleIndexerSuite) TestFromResource_Error() { res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) suite.indexer.EXPECT(). Execute(dec). Return(false, nil, fakeTestError). Once() indexed, val, err := suite.index.FromResource(res) require.False(suite.T(), indexed) require.ErrorIs(suite.T(), err, fakeTestError) require.Nil(suite.T(), val) } func (suite *decodedSingleIndexerSuite) TestFromResource_DecodeError() { res := resourcetest.Resource(pbdemo.ArtistType, "foo"). WithData(suite.T(), &pbdemo.Artist{ Name: "blah", }). Build() var expectedErr resource.ErrDataParse indexed, val, err := suite.index.FromResource(res) require.False(suite.T(), indexed) require.ErrorAs(suite.T(), err, &expectedErr) require.Nil(suite.T(), val) } func (suite *decodedSingleIndexerSuite) TestIntegration() { // This test attempts to do enough to ensure that the // cache Index creator configures all the interfaces/funcs // the correct way. It is not meant to fully test the // Index type itself. res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) idx := DecodedSingleIndexer("test", suite.args.Execute, suite.indexer.Execute).IndexedData() suite.indexer.EXPECT(). Execute(dec). Return(true, []byte{1, 2}, nil). Once() txn := idx.Txn() require.NoError(suite.T(), txn.Insert(res)) txn.Commit() suite.args.EXPECT(). Execute("fake"). Return([]byte{1, 2}, nil). Once() r, err := idx.Txn().Get("fake") require.NoError(suite.T(), err) prototest.AssertDeepEqual(suite.T(), res, r) } func TestMultiIndexer(t *testing.T) { suite.Run(t, &decodedMultiIndexerSuite{}) } type decodedMultiIndexerSuite struct { suite.Suite indexer *indexersmock.MultiIndexer[*pbdemo.Album] args *indexersmock.FromArgs index *multiIndexer[*pbdemo.Album] } func (suite *decodedMultiIndexerSuite) SetupTest() { suite.indexer = indexersmock.NewMultiIndexer[*pbdemo.Album](suite.T()) suite.args = indexersmock.NewFromArgs(suite.T()) suite.index = &multiIndexer[*pbdemo.Album]{ indexArgs: suite.args.Execute, decodedIndexer: suite.indexer.Execute, } } func (suite *decodedMultiIndexerSuite) TestFromArgs() { suite.args.EXPECT(). Execute("blah", 1, true). Return([]byte("foo"), nil). Once() val, err := suite.index.FromArgs("blah", 1, true) require.NoError(suite.T(), err) require.Equal(suite.T(), []byte("foo"), val) } func (suite *decodedMultiIndexerSuite) TestFromArgs_Error() { suite.args.EXPECT(). Execute("blah", 1, true). Return(nil, fakeTestError). Once() val, err := suite.index.FromArgs("blah", 1, true) require.ErrorIs(suite.T(), err, fakeTestError) require.Nil(suite.T(), val) } func (suite *decodedMultiIndexerSuite) TestFromResource() { res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) suite.indexer.EXPECT(). Execute(dec). Return(true, [][]byte{{1, 2}, {3}}, nil). Once() indexed, val, err := suite.index.FromResource(res) require.True(suite.T(), indexed) require.NoError(suite.T(), err) require.Equal(suite.T(), [][]byte{{1, 2}, {3}}, val) } func (suite *decodedMultiIndexerSuite) TestFromResource_Error() { res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) suite.indexer.EXPECT(). Execute(dec). Return(false, nil, fakeTestError). Once() indexed, val, err := suite.index.FromResource(res) require.False(suite.T(), indexed) require.ErrorIs(suite.T(), err, fakeTestError) require.Nil(suite.T(), val) } func (suite *decodedMultiIndexerSuite) TestFromResource_DecodeError() { res := resourcetest.Resource(pbdemo.ArtistType, "foo"). WithData(suite.T(), &pbdemo.Artist{ Name: "blah", }). Build() var expectedErr resource.ErrDataParse indexed, val, err := suite.index.FromResource(res) require.False(suite.T(), indexed) require.ErrorAs(suite.T(), err, &expectedErr) require.Nil(suite.T(), val) } func (suite *decodedMultiIndexerSuite) TestIntegration() { // This test attempts to do enough to ensure that the // cache Index creator configures all the interfaces/funcs // the correct way. It is not meant to fully test the // Index type itself. res := resourcetest.Resource(pbdemo.AlbumType, "foo"). WithData(suite.T(), &pbdemo.Album{ Name: "blah", }). Build() dec := resourcetest.MustDecode[*pbdemo.Album](suite.T(), res) idx := DecodedMultiIndexer("test", suite.args.Execute, suite.indexer.Execute).IndexedData() suite.indexer.EXPECT(). Execute(dec). Return(true, [][]byte{{1, 2}, {3}}, nil). Once() txn := idx.Txn() require.NoError(suite.T(), txn.Insert(res)) txn.Commit() suite.args.EXPECT(). Execute("fake"). Return([]byte{1, 2}, nil). Once() suite.args.EXPECT(). Execute("fake2"). Return([]byte{3}, nil). Once() txn = idx.Txn() r, err := txn.Get("fake") require.NoError(suite.T(), err) prototest.AssertDeepEqual(suite.T(), res, r) r, err = txn.Get("fake2") require.NoError(suite.T(), err) prototest.AssertDeepEqual(suite.T(), res, r) }