mirror of https://github.com/tailscale/tailscale/
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.
570 lines
15 KiB
Go
570 lines
15 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package setting
|
|
|
|
import (
|
|
"cmp"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
jsonv2 "github.com/go-json-experiment/json"
|
|
"tailscale.com/util/syspolicy/internal"
|
|
)
|
|
|
|
func TestMergeSnapshots(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s1, s2 *Snapshot
|
|
want *Snapshot
|
|
}{
|
|
{
|
|
name: "both-nil",
|
|
s1: nil,
|
|
s2: nil,
|
|
want: NewSnapshot(map[Key]RawItem{}),
|
|
},
|
|
{
|
|
name: "both-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}),
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
want: NewSnapshot(map[Key]RawItem{}),
|
|
},
|
|
{
|
|
name: "first-nil",
|
|
s1: nil,
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
},
|
|
{
|
|
name: "first-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
},
|
|
{
|
|
name: "second-nil",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
s2: nil,
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
},
|
|
{
|
|
name: "second-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
},
|
|
{
|
|
name: "no-conflicts",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
"Setting5": RawItemOf(VisibleByPolicy),
|
|
"Setting6": RawItemOf(ShowChoiceByPolicy),
|
|
}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
"Setting5": RawItemOf(VisibleByPolicy),
|
|
"Setting6": RawItemOf(ShowChoiceByPolicy),
|
|
}),
|
|
},
|
|
{
|
|
name: "with-conflicts",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(456),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(456),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}),
|
|
},
|
|
{
|
|
name: "with-scope-first-wins",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}, DeviceScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(456),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}, CurrentUserScope),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}, CurrentUserScope),
|
|
},
|
|
{
|
|
name: "with-scope-second-wins",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}, CurrentUserScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(456),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}, DeviceScope),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(456),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
}, CurrentUserScope),
|
|
},
|
|
{
|
|
name: "with-scope-both-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}, CurrentUserScope),
|
|
s2: NewSnapshot(map[Key]RawItem{}, DeviceScope),
|
|
want: NewSnapshot(map[Key]RawItem{}, CurrentUserScope),
|
|
},
|
|
{
|
|
name: "with-scope-first-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}, CurrentUserScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true)}, DeviceScope, NewNamedOrigin("TestPolicy", DeviceScope)),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}, CurrentUserScope, NewNamedOrigin("TestPolicy", DeviceScope)),
|
|
},
|
|
{
|
|
name: "with-scope-second-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}, CurrentUserScope),
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
want: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}, CurrentUserScope),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := MergeSnapshots(tt.s1, tt.s2)
|
|
if !got.Equal(tt.want) {
|
|
t.Errorf("got %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSnapshotEqual(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
s1, s2 *Snapshot
|
|
wantEqual bool
|
|
wantEqualItems bool
|
|
}{
|
|
{
|
|
name: "nil-nil",
|
|
s1: nil,
|
|
s2: nil,
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "nil-empty",
|
|
s1: nil,
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "empty-nil",
|
|
s1: NewSnapshot(map[Key]RawItem{}),
|
|
s2: nil,
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "empty-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}),
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "first-nil",
|
|
s1: nil,
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
wantEqual: false,
|
|
wantEqualItems: false,
|
|
},
|
|
{
|
|
name: "first-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{}),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
wantEqual: false,
|
|
wantEqualItems: false,
|
|
},
|
|
{
|
|
name: "second-nil",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(true),
|
|
}),
|
|
s2: nil,
|
|
wantEqual: false,
|
|
wantEqualItems: false,
|
|
},
|
|
{
|
|
name: "second-empty",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
s2: NewSnapshot(map[Key]RawItem{}),
|
|
wantEqual: false,
|
|
wantEqualItems: false,
|
|
},
|
|
{
|
|
name: "same-items-same-order-no-scope",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}),
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "same-items-same-order-same-scope",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, DeviceScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, DeviceScope),
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "same-items-different-order-same-scope",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, DeviceScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting3": RawItemOf(false),
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
}, DeviceScope),
|
|
wantEqual: true,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "same-items-same-order-different-scope",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, DeviceScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, CurrentUserScope),
|
|
wantEqual: false,
|
|
wantEqualItems: true,
|
|
},
|
|
{
|
|
name: "different-items-same-scope",
|
|
s1: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(123),
|
|
"Setting2": RawItemOf("String"),
|
|
"Setting3": RawItemOf(false),
|
|
}, DeviceScope),
|
|
s2: NewSnapshot(map[Key]RawItem{
|
|
"Setting4": RawItemOf(2 * time.Hour),
|
|
"Setting5": RawItemOf(VisibleByPolicy),
|
|
"Setting6": RawItemOf(ShowChoiceByPolicy),
|
|
}, DeviceScope),
|
|
wantEqual: false,
|
|
wantEqualItems: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if gotEqual := tt.s1.Equal(tt.s2); gotEqual != tt.wantEqual {
|
|
t.Errorf("WantEqual: got %v, want %v", gotEqual, tt.wantEqual)
|
|
}
|
|
if gotEqualItems := tt.s1.EqualItems(tt.s2); gotEqualItems != tt.wantEqualItems {
|
|
t.Errorf("WantEqualItems: got %v, want %v", gotEqualItems, tt.wantEqualItems)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSnapshotString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
snapshot *Snapshot
|
|
wantString string
|
|
}{
|
|
{
|
|
name: "nil",
|
|
snapshot: nil,
|
|
wantString: "{Empty}",
|
|
},
|
|
{
|
|
name: "empty",
|
|
snapshot: NewSnapshot(nil),
|
|
wantString: "{Empty}",
|
|
},
|
|
{
|
|
name: "empty-with-scope",
|
|
snapshot: NewSnapshot(nil, DeviceScope),
|
|
wantString: "{Empty, Device}",
|
|
},
|
|
{
|
|
name: "empty-with-origin",
|
|
snapshot: NewSnapshot(nil, NewNamedOrigin("Test Policy", DeviceScope)),
|
|
wantString: "{Empty, Test Policy (Device)}",
|
|
},
|
|
{
|
|
name: "non-empty",
|
|
snapshot: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemOf(2 * time.Hour),
|
|
"Setting2": RawItemOf(VisibleByPolicy),
|
|
"Setting3": RawItemOf(ShowChoiceByPolicy),
|
|
}, NewNamedOrigin("Test Policy", DeviceScope)),
|
|
wantString: `{Test Policy (Device)}
|
|
Setting1 = 2h0m0s
|
|
Setting2 = show
|
|
Setting3 = user-decides`,
|
|
},
|
|
{
|
|
name: "non-empty-with-item-origin",
|
|
snapshot: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemWith(42, nil, NewNamedOrigin("Test Policy", DeviceScope)),
|
|
}),
|
|
wantString: `Setting1 = 42 - {Test Policy (Device)}`,
|
|
},
|
|
{
|
|
name: "non-empty-with-item-error",
|
|
snapshot: NewSnapshot(map[Key]RawItem{
|
|
"Setting1": RawItemWith(nil, NewErrorText("bang!"), nil),
|
|
}),
|
|
wantString: `Setting1 = Error{"bang!"}`,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if gotString := tt.snapshot.String(); gotString != tt.wantString {
|
|
t.Errorf("got %v\nwant %v", gotString, tt.wantString)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMarshalUnmarshalSnapshot(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
snapshot *Snapshot
|
|
wantJSON string
|
|
wantBack *Snapshot
|
|
}{
|
|
{
|
|
name: "Nil",
|
|
snapshot: (*Snapshot)(nil),
|
|
wantJSON: "null",
|
|
wantBack: NewSnapshot(nil),
|
|
},
|
|
{
|
|
name: "Zero",
|
|
snapshot: &Snapshot{},
|
|
wantJSON: "{}",
|
|
},
|
|
{
|
|
name: "Bool/True",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(true)}),
|
|
wantJSON: `{"Settings": {"BoolPolicy": {"Value": true}}}`,
|
|
},
|
|
{
|
|
name: "Bool/False",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(false)}),
|
|
wantJSON: `{"Settings": {"BoolPolicy": {"Value": false}}}`,
|
|
},
|
|
{
|
|
name: "String/Non-Empty",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("StringValue")}),
|
|
wantJSON: `{"Settings": {"StringPolicy": {"Value": "StringValue"}}}`,
|
|
},
|
|
{
|
|
name: "String/Empty",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("")}),
|
|
wantJSON: `{"Settings": {"StringPolicy": {"Value": ""}}}`,
|
|
},
|
|
{
|
|
name: "Integer/NonZero",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(42))}),
|
|
wantJSON: `{"Settings": {"IntPolicy": {"Value": 42}}}`,
|
|
},
|
|
{
|
|
name: "Integer/Zero",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(0))}),
|
|
wantJSON: `{"Settings": {"IntPolicy": {"Value": 0}}}`,
|
|
},
|
|
{
|
|
name: "String-List",
|
|
snapshot: NewSnapshot(map[Key]RawItem{"ListPolicy": RawItemOf([]string{"Value1", "Value2"})}),
|
|
wantJSON: `{"Settings": {"ListPolicy": {"Value": ["Value1", "Value2"]}}}`,
|
|
},
|
|
{
|
|
name: "Empty/With-Summary",
|
|
snapshot: NewSnapshot(
|
|
map[Key]RawItem{},
|
|
SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)),
|
|
),
|
|
wantJSON: `{"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"}}`,
|
|
},
|
|
{
|
|
name: "Setting/With-Summary",
|
|
snapshot: NewSnapshot(
|
|
map[Key]RawItem{"PolicySetting": RawItemOf(uint64(42))},
|
|
SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)),
|
|
),
|
|
wantJSON: `{
|
|
"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"},
|
|
"Settings": {"PolicySetting": {"Value": 42}}
|
|
}`,
|
|
},
|
|
{
|
|
name: "Settings/With-Origins",
|
|
snapshot: NewSnapshot(
|
|
map[Key]RawItem{
|
|
"SettingA": RawItemWith(uint64(42), nil, NewNamedOrigin("SourceA", DeviceScope)),
|
|
"SettingB": RawItemWith("B", nil, NewNamedOrigin("SourceB", CurrentProfileScope)),
|
|
"SettingC": RawItemWith(true, nil, NewNamedOrigin("SourceC", CurrentUserScope)),
|
|
},
|
|
),
|
|
wantJSON: `{
|
|
"Settings": {
|
|
"SettingA": {"Value": 42, "Origin": {"Name": "SourceA", "Scope": "Device"}},
|
|
"SettingB": {"Value": "B", "Origin": {"Name": "SourceB", "Scope": "Profile"}},
|
|
"SettingC": {"Value": true, "Origin": {"Name": "SourceC", "Scope": "User"}}
|
|
}
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
doTest := func(t *testing.T, useJSONv2 bool) {
|
|
var gotJSON []byte
|
|
var err error
|
|
if useJSONv2 {
|
|
gotJSON, err = jsonv2.Marshal(tt.snapshot)
|
|
} else {
|
|
gotJSON, err = json.Marshal(tt.snapshot)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got, want, equal := internal.EqualJSONForTest(t, gotJSON, []byte(tt.wantJSON)); !equal {
|
|
t.Errorf("JSON: got %s; want %s", got, want)
|
|
}
|
|
|
|
gotBack := &Snapshot{}
|
|
if useJSONv2 {
|
|
err = jsonv2.Unmarshal(gotJSON, &gotBack)
|
|
} else {
|
|
err = json.Unmarshal(gotJSON, &gotBack)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if wantBack := cmp.Or(tt.wantBack, tt.snapshot); !gotBack.Equal(wantBack) {
|
|
t.Errorf("Snapshot: got %+v; want %+v", gotBack, wantBack)
|
|
}
|
|
}
|
|
|
|
t.Run("json", func(t *testing.T) { doTest(t, false) })
|
|
t.Run("jsonv2", func(t *testing.T) { doTest(t, true) })
|
|
})
|
|
}
|
|
}
|