Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
package netmap
import ( "fmt" "net/netip" "reflect" "testing" "time"
"github.com/google/go-cmp/cmp" "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/types/opt")
// tests mapResponseContainsNonPatchFieldsfunc TestMapResponseContainsNonPatchFields(t *testing.T) {
// reflectNonzero returns a non-zero value of the given type. reflectNonzero := func(t reflect.Type) reflect.Value {
switch t.Kind() { case reflect.Bool: return reflect.ValueOf(true) case reflect.String: if reflect.TypeFor[opt.Bool]() == t { return reflect.ValueOf("true").Convert(t) } return reflect.ValueOf("foo").Convert(t) case reflect.Int64: return reflect.ValueOf(int64(1)).Convert(t) case reflect.Slice: return reflect.MakeSlice(t, 1, 1) case reflect.Pointer: return reflect.New(t.Elem()) case reflect.Map: return reflect.MakeMap(t) } panic(fmt.Sprintf("unhandled %v", t)) }
rt := reflect.TypeFor[tailcfg.MapResponse]() for f := range rt.Fields() {
var want bool switch f.Name { case "MapSessionHandle", "Seq", "KeepAlive", "PingRequest", "PopBrowserURL", "ControlTime": // There are meta fields that apply to all MapResponse values. // They should be ignored. want = false case "PeersChangedPatch", "PeerSeenChange", "OnlineChange": // The three legacy delta fields handled via NodeMutation patches. want = false case "PeersChanged", "PeersRemoved": // Now carried as NodeMutationUpsert / NodeMutationRemove entries. want = false case "PacketFilter", "PacketFilters": // Now delivered separately via PacketFilterUpdater. want = false case "UserProfiles": // Now delivered separately via UserProfileUpdater. want = false default: // Everything else should be conseratively handled as a // non-delta field. We want it to return true so if // the field is not listed in the function being tested, // it'll return false and we'll fail this test. // This makes sure any new fields added to MapResponse // are accounted for here. want = true }
var v tailcfg.MapResponse rv := reflect.ValueOf(&v).Elem() rv.FieldByName(f.Name).Set(reflectNonzero(f.Type))
got := mapResponseContainsNonPatchFields(&v) if got != want { t.Errorf("field %q: got %v; want %v\nJSON: %v", f.Name, got, want, logger.AsJSON(v)) } }}
// tests MutationsFromMapResponsefunc TestMutationsFromMapResponse(t *testing.T) { someTime := time.Unix(123, 0) fromChanges := func(changes ...*tailcfg.PeerChange) *tailcfg.MapResponse { return &tailcfg.MapResponse{ PeersChangedPatch: changes, } } muts := func(muts ...NodeMutation) []NodeMutation { return muts } tests := []struct { name string mr *tailcfg.MapResponse want []NodeMutation // nil means !ok, zero-length means none }{ { name: "patch-ep", mr: fromChanges(&tailcfg.PeerChange{ NodeID: 1, Endpoints: eps("1.2.3.4:567"), }, &tailcfg.PeerChange{ NodeID: 2, Endpoints: eps("8.9.10.11:1234"), }), want: muts( NodeMutationEndpoints{1, []netip.AddrPort{netip.MustParseAddrPort("1.2.3.4:567")}}, NodeMutationEndpoints{2, []netip.AddrPort{netip.MustParseAddrPort("8.9.10.11:1234")}}, ), }, { name: "patch-derp", mr: fromChanges(&tailcfg.PeerChange{ NodeID: 1, DERPRegion: 2, }), want: muts(NodeMutationDERPHome{1, 2}), }, { name: "patch-online", mr: fromChanges(&tailcfg.PeerChange{ NodeID: 1, Online: new(true), }), want: muts(NodeMutationOnline{1, true}), }, { name: "patch-online-false", mr: fromChanges(&tailcfg.PeerChange{ NodeID: 1, Online: new(false), }), want: muts(NodeMutationOnline{1, false}), }, { name: "patch-lastseen", mr: fromChanges(&tailcfg.PeerChange{ NodeID: 1, LastSeen: new(time.Unix(12345, 0)), }), want: muts(NodeMutationLastSeen{1, time.Unix(12345, 0)}), }, { name: "legacy-online-change", // the old pre-Patch style mr: &tailcfg.MapResponse{ OnlineChange: map[tailcfg.NodeID]bool{ 1: true, 2: false, }, }, want: muts( NodeMutationOnline{1, true}, NodeMutationOnline{2, false}, ), }, { name: "legacy-lastseen-change", // the old pre-Patch style mr: &tailcfg.MapResponse{ PeerSeenChange: map[tailcfg.NodeID]bool{ 1: true, }, }, want: muts( NodeMutationLastSeen{1, someTime}, ), }, { name: "no-changes", mr: fromChanges(), want: make([]NodeMutation, 0), // non-nil to mean want ok but no changes }, { name: "not-okay-patch-node-change", mr: &tailcfg.MapResponse{ Node: &tailcfg.Node{}, // non-nil PeersChangedPatch: []*tailcfg.PeerChange{{ NodeID: 1, DERPRegion: 2, }}, }, want: nil, }, { name: "peer-removed", mr: &tailcfg.MapResponse{ PeersRemoved: []tailcfg.NodeID{5}, }, want: muts(NodeMutationRemove{5}), }, { name: "peer-added", mr: &tailcfg.MapResponse{ PeersChanged: []*tailcfg.Node{{ID: 7}}, }, want: muts(NodeMutationUpsert{Node: (&tailcfg.Node{ID: 7}).View()}), }, { name: "add-and-remove-mixed-with-patch", mr: &tailcfg.MapResponse{ PeersRemoved: []tailcfg.NodeID{3}, PeersChanged: []*tailcfg.Node{{ID: 7}}, PeersChangedPatch: []*tailcfg.PeerChange{{ NodeID: 5, DERPRegion: 2, }}, }, want: muts( NodeMutationRemove{3}, NodeMutationDERPHome{5, 2}, NodeMutationUpsert{Node: (&tailcfg.Node{ID: 7}).View()}, ), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, gotOK := MutationsFromMapResponse(tt.mr, someTime) wantOK := tt.want != nil if gotOK != wantOK { t.Errorf("got ok=%v; want %v", gotOK, wantOK) } else if got == nil && gotOK { got = make([]NodeMutation, 0) // for cmd.Diff } if diff := cmp.Diff(tt.want, got, cmp.Comparer(func(a, b netip.Addr) bool { return a == b }), cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }), cmp.Comparer(func(a, b tailcfg.NodeView) bool { return a.ID() == b.ID() }), cmp.AllowUnexported( NodeMutationEndpoints{}, NodeMutationDERPHome{}, NodeMutationOnline{}, NodeMutationLastSeen{}, NodeMutationRemove{}, )); diff != "" { t.Errorf("wrong result (-want +got):\n%s", diff) } }) }}