mirror of https://github.com/hashicorp/consul
315 lines
11 KiB
Go
315 lines
11 KiB
Go
// 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",
|
|
},
|
|
}
|
|
}
|