omni/internal/pkg/auth/scope/set_test.go
Edward Sammut Alessi d3ae77c0cc
chore: bump copyright to 2026
Bump copyright for conformance to 2026

Signed-off-by: Edward Sammut Alessi <edward.sammutalessi@siderolabs.com>
2026-01-21 15:30:49 +01:00

142 lines
4.1 KiB
Go

// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
package scope_test
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/omni/internal/pkg/auth/scope"
)
func TestScopeCheck(t *testing.T) {
for _, tt := range []struct { //nolint:govet
name string
actorScopes []scope.Scope
requiredScopes []scope.Scope
expectedError string
}{
{
name: "empty",
},
{
name: "actor okay",
actorScopes: []scope.Scope{
scope.ClusterAny,
scope.UserAny,
scope.MachineAny,
},
requiredScopes: []scope.Scope{
scope.New(scope.ObjectCluster, scope.ActionRead, scope.PerspectiveNone),
scope.New(scope.ObjectUser, scope.ActionDestroy, scope.PerspectiveNone),
},
},
{
name: "missing scope",
actorScopes: []scope.Scope{
scope.UserAny,
scope.MachineAny,
},
requiredScopes: []scope.Scope{
scope.New(scope.ObjectCluster, scope.ActionRead, scope.PerspectiveNone),
scope.New(scope.ObjectUser, scope.ActionDestroy, scope.PerspectiveNone),
},
expectedError: "missing required scope: cluster:read",
},
} {
t.Run(tt.name, func(t *testing.T) {
actorScopes := scope.NewScopes(tt.actorScopes...)
err := actorScopes.Check(tt.requiredScopes...)
if tt.expectedError != "" {
require.Error(t, err)
assert.EqualError(t, err, tt.expectedError)
} else {
require.NoError(t, err)
}
})
}
}
func TestParseScopes(t *testing.T) {
scopes := scope.NewScopes(scope.ClusterAny, scope.MachineAny)
assert.Equal(t, []string{"cluster:*", "machine:*"}, scopes.Strings())
parsedScopes, err := scope.ParseScopes(scopes.Strings())
require.NoError(t, err)
assert.Equal(t, scopes, parsedScopes)
}
func TestIntersection(t *testing.T) {
scopeUserRead := scope.New(scope.ObjectUser, scope.ActionRead, scope.PerspectiveNone)
scopeClusterRead := scope.New(scope.ObjectCluster, scope.ActionRead, scope.PerspectiveNone)
scopeClusterCreate := scope.New(scope.ObjectCluster, scope.ActionCreate, scope.PerspectiveNone)
scopeClusterUpgrade := scope.New(scope.ObjectCluster, scope.ActionModify, scope.PerspectiveKubernetesUpgrade)
scopeMachineDestroy := scope.New(scope.ObjectMachine, scope.ActionDestroy, scope.PerspectiveNone)
testCases := []struct {
name string
scopes1 scope.Scopes
scopes2 scope.Scopes
expected scope.Scopes
}{
{
name: "with-any",
scopes1: scope.NewScopes(scope.ClusterAny, scope.MachineAny),
scopes2: scope.NewScopes(scope.ClusterAny, scope.MachineAny, scope.UserAny),
expected: scope.NewScopes(scope.ClusterAny, scope.MachineAny),
},
{
name: "with-empty",
scopes1: scope.NewScopes(scopeUserRead, scope.MachineAny),
scopes2: scope.NewScopes(),
expected: scope.NewScopes(),
},
{
name: "mixed-1",
scopes1: scope.NewScopes(scopeClusterCreate, scope.MachineAny),
scopes2: scope.NewScopes(scope.ClusterAny, scope.MachineAny),
expected: scope.NewScopes(scopeClusterCreate, scope.MachineAny),
},
{
name: "mixed-2",
scopes1: scope.NewScopes(scope.UserDefaultScopes...),
scopes2: scope.NewScopes(scopeClusterRead),
expected: scope.NewScopes(scopeClusterRead),
},
{
name: "mixed-inverted",
scopes2: scope.NewScopes(scope.ClusterAny, scope.MachineAny),
scopes1: scope.NewScopes(scopeClusterCreate, scope.MachineAny),
expected: scope.NewScopes(scopeClusterCreate, scope.MachineAny),
},
{
name: "complex",
scopes1: scope.NewScopes(scopeUserRead, scopeMachineDestroy, scopeClusterUpgrade),
scopes2: scope.NewScopes(scope.ClusterAny, scope.MachineAny),
expected: scope.NewScopes(scopeMachineDestroy, scopeClusterUpgrade),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
intersection := tc.scopes1.Intersect(tc.scopes2)
expectedStrs := tc.expected.Strings()
actualStrs := intersection.Strings()
slices.Sort(expectedStrs)
slices.Sort(actualStrs)
assert.Equal(t, expectedStrs, actualStrs)
})
}
}