tailscale/util/syspolicy/setting/snapshot_test.go
Nick Khyl 2a2228f97b util/syspolicy/setting: make setting.RawItem JSON-marshallable
We add setting.RawValue, a new type that facilitates unmarshalling JSON numbers and arrays
as uint64 and []string (instead of float64 and []any) for policy setting values.
We then use it to make setting.RawItem JSON-marshallable and update the tests.

Updates #12687

Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-10-30 12:50:29 -05:00

435 lines
11 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package setting
import (
"testing"
"time"
)
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)
}
})
}
}