| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- // 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"
- "tailscale.com/util/syspolicy/pkey"
- "tailscale.com/util/syspolicy/ptype"
- )
- const (
- VisibleByPolicy = ptype.VisibleByPolicy
- ShowChoiceByPolicy = ptype.ShowChoiceByPolicy
- )
- func TestMergeSnapshots(t *testing.T) {
- tests := []struct {
- name string
- s1, s2 *Snapshot
- want *Snapshot
- }{
- {
- name: "both-nil",
- s1: nil,
- s2: nil,
- want: NewSnapshot(map[pkey.Key]RawItem{}),
- },
- {
- name: "both-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{}),
- s2: NewSnapshot(map[pkey.Key]RawItem{}),
- want: NewSnapshot(map[pkey.Key]RawItem{}),
- },
- {
- name: "first-nil",
- s1: nil,
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- },
- {
- name: "first-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{}),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- },
- {
- name: "second-nil",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- s2: nil,
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- },
- {
- name: "second-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- s2: NewSnapshot(map[pkey.Key]RawItem{}),
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- },
- {
- name: "no-conflicts",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting4": RawItemOf(2 * time.Hour),
- "Setting5": RawItemOf(VisibleByPolicy),
- "Setting6": RawItemOf(ShowChoiceByPolicy),
- }),
- want: NewSnapshot(map[pkey.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[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(456),
- "Setting3": RawItemOf(false),
- "Setting4": RawItemOf(2 * time.Hour),
- }),
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(456),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- "Setting4": RawItemOf(2 * time.Hour),
- }),
- },
- {
- name: "with-scope-first-wins",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }, DeviceScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(456),
- "Setting3": RawItemOf(false),
- "Setting4": RawItemOf(2 * time.Hour),
- }, CurrentUserScope),
- want: NewSnapshot(map[pkey.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[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }, CurrentUserScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(456),
- "Setting3": RawItemOf(false),
- "Setting4": RawItemOf(2 * time.Hour),
- }, DeviceScope),
- want: NewSnapshot(map[pkey.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[pkey.Key]RawItem{}, CurrentUserScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{}, DeviceScope),
- want: NewSnapshot(map[pkey.Key]RawItem{}, CurrentUserScope),
- },
- {
- name: "with-scope-first-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{}, CurrentUserScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true)}, DeviceScope, NewNamedOrigin("TestPolicy", DeviceScope)),
- want: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }, CurrentUserScope, NewNamedOrigin("TestPolicy", DeviceScope)),
- },
- {
- name: "with-scope-second-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }, CurrentUserScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{}),
- want: NewSnapshot(map[pkey.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[pkey.Key]RawItem{}),
- wantEqual: true,
- wantEqualItems: true,
- },
- {
- name: "empty-nil",
- s1: NewSnapshot(map[pkey.Key]RawItem{}),
- s2: nil,
- wantEqual: true,
- wantEqualItems: true,
- },
- {
- name: "empty-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{}),
- s2: NewSnapshot(map[pkey.Key]RawItem{}),
- wantEqual: true,
- wantEqualItems: true,
- },
- {
- name: "first-nil",
- s1: nil,
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- wantEqual: false,
- wantEqualItems: false,
- },
- {
- name: "first-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{}),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- wantEqual: false,
- wantEqualItems: false,
- },
- {
- name: "second-nil",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(true),
- }),
- s2: nil,
- wantEqual: false,
- wantEqualItems: false,
- },
- {
- name: "second-empty",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- s2: NewSnapshot(map[pkey.Key]RawItem{}),
- wantEqual: false,
- wantEqualItems: false,
- },
- {
- name: "same-items-same-order-no-scope",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }),
- s2: NewSnapshot(map[pkey.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[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }, DeviceScope),
- s2: NewSnapshot(map[pkey.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[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }, DeviceScope),
- s2: NewSnapshot(map[pkey.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[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }, DeviceScope),
- s2: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }, CurrentUserScope),
- wantEqual: false,
- wantEqualItems: true,
- },
- {
- name: "different-items-same-scope",
- s1: NewSnapshot(map[pkey.Key]RawItem{
- "Setting1": RawItemOf(123),
- "Setting2": RawItemOf("String"),
- "Setting3": RawItemOf(false),
- }, DeviceScope),
- s2: NewSnapshot(map[pkey.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[pkey.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[pkey.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[pkey.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[pkey.Key]RawItem{"BoolPolicy": RawItemOf(true)}),
- wantJSON: `{"Settings": {"BoolPolicy": {"Value": true}}}`,
- },
- {
- name: "Bool/False",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"BoolPolicy": RawItemOf(false)}),
- wantJSON: `{"Settings": {"BoolPolicy": {"Value": false}}}`,
- },
- {
- name: "String/Non-Empty",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"StringPolicy": RawItemOf("StringValue")}),
- wantJSON: `{"Settings": {"StringPolicy": {"Value": "StringValue"}}}`,
- },
- {
- name: "String/Empty",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"StringPolicy": RawItemOf("")}),
- wantJSON: `{"Settings": {"StringPolicy": {"Value": ""}}}`,
- },
- {
- name: "Integer/NonZero",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"IntPolicy": RawItemOf(uint64(42))}),
- wantJSON: `{"Settings": {"IntPolicy": {"Value": 42}}}`,
- },
- {
- name: "Integer/Zero",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"IntPolicy": RawItemOf(uint64(0))}),
- wantJSON: `{"Settings": {"IntPolicy": {"Value": 0}}}`,
- },
- {
- name: "String-List",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"ListPolicy": RawItemOf([]string{"Value1", "Value2"})}),
- wantJSON: `{"Settings": {"ListPolicy": {"Value": ["Value1", "Value2"]}}}`,
- },
- {
- name: "Duration/Zero",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf(time.Duration(0))}),
- wantJSON: `{"Settings": {"DurationPolicy": {"Value": "0s"}}}`,
- wantBack: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf("0s")}),
- },
- {
- name: "Duration/NonZero",
- snapshot: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf(2 * time.Hour)}),
- wantJSON: `{"Settings": {"DurationPolicy": {"Value": "2h0m0s"}}}`,
- wantBack: NewSnapshot(map[pkey.Key]RawItem{"DurationPolicy": RawItemOf("2h0m0s")}),
- },
- {
- name: "Empty/With-Summary",
- snapshot: NewSnapshot(
- map[pkey.Key]RawItem{},
- SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)),
- ),
- wantJSON: `{"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"}}`,
- },
- {
- name: "Setting/With-Summary",
- snapshot: NewSnapshot(
- map[pkey.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[pkey.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) })
- })
- }
- }
|