tailscale/util/syspolicy/setting/setting_test.go
Nick Khyl 67df9abdc6 util/syspolicy/setting: add package that contains types for the next syspolicy PRs
Package setting contains types for defining and representing policy settings.
It facilitates the registration of setting definitions using Register and RegisterDefinition,
and the retrieval of registered setting definitions via Definitions and DefinitionOf.
This package is intended for use primarily within the syspolicy package hierarchy,
and added in a preparation for the next PRs.

Updates #12687

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-08-12 21:02:35 -05:00

345 lines
11 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"slices"
"strings"
"testing"
"tailscale.com/types/lazy"
"tailscale.com/types/ptr"
"tailscale.com/util/syspolicy/internal"
)
func TestSettingDefinition(t *testing.T) {
tests := []struct {
name string
setting *Definition
osOverride string
wantKey Key
wantScope Scope
wantType Type
wantIsSupported bool
wantSupportedPlatforms PlatformList
wantString string
}{
{
name: "Nil",
setting: nil,
wantKey: "",
wantScope: 0,
wantType: InvalidValue,
wantIsSupported: false,
wantString: "(nil)",
},
{
name: "Device/Invalid",
setting: NewDefinition("TestDevicePolicySetting", DeviceSetting, InvalidValue),
wantKey: "TestDevicePolicySetting",
wantScope: DeviceSetting,
wantType: InvalidValue,
wantIsSupported: true,
wantString: `Device("TestDevicePolicySetting", Invalid)`,
},
{
name: "Device/Integer",
setting: NewDefinition("TestDevicePolicySetting", DeviceSetting, IntegerValue),
wantKey: "TestDevicePolicySetting",
wantScope: DeviceSetting,
wantType: IntegerValue,
wantIsSupported: true,
wantString: `Device("TestDevicePolicySetting", Integer)`,
},
{
name: "Profile/String",
setting: NewDefinition("TestProfilePolicySetting", ProfileSetting, StringValue),
wantKey: "TestProfilePolicySetting",
wantScope: ProfileSetting,
wantType: StringValue,
wantIsSupported: true,
wantString: `Profile("TestProfilePolicySetting", String)`,
},
{
name: "Device/StringList",
setting: NewDefinition("AllowedSuggestedExitNodes", DeviceSetting, StringListValue),
wantKey: "AllowedSuggestedExitNodes",
wantScope: DeviceSetting,
wantType: StringListValue,
wantIsSupported: true,
wantString: `Device("AllowedSuggestedExitNodes", StringList)`,
},
{
name: "Device/PreferenceOption",
setting: NewDefinition("AdvertiseExitNode", DeviceSetting, PreferenceOptionValue),
wantKey: "AdvertiseExitNode",
wantScope: DeviceSetting,
wantType: PreferenceOptionValue,
wantIsSupported: true,
wantString: `Device("AdvertiseExitNode", PreferenceOption)`,
},
{
name: "User/Boolean",
setting: NewDefinition("TestUserPolicySetting", UserSetting, BooleanValue),
wantKey: "TestUserPolicySetting",
wantScope: UserSetting,
wantType: BooleanValue,
wantIsSupported: true,
wantString: `User("TestUserPolicySetting", Boolean)`,
},
{
name: "User/Visibility",
setting: NewDefinition("AdminConsole", UserSetting, VisibilityValue),
wantKey: "AdminConsole",
wantScope: UserSetting,
wantType: VisibilityValue,
wantIsSupported: true,
wantString: `User("AdminConsole", Visibility)`,
},
{
name: "User/Duration",
setting: NewDefinition("KeyExpirationNotice", UserSetting, DurationValue),
wantKey: "KeyExpirationNotice",
wantScope: UserSetting,
wantType: DurationValue,
wantIsSupported: true,
wantString: `User("KeyExpirationNotice", Duration)`,
},
{
name: "SupportedSetting",
setting: NewDefinition("DesktopPolicySetting", DeviceSetting, StringValue, "macos", "windows"),
osOverride: "windows",
wantKey: "DesktopPolicySetting",
wantScope: DeviceSetting,
wantType: StringValue,
wantIsSupported: true,
wantSupportedPlatforms: PlatformList{"macos", "windows"},
wantString: `Device("DesktopPolicySetting", String)`,
},
{
name: "UnsupportedSetting",
setting: NewDefinition("AndroidPolicySetting", DeviceSetting, StringValue, "android"),
osOverride: "macos",
wantKey: "AndroidPolicySetting",
wantScope: DeviceSetting,
wantType: StringValue,
wantIsSupported: false,
wantSupportedPlatforms: PlatformList{"android"},
wantString: `Device("AndroidPolicySetting", String)`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.osOverride != "" {
internal.OSForTesting.SetForTest(t, tt.osOverride, nil)
}
if !tt.setting.Equal(tt.setting) {
t.Errorf("the setting should be equal to itself")
}
if tt.setting != nil && !tt.setting.Equal(ptr.To(*tt.setting)) {
t.Errorf("the setting should be equal to its shallow copy")
}
if gotKey := tt.setting.Key(); gotKey != tt.wantKey {
t.Errorf("Key: got %q, want %q", gotKey, tt.wantKey)
}
if gotScope := tt.setting.Scope(); gotScope != tt.wantScope {
t.Errorf("Scope: got %v, want %v", gotScope, tt.wantScope)
}
if gotType := tt.setting.Type(); gotType != tt.wantType {
t.Errorf("Type: got %v, want %v", gotType, tt.wantType)
}
if gotIsSupported := tt.setting.IsSupported(); gotIsSupported != tt.wantIsSupported {
t.Errorf("IsSupported: got %v, want %v", gotIsSupported, tt.wantIsSupported)
}
if gotSupportedPlatforms := tt.setting.SupportedPlatforms(); !slices.Equal(gotSupportedPlatforms, tt.wantSupportedPlatforms) {
t.Errorf("SupportedPlatforms: got %v, want %v", gotSupportedPlatforms, tt.wantSupportedPlatforms)
}
if gotString := tt.setting.String(); gotString != tt.wantString {
t.Errorf("String: got %v, want %v", gotString, tt.wantString)
}
})
}
}
func TestRegisterSettingDefinition(t *testing.T) {
const testPolicySettingKey Key = "TestPolicySetting"
tests := []struct {
name string
key Key
wantEq *Definition
wantErr error
}{
{
name: "GetRegistered",
key: "TestPolicySetting",
wantEq: NewDefinition(testPolicySettingKey, DeviceSetting, StringValue),
},
{
name: "GetNonRegistered",
key: "OtherPolicySetting",
wantEq: nil,
wantErr: ErrNoSuchKey,
},
}
resetSettingDefinitions(t)
Register(testPolicySettingKey, DeviceSetting, StringValue)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotErr := DefinitionOf(tt.key)
if gotErr != tt.wantErr {
t.Errorf("gotErr %v, wantErr %v", gotErr, tt.wantErr)
}
if !got.Equal(tt.wantEq) {
t.Errorf("got %v, want %v", got, tt.wantEq)
}
})
}
}
func TestRegisterAfterUsePanics(t *testing.T) {
resetSettingDefinitions(t)
Register("TestPolicySetting", DeviceSetting, StringValue)
DefinitionOf("TestPolicySetting")
func() {
defer func() {
if gotPanic, wantPanic := recover(), "policy definitions are already in use"; gotPanic != wantPanic {
t.Errorf("gotPanic: %q, wantPanic: %q", gotPanic, wantPanic)
}
}()
Register("TestPolicySetting", DeviceSetting, StringValue)
}()
}
func TestRegisterDuplicateSettings(t *testing.T) {
tests := []struct {
name string
settings []*Definition
wantEq *Definition
wantErrStr string
}{
{
name: "NoConflict/Exact",
settings: []*Definition{
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
},
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
},
{
name: "NoConflict/MergeOS-First",
settings: []*Definition{
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "android", "macos"),
NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
},
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
},
{
name: "NoConflict/MergeOS-Second",
settings: []*Definition{
NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "android", "macos"),
},
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
},
{
name: "NoConflict/MergeOS-Both",
settings: []*Definition{
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "macos"),
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "windows"),
},
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "macos", "windows"),
},
{
name: "Conflict/Scope",
settings: []*Definition{
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
NewDefinition("TestPolicySetting", UserSetting, StringValue),
},
wantEq: nil,
wantErrStr: `duplicate policy definition: "TestPolicySetting"`,
},
{
name: "Conflict/Type",
settings: []*Definition{
NewDefinition("TestPolicySetting", UserSetting, StringValue),
NewDefinition("TestPolicySetting", UserSetting, IntegerValue),
},
wantEq: nil,
wantErrStr: `duplicate policy definition: "TestPolicySetting"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resetSettingDefinitions(t)
for _, s := range tt.settings {
Register(s.Key(), s.Scope(), s.Type(), s.SupportedPlatforms()...)
}
got, err := DefinitionOf("TestPolicySetting")
var gotErrStr string
if err != nil {
gotErrStr = err.Error()
}
if gotErrStr != tt.wantErrStr {
t.Fatalf("ErrStr: got %q, want %q", gotErrStr, tt.wantErrStr)
}
if !got.Equal(tt.wantEq) {
t.Errorf("Definition got %v, want %v", got, tt.wantEq)
}
if !slices.Equal(got.SupportedPlatforms(), tt.wantEq.SupportedPlatforms()) {
t.Errorf("SupportedPlatforms got %v, want %v", got.SupportedPlatforms(), tt.wantEq.SupportedPlatforms())
}
})
}
}
func TestListSettingDefinitions(t *testing.T) {
definitions := []*Definition{
NewDefinition("TestDevicePolicySetting", DeviceSetting, IntegerValue),
NewDefinition("TestProfilePolicySetting", ProfileSetting, StringValue),
NewDefinition("TestUserPolicySetting", UserSetting, BooleanValue),
NewDefinition("TestStringListPolicySetting", DeviceSetting, StringListValue),
}
if err := SetDefinitionsForTest(t, definitions...); err != nil {
t.Fatalf("SetDefinitionsForTest failed: %v", err)
}
cmp := func(l, r *Definition) int {
return strings.Compare(string(l.Key()), string(r.Key()))
}
want := append([]*Definition{}, definitions...)
slices.SortFunc(want, cmp)
got, err := Definitions()
if err != nil {
t.Fatalf("Definitions failed: %v", err)
}
slices.SortFunc(got, cmp)
if !slices.Equal(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
func resetSettingDefinitions(t *testing.T) {
t.Cleanup(func() {
definitionsMu.Lock()
definitionsList = nil
definitions = lazy.SyncValue[DefinitionMap]{}
definitionsUsed = false
definitionsMu.Unlock()
})
definitionsMu.Lock()
definitionsList = nil
definitions = lazy.SyncValue[DefinitionMap]{}
definitionsUsed = false
definitionsMu.Unlock()
}