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.
consul/agent/grpc-external/services/resource/write_mav_common_test.go

315 lines
11 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package resource_test
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/anypb"
svc "github.com/hashicorp/consul/agent/grpc-external/services/resource"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
)
// Common test structs and test cases shared by the Write and MutateAndValidate RPCs
// only. These are not intended to be used by other tests.
type resourceValidTestCase struct {
modFn func(artist, recordLabel *pbresource.Resource) *pbresource.Resource
errContains string
}
func resourceValidTestCases(t *testing.T) map[string]resourceValidTestCase {
return map[string]resourceValidTestCase{
"no resource": {
modFn: func(_, _ *pbresource.Resource) *pbresource.Resource {
return nil
},
errContains: "resource is required",
},
"no id": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id = nil
return artist
},
errContains: "resource.id is required",
},
"no type": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Type = nil
return artist
},
errContains: "resource.id.type is required",
},
"no name": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Name = ""
return artist
},
errContains: "resource.id.name invalid",
},
"name is mixed case": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Name = "MixedCaseNotAllowed"
return artist
},
errContains: "resource.id.name invalid",
},
"name too long": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Name = strings.Repeat("a", resource.MaxNameLength+1)
return artist
},
errContains: "resource.id.name invalid",
},
"wrong data type": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
var err error
artist.Data, err = anypb.New(&pbdemov2.Album{})
require.NoError(t, err)
return artist
},
errContains: "resource.data is of wrong type",
},
"partition is mixed case": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Partition = "Default"
return artist
},
errContains: "resource.id.tenancy.partition invalid",
},
"partition too long": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1)
return artist
},
errContains: "resource.id.tenancy.partition invalid",
},
"namespace is mixed case": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Namespace = "Default"
return artist
},
errContains: "resource.id.tenancy.namespace invalid",
},
"namespace too long": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1)
return artist
},
errContains: "resource.id.tenancy.namespace invalid",
},
"fail validation hook": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
buffer := &pbdemov2.Artist{}
require.NoError(t, artist.Data.UnmarshalTo(buffer))
buffer.Name = "" // name cannot be empty
require.NoError(t, artist.Data.MarshalFrom(buffer))
return artist
},
errContains: "artist.name required",
},
"partition scope with non-empty namespace": {
modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource {
recordLabel.Id.Tenancy.Namespace = "bogus"
return recordLabel
},
errContains: "cannot have a namespace",
},
}
}
type ownerValidTestCase struct {
modFn func(res *pbresource.Resource)
errorContains string
}
func ownerValidationTestCases(t *testing.T) map[string]ownerValidTestCase {
return map[string]ownerValidTestCase{
"no owner type": {
modFn: func(res *pbresource.Resource) { res.Owner.Type = nil },
errorContains: "resource.owner.type is required",
},
"no owner name": {
modFn: func(res *pbresource.Resource) { res.Owner.Name = "" },
errorContains: "resource.owner.name invalid",
},
"mixed case owner name": {
modFn: func(res *pbresource.Resource) { res.Owner.Name = strings.ToUpper(res.Owner.Name) },
errorContains: "resource.owner.name invalid",
},
"owner name too long": {
modFn: func(res *pbresource.Resource) {
res.Owner.Name = strings.Repeat("a", resource.MaxNameLength+1)
},
errorContains: "resource.owner.name invalid",
},
"owner partition is mixed case": {
modFn: func(res *pbresource.Resource) {
res.Owner.Tenancy.Partition = "Default"
},
errorContains: "resource.owner.tenancy.partition invalid",
},
"owner partition too long": {
modFn: func(res *pbresource.Resource) {
res.Owner.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1)
},
errorContains: "resource.owner.tenancy.partition invalid",
},
"owner namespace is mixed case": {
modFn: func(res *pbresource.Resource) {
res.Owner.Tenancy.Namespace = "Default"
},
errorContains: "resource.owner.tenancy.namespace invalid",
},
"owner namespace too long": {
modFn: func(res *pbresource.Resource) {
res.Owner.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1)
},
errorContains: "resource.owner.tenancy.namespace invalid",
},
}
}
// Test case struct shared by MutateAndValidate and Write success test cases
type mavOrWriteSuccessTestCase struct {
modFn func(artist, recordLabel *pbresource.Resource) *pbresource.Resource
expectedTenancy *pbresource.Tenancy
}
// Test case struct shared by MutateAndValidate and Write success test cases
func mavOrWriteSuccessTestCases(t *testing.T) map[string]mavOrWriteSuccessTestCase {
return map[string]mavOrWriteSuccessTestCase{
"namespaced resource provides nonempty partition and namespace": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
return artist
},
expectedTenancy: resource.DefaultNamespacedTenancy(),
},
"namespaced resource inherits tokens partition when empty": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Partition = ""
return artist
},
expectedTenancy: resource.DefaultNamespacedTenancy(),
},
"namespaced resource inherits tokens namespace when empty": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Namespace = ""
return artist
},
expectedTenancy: resource.DefaultNamespacedTenancy(),
},
"namespaced resource inherits tokens partition and namespace when empty": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Partition = ""
artist.Id.Tenancy.Namespace = ""
return artist
},
expectedTenancy: resource.DefaultNamespacedTenancy(),
},
"namespaced resource inherits tokens partition and namespace when tenancy nil": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy = nil
return artist
},
expectedTenancy: resource.DefaultNamespacedTenancy(),
},
"partitioned resource provides nonempty partition": {
modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource {
return recordLabel
},
expectedTenancy: resource.DefaultPartitionedTenancy(),
},
"partitioned resource inherits tokens partition when empty": {
modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource {
recordLabel.Id.Tenancy.Partition = ""
return recordLabel
},
expectedTenancy: resource.DefaultPartitionedTenancy(),
},
"partitioned resource inherits tokens partition when tenancy nil": {
modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource {
recordLabel.Id.Tenancy = nil
return recordLabel
},
expectedTenancy: resource.DefaultPartitionedTenancy(),
},
}
}
// Test case struct shared by MutateAndValidate and Write test cases where tenancy is not found
type mavOrWriteTenancyNotFoundTestCase map[string]struct {
modFn func(artist, recordLabel *pbresource.Resource) *pbresource.Resource
errCode codes.Code
errContains string
}
// Test case struct shared by MutateAndValidate and Write test cases where tenancy is not found
func mavOrWriteTenancyNotFoundTestCases(t *testing.T) mavOrWriteTenancyNotFoundTestCase {
return mavOrWriteTenancyNotFoundTestCase{
"namespaced resource provides nonexistant partition": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Partition = "boguspartition"
return artist
},
errCode: codes.InvalidArgument,
errContains: "partition not found",
},
"namespaced resource provides nonexistant namespace": {
modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource {
artist.Id.Tenancy.Namespace = "bogusnamespace"
return artist
},
errCode: codes.InvalidArgument,
errContains: "namespace not found",
},
"partitioned resource provides nonexistant partition": {
modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource {
recordLabel.Id.Tenancy.Partition = "boguspartition"
return recordLabel
},
errCode: codes.InvalidArgument,
errContains: "partition not found",
},
}
}
type mavOrWriteTenancyMarkedForDeletionTestCase struct {
modFn func(artist, recordLabel *pbresource.Resource, mockTenancyBridge *svc.MockTenancyBridge) *pbresource.Resource
errContains string
}
func mavOrWriteTenancyMarkedForDeletionTestCases(t *testing.T) map[string]mavOrWriteTenancyMarkedForDeletionTestCase {
return map[string]mavOrWriteTenancyMarkedForDeletionTestCase{
"namespaced resources partition marked for deletion": {
modFn: func(artist, _ *pbresource.Resource, mockTenancyBridge *svc.MockTenancyBridge) *pbresource.Resource {
mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(true, nil)
return artist
},
errContains: "tenancy marked for deletion",
},
"namespaced resources namespace marked for deletion": {
modFn: func(artist, _ *pbresource.Resource, mockTenancyBridge *svc.MockTenancyBridge) *pbresource.Resource {
mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(false, nil)
mockTenancyBridge.On("IsNamespaceMarkedForDeletion", "ap1", "ns1").Return(true, nil)
return artist
},
errContains: "tenancy marked for deletion",
},
"partitioned resources partition marked for deletion": {
modFn: func(_, recordLabel *pbresource.Resource, mockTenancyBridge *svc.MockTenancyBridge) *pbresource.Resource {
mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(true, nil)
return recordLabel
},
errContains: "tenancy marked for deletion",
},
}
}