// 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": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), }, { name: "first-empty", s1: NewSnapshot(map[Key]RawItem{}), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), }, { name: "second-nil", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), s2: nil, want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), }, { name: "second-empty", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), s2: NewSnapshot(map[Key]RawItem{}), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), }, { name: "no-conflicts", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), s2: NewSnapshot(map[Key]RawItem{ "Setting4": {value: 2 * time.Hour}, "Setting5": {value: VisibleByPolicy}, "Setting6": {value: ShowChoiceByPolicy}, }), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, "Setting4": {value: 2 * time.Hour}, "Setting5": {value: VisibleByPolicy}, "Setting6": {value: ShowChoiceByPolicy}, }), }, { name: "with-conflicts", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 456}, "Setting3": {value: false}, "Setting4": {value: 2 * time.Hour}, }), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 456}, "Setting2": {value: "String"}, "Setting3": {value: false}, "Setting4": {value: 2 * time.Hour}, }), }, { name: "with-scope-first-wins", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }, DeviceScope), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 456}, "Setting3": {value: false}, "Setting4": {value: 2 * time.Hour}, }, CurrentUserScope), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, "Setting4": {value: 2 * time.Hour}, }, CurrentUserScope), }, { name: "with-scope-second-wins", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }, CurrentUserScope), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 456}, "Setting3": {value: false}, "Setting4": {value: 2 * time.Hour}, }, DeviceScope), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 456}, "Setting2": {value: "String"}, "Setting3": {value: false}, "Setting4": {value: 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": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}}, DeviceScope, NewNamedOrigin("TestPolicy", DeviceScope)), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }, CurrentUserScope, NewNamedOrigin("TestPolicy", DeviceScope)), }, { name: "with-scope-second-empty", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }, CurrentUserScope), s2: NewSnapshot(map[Key]RawItem{}), want: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: 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": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), wantEqual: false, wantEqualItems: false, }, { name: "first-empty", s1: NewSnapshot(map[Key]RawItem{}), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), wantEqual: false, wantEqualItems: false, }, { name: "second-nil", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: true}, }), s2: nil, wantEqual: false, wantEqualItems: false, }, { name: "second-empty", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), s2: NewSnapshot(map[Key]RawItem{}), wantEqual: false, wantEqualItems: false, }, { name: "same-items-same-order-no-scope", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }), wantEqual: true, wantEqualItems: true, }, { name: "same-items-same-order-same-scope", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, DeviceScope), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, DeviceScope), wantEqual: true, wantEqualItems: true, }, { name: "same-items-different-order-same-scope", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, DeviceScope), s2: NewSnapshot(map[Key]RawItem{ "Setting3": {value: false}, "Setting1": {value: 123}, "Setting2": {value: "String"}, }, DeviceScope), wantEqual: true, wantEqualItems: true, }, { name: "same-items-same-order-different-scope", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, DeviceScope), s2: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, CurrentUserScope), wantEqual: false, wantEqualItems: true, }, { name: "different-items-same-scope", s1: NewSnapshot(map[Key]RawItem{ "Setting1": {value: 123}, "Setting2": {value: "String"}, "Setting3": {value: false}, }, DeviceScope), s2: NewSnapshot(map[Key]RawItem{ "Setting4": {value: 2 * time.Hour}, "Setting5": {value: VisibleByPolicy}, "Setting6": {value: 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": {value: 2 * time.Hour}, "Setting2": {value: VisibleByPolicy}, "Setting3": {value: 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": {value: 42, origin: NewNamedOrigin("Test Policy", DeviceScope)}, }), wantString: `Setting1 = 42 - {Test Policy (Device)}`, }, { name: "non-empty-with-item-error", snapshot: NewSnapshot(map[Key]RawItem{ "Setting1": {err: NewErrorText("bang!")}, }), 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) } }) } }