mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
7.8 KiB
265 lines
7.8 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package cache |
|
|
|
import ( |
|
"context" |
|
"testing" |
|
|
|
"github.com/stretchr/testify/mock" |
|
"github.com/stretchr/testify/require" |
|
"github.com/stretchr/testify/suite" |
|
|
|
mockpbresource "github.com/hashicorp/consul/grpcmocks/proto-public/pbresource" |
|
"github.com/hashicorp/consul/internal/resource" |
|
"github.com/hashicorp/consul/internal/resource/resourcetest" |
|
"github.com/hashicorp/consul/proto-public/pbresource" |
|
pbdemo "github.com/hashicorp/consul/proto/private/pbdemo/v1" |
|
"github.com/hashicorp/consul/proto/private/prototest" |
|
) |
|
|
|
type cacheClientSuite struct { |
|
suite.Suite |
|
|
|
cache Cache |
|
mclient *mockpbresource.ResourceServiceClient_Expecter |
|
client pbresource.ResourceServiceClient |
|
|
|
album1 *pbresource.Resource |
|
album2 *pbresource.Resource |
|
} |
|
|
|
func (suite *cacheClientSuite) SetupTest() { |
|
suite.cache = New() |
|
|
|
// It would be difficult to use the inmem resource service here due to cyclical dependencies. |
|
// Any type registrations from other packages cannot be imported because those packages |
|
// will require the controller package which will require this cache package. The easiest |
|
// way of getting around this was to not use the real resource service and require type registrations. |
|
client := mockpbresource.NewResourceServiceClient(suite.T()) |
|
suite.mclient = client.EXPECT() |
|
|
|
require.NoError(suite.T(), suite.cache.AddIndex(pbdemo.AlbumType, namePrefixIndexer())) |
|
require.NoError(suite.T(), suite.cache.AddIndex(pbdemo.AlbumType, releaseYearIndexer())) |
|
require.NoError(suite.T(), suite.cache.AddIndex(pbdemo.AlbumType, tracksIndexer())) |
|
|
|
suite.album1 = resourcetest.Resource(pbdemo.AlbumType, "one"). |
|
WithTenancy(resource.DefaultNamespacedTenancy()). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "one", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"foo", "bar", "baz"}, |
|
}). |
|
Build() |
|
|
|
suite.album2 = resourcetest.Resource(pbdemo.AlbumType, "two"). |
|
WithTenancy(resource.DefaultNamespacedTenancy()). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "two", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"fangorn", "zoo"}, |
|
}). |
|
Build() |
|
|
|
suite.cache.Insert(suite.album1) |
|
suite.cache.Insert(suite.album2) |
|
|
|
suite.client = NewCachedClient(suite.cache, client) |
|
} |
|
|
|
func (suite *cacheClientSuite) performWrite(res *pbresource.Resource, shouldError bool) { |
|
req := &pbresource.WriteRequest{ |
|
Resource: res, |
|
} |
|
|
|
// Setup the expectation for the inner mocked client to receive the real request |
|
if shouldError { |
|
suite.mclient.Write(mock.Anything, req). |
|
Return(nil, fakeWrappedErr). |
|
Once() |
|
} else { |
|
suite.mclient.Write(mock.Anything, req). |
|
Return(&pbresource.WriteResponse{ |
|
Resource: res, |
|
}, nil). |
|
Once() |
|
} |
|
|
|
// Now use the wrapper client to perform the request |
|
out, err := suite.client.Write(context.Background(), req) |
|
if shouldError { |
|
require.ErrorIs(suite.T(), err, fakeWrappedErr) |
|
require.Nil(suite.T(), out) |
|
} else { |
|
require.NoError(suite.T(), err) |
|
prototest.AssertDeepEqual(suite.T(), res, out.Resource) |
|
} |
|
} |
|
|
|
func (suite *cacheClientSuite) performDelete(id *pbresource.ID, shouldError bool) { |
|
req := &pbresource.DeleteRequest{ |
|
Id: id, |
|
} |
|
|
|
// Setup the expectation for the inner mocked client to receive the real request |
|
if shouldError { |
|
suite.mclient.Delete(mock.Anything, req). |
|
Return(nil, fakeWrappedErr). |
|
Once() |
|
} else { |
|
suite.mclient.Delete(mock.Anything, req). |
|
Return(&pbresource.DeleteResponse{}, nil). |
|
Once() |
|
} |
|
|
|
// Now use the wrapper client to perform the request |
|
out, err := suite.client.Delete(context.Background(), req) |
|
if shouldError { |
|
require.ErrorIs(suite.T(), err, fakeWrappedErr) |
|
require.Nil(suite.T(), out) |
|
} else { |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), out) |
|
} |
|
} |
|
|
|
func (suite *cacheClientSuite) performWriteStatus(res *pbresource.Resource, key string, status *pbresource.Status, shouldError bool) { |
|
req := &pbresource.WriteStatusRequest{ |
|
Id: res.Id, |
|
Key: key, |
|
Status: status, |
|
} |
|
|
|
// Setup the expectation for the inner mocked client to receive the real request |
|
if shouldError { |
|
suite.mclient.WriteStatus(mock.Anything, req). |
|
Return(nil, fakeWrappedErr). |
|
Once() |
|
} else { |
|
suite.mclient.WriteStatus(mock.Anything, req). |
|
Return(&pbresource.WriteStatusResponse{ |
|
Resource: res, |
|
}, nil). |
|
Once() |
|
} |
|
|
|
// Now use the wrapper client to perform the request |
|
out, err := suite.client.WriteStatus(context.Background(), req) |
|
if shouldError { |
|
require.ErrorIs(suite.T(), err, fakeWrappedErr) |
|
require.Nil(suite.T(), out) |
|
} else { |
|
require.NoError(suite.T(), err) |
|
prototest.AssertDeepEqual(suite.T(), res, out.Resource) |
|
} |
|
} |
|
|
|
func (suite *cacheClientSuite) TestWrite_Ok() { |
|
newRes := resourcetest.ResourceID(suite.album1.Id). |
|
WithTenancy(&pbresource.Tenancy{ |
|
Partition: "default", |
|
Namespace: "default", |
|
}). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "changed", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"fangorn", "zoo"}, |
|
}). |
|
Build() |
|
|
|
suite.performWrite(newRes, false) |
|
|
|
// now ensure the entry was updated in the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), res) |
|
prototest.AssertDeepEqual(suite.T(), newRes, res) |
|
} |
|
|
|
func (suite *cacheClientSuite) TestWrite_Error() { |
|
newRes := resourcetest.ResourceID(suite.album1.Id). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "changed", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"fangorn", "zoo"}, |
|
}). |
|
WithVersion("notaversion"). |
|
Build() |
|
|
|
suite.performWrite(newRes, true) |
|
|
|
// now ensure the entry was not updated in the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), res) |
|
prototest.AssertDeepEqual(suite.T(), suite.album1, res) |
|
} |
|
|
|
func (suite *cacheClientSuite) TestWriteStatus_Ok() { |
|
status := &pbresource.Status{ObservedGeneration: suite.album1.Generation} |
|
|
|
updatedRes := resourcetest.ResourceID(suite.album1.Id). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "changed", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"fangorn", "zoo"}, |
|
}). |
|
WithStatus("testing", status). |
|
WithVersion("notaversion"). |
|
Build() |
|
|
|
suite.performWriteStatus(updatedRes, "testing", status, false) |
|
|
|
// now ensure the entry was updated in the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), res) |
|
_, updated := res.Status["testing"] |
|
require.True(suite.T(), updated) |
|
} |
|
|
|
func (suite *cacheClientSuite) TestWriteStatus_Error() { |
|
status := &pbresource.Status{ObservedGeneration: suite.album1.Generation} |
|
|
|
updatedRes := resourcetest.ResourceID(suite.album1.Id). |
|
WithData(suite.T(), &pbdemo.Album{ |
|
Name: "changed", |
|
YearOfRelease: 2023, |
|
Tracks: []string{"fangorn", "zoo"}, |
|
}). |
|
WithStatus("testing", status). |
|
WithVersion("notaversion"). |
|
Build() |
|
|
|
suite.performWriteStatus(updatedRes, "testing", status, true) |
|
|
|
// now ensure the entry was not updated in the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), res) |
|
_, updated := res.Status["testing"] |
|
require.False(suite.T(), updated) |
|
} |
|
|
|
func (suite *cacheClientSuite) TestDelete_Ok() { |
|
suite.performDelete(suite.album1.Id, false) |
|
|
|
// now ensure the entry was removed from the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.Nil(suite.T(), res) |
|
} |
|
|
|
func (suite *cacheClientSuite) TestDelete_Error() { |
|
suite.performDelete(suite.album1.Id, true) |
|
|
|
// now ensure the entry was NOT removed from the cache |
|
res, err := suite.cache.Get(suite.album1.Id.Type, "id", suite.album1.Id) |
|
require.NoError(suite.T(), err) |
|
require.NotNil(suite.T(), res) |
|
} |
|
|
|
func TestCacheClient(t *testing.T) { |
|
suite.Run(t, new(cacheClientSuite)) |
|
}
|
|
|