2023-03-28 22:48:58 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 22:48:58 +00:00
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
package resource_test
|
2023-03-14 18:30:25 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
2023-09-22 14:53:55 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
"github.com/hashicorp/consul/agent/grpc-external/testutils"
|
|
|
|
"github.com/hashicorp/consul/internal/resource"
|
|
|
|
"github.com/hashicorp/consul/internal/resource/demo"
|
2023-03-14 18:30:25 +00:00
|
|
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
2023-09-01 14:44:53 +00:00
|
|
|
pbdemov1 "github.com/hashicorp/consul/proto/private/pbdemo/v1"
|
|
|
|
demov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
|
2023-03-14 18:30:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestRegister(t *testing.T) {
|
2023-04-11 11:10:14 +00:00
|
|
|
r := resource.NewRegistry()
|
2023-03-14 18:30:25 +00:00
|
|
|
|
2023-04-11 11:10:14 +00:00
|
|
|
// register success
|
2023-09-01 14:44:53 +00:00
|
|
|
reg := resource.Registration{
|
|
|
|
Type: demo.TypeV2Artist,
|
|
|
|
Proto: &demov2.Artist{},
|
|
|
|
Scope: resource.ScopeNamespace,
|
|
|
|
}
|
2023-04-28 15:49:08 +00:00
|
|
|
r.Register(reg)
|
|
|
|
actual, ok := r.Resolve(demo.TypeV2Artist)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.True(t, proto.Equal(demo.TypeV2Artist, actual.Type))
|
2023-03-14 18:30:25 +00:00
|
|
|
|
|
|
|
// register existing should panic
|
2023-06-26 12:25:14 +00:00
|
|
|
require.PanicsWithValue(t, "resource type demo.v2.Artist already registered", func() {
|
2023-04-28 15:49:08 +00:00
|
|
|
r.Register(reg)
|
|
|
|
})
|
2023-09-01 14:44:53 +00:00
|
|
|
|
|
|
|
// register success when scope undefined and type exempt from scope
|
|
|
|
// skip: can't test this because tombstone type is registered as part of NewRegistry()
|
2023-03-14 18:30:25 +00:00
|
|
|
}
|
|
|
|
|
2023-04-11 11:55:32 +00:00
|
|
|
func TestRegister_Defaults(t *testing.T) {
|
2023-04-11 11:10:14 +00:00
|
|
|
r := resource.NewRegistry()
|
2023-09-01 14:44:53 +00:00
|
|
|
r.Register(resource.Registration{
|
|
|
|
Type: demo.TypeV2Artist,
|
|
|
|
Proto: &demov2.Artist{},
|
|
|
|
Scope: resource.ScopeNamespace,
|
|
|
|
})
|
2023-04-11 11:10:14 +00:00
|
|
|
artist, err := demo.GenerateV2Artist()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
reg, ok := r.Resolve(demo.TypeV2Artist)
|
|
|
|
require.True(t, ok)
|
|
|
|
|
|
|
|
// verify default read hook requires operator:read
|
2023-09-22 14:53:55 +00:00
|
|
|
require.NoError(t, reg.ACLs.Read(testutils.ACLOperatorRead(t), nil, artist.Id, nil))
|
|
|
|
require.True(t, acl.IsErrPermissionDenied(reg.ACLs.Read(testutils.ACLNoPermissions(t), nil, artist.Id, nil)))
|
2023-04-11 11:10:14 +00:00
|
|
|
|
|
|
|
// verify default write hook requires operator:write
|
2023-08-10 14:53:38 +00:00
|
|
|
require.NoError(t, reg.ACLs.Write(testutils.ACLOperatorWrite(t), nil, artist))
|
|
|
|
require.True(t, acl.IsErrPermissionDenied(reg.ACLs.Write(testutils.ACLNoPermissions(t), nil, artist)))
|
2023-04-11 11:10:14 +00:00
|
|
|
|
|
|
|
// verify default list hook requires operator:read
|
2023-08-15 21:57:59 +00:00
|
|
|
require.NoError(t, reg.ACLs.List(testutils.ACLOperatorRead(t), nil))
|
|
|
|
require.True(t, acl.IsErrPermissionDenied(reg.ACLs.List(testutils.ACLNoPermissions(t), nil)))
|
2023-04-11 11:55:32 +00:00
|
|
|
|
|
|
|
// verify default validate is a no-op
|
|
|
|
require.NoError(t, reg.Validate(nil))
|
2023-04-12 21:50:07 +00:00
|
|
|
|
|
|
|
// verify default mutate is a no-op
|
|
|
|
require.NoError(t, reg.Mutate(nil))
|
2023-04-11 11:10:14 +00:00
|
|
|
}
|
|
|
|
|
2023-04-28 15:49:08 +00:00
|
|
|
func TestNewRegistry(t *testing.T) {
|
|
|
|
r := resource.NewRegistry()
|
|
|
|
|
|
|
|
// verify tombstone type registered implicitly
|
|
|
|
_, ok := r.Resolve(resource.TypeV1Tombstone)
|
|
|
|
require.True(t, ok)
|
2023-03-14 18:30:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestResolve(t *testing.T) {
|
2023-04-11 11:10:14 +00:00
|
|
|
r := resource.NewRegistry()
|
2023-03-14 18:30:25 +00:00
|
|
|
|
|
|
|
// not found
|
2023-09-01 14:44:53 +00:00
|
|
|
_, ok := r.Resolve(demo.TypeV1Album)
|
2023-03-14 18:30:25 +00:00
|
|
|
assert.False(t, ok)
|
|
|
|
|
|
|
|
// found
|
2023-09-01 14:44:53 +00:00
|
|
|
r.Register(resource.Registration{
|
|
|
|
Type: demo.TypeV1Album,
|
|
|
|
Proto: &pbdemov1.Album{},
|
|
|
|
Scope: resource.ScopeNamespace,
|
|
|
|
})
|
|
|
|
registration, ok := r.Resolve(demo.TypeV1Album)
|
2023-03-14 18:30:25 +00:00
|
|
|
assert.True(t, ok)
|
2023-09-01 14:44:53 +00:00
|
|
|
assert.Equal(t, registration.Type, demo.TypeV1Album)
|
2023-03-14 18:30:25 +00:00
|
|
|
}
|
2023-06-26 12:25:14 +00:00
|
|
|
|
|
|
|
func TestRegister_TypeValidation(t *testing.T) {
|
|
|
|
registry := resource.NewRegistry()
|
|
|
|
|
|
|
|
testCases := map[string]struct {
|
|
|
|
fn func(*pbresource.Type)
|
|
|
|
valid bool
|
|
|
|
}{
|
|
|
|
"Valid": {valid: true},
|
|
|
|
"Group empty": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Group = "" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Group PascalCase": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Group = "Foo" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Group kebab-case": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Group = "foo-bar" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Group snake_case": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Group = "foo_bar" },
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
"GroupVersion empty": {
|
|
|
|
fn: func(t *pbresource.Type) { t.GroupVersion = "" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"GroupVersion snake_case": {
|
|
|
|
fn: func(t *pbresource.Type) { t.GroupVersion = "v_1" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"GroupVersion kebab-case": {
|
|
|
|
fn: func(t *pbresource.Type) { t.GroupVersion = "v-1" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"GroupVersion no leading v": {
|
|
|
|
fn: func(t *pbresource.Type) { t.GroupVersion = "1" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"GroupVersion no trailing number": {
|
|
|
|
fn: func(t *pbresource.Type) { t.GroupVersion = "OnePointOh" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Kind PascalCase with numbers": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Kind = "Number1" },
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
"Kind camelCase": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Kind = "barBaz" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Kind snake_case": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Kind = "bar_baz" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
"Kind empty": {
|
|
|
|
fn: func(t *pbresource.Type) { t.Kind = "" },
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for desc, tc := range testCases {
|
|
|
|
t.Run(desc, func(t *testing.T) {
|
|
|
|
reg := func() {
|
|
|
|
typ := &pbresource.Type{
|
|
|
|
Group: "foo",
|
|
|
|
GroupVersion: "v1",
|
|
|
|
Kind: "Bar",
|
|
|
|
}
|
|
|
|
if tc.fn != nil {
|
|
|
|
tc.fn(typ)
|
|
|
|
}
|
|
|
|
registry.Register(resource.Registration{
|
|
|
|
Type: typ,
|
2023-09-01 14:44:53 +00:00
|
|
|
// Just pass anything since proto is a required field.
|
|
|
|
Proto: &pbdemov1.Artist{},
|
|
|
|
// Scope is also required
|
|
|
|
Scope: resource.ScopeNamespace,
|
2023-06-26 12:25:14 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if tc.valid {
|
|
|
|
require.NotPanics(t, reg)
|
|
|
|
} else {
|
|
|
|
require.Panics(t, reg)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|