You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/util/syspolicy/setting/setting_test.go

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()
}