nodemut_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package netmap
  4. import (
  5. "fmt"
  6. "net/netip"
  7. "reflect"
  8. "testing"
  9. "time"
  10. "github.com/google/go-cmp/cmp"
  11. "tailscale.com/tailcfg"
  12. "tailscale.com/types/logger"
  13. "tailscale.com/types/opt"
  14. "tailscale.com/types/ptr"
  15. )
  16. // tests mapResponseContainsNonPatchFields
  17. func TestMapResponseContainsNonPatchFields(t *testing.T) {
  18. // reflectNonzero returns a non-zero value of the given type.
  19. reflectNonzero := func(t reflect.Type) reflect.Value {
  20. switch t.Kind() {
  21. case reflect.Bool:
  22. return reflect.ValueOf(true)
  23. case reflect.String:
  24. if reflect.TypeOf(opt.Bool("")) == t {
  25. return reflect.ValueOf("true").Convert(t)
  26. }
  27. return reflect.ValueOf("foo").Convert(t)
  28. case reflect.Int64:
  29. return reflect.ValueOf(int64(1)).Convert(t)
  30. case reflect.Slice:
  31. return reflect.MakeSlice(t, 1, 1)
  32. case reflect.Ptr:
  33. return reflect.New(t.Elem())
  34. case reflect.Map:
  35. return reflect.MakeMap(t)
  36. }
  37. panic(fmt.Sprintf("unhandled %v", t))
  38. }
  39. rt := reflect.TypeOf(tailcfg.MapResponse{})
  40. for i := 0; i < rt.NumField(); i++ {
  41. f := rt.Field(i)
  42. var want bool
  43. switch f.Name {
  44. case "MapSessionHandle", "Seq", "KeepAlive", "PingRequest", "PopBrowserURL", "ControlTime":
  45. // There are meta fields that apply to all MapResponse values.
  46. // They should be ignored.
  47. want = false
  48. case "PeersChangedPatch", "PeerSeenChange", "OnlineChange":
  49. // The actual three delta fields we care about handling.
  50. want = false
  51. default:
  52. // Everything else should be conseratively handled as a
  53. // non-delta field. We want it to return true so if
  54. // the field is not listed in the function being tested,
  55. // it'll return false and we'll fail this test.
  56. // This makes sure any new fields added to MapResponse
  57. // are accounted for here.
  58. want = true
  59. }
  60. var v tailcfg.MapResponse
  61. rv := reflect.ValueOf(&v).Elem()
  62. rv.FieldByName(f.Name).Set(reflectNonzero(f.Type))
  63. got := mapResponseContainsNonPatchFields(&v)
  64. if got != want {
  65. t.Errorf("field %q: got %v; want %v\nJSON: %v", f.Name, got, want, logger.AsJSON(v))
  66. }
  67. }
  68. }
  69. // tests MutationsFromMapResponse
  70. func TestMutationsFromMapResponse(t *testing.T) {
  71. someTime := time.Unix(123, 0)
  72. fromChanges := func(changes ...*tailcfg.PeerChange) *tailcfg.MapResponse {
  73. return &tailcfg.MapResponse{
  74. PeersChangedPatch: changes,
  75. }
  76. }
  77. muts := func(muts ...NodeMutation) []NodeMutation { return muts }
  78. tests := []struct {
  79. name string
  80. mr *tailcfg.MapResponse
  81. want []NodeMutation // nil means !ok, zero-length means none
  82. }{
  83. {
  84. name: "patch-ep",
  85. mr: fromChanges(&tailcfg.PeerChange{
  86. NodeID: 1,
  87. Endpoints: eps("1.2.3.4:567"),
  88. }, &tailcfg.PeerChange{
  89. NodeID: 2,
  90. Endpoints: eps("8.9.10.11:1234"),
  91. }),
  92. want: muts(
  93. NodeMutationEndpoints{1, []netip.AddrPort{netip.MustParseAddrPort("1.2.3.4:567")}},
  94. NodeMutationEndpoints{2, []netip.AddrPort{netip.MustParseAddrPort("8.9.10.11:1234")}},
  95. ),
  96. },
  97. {
  98. name: "patch-derp",
  99. mr: fromChanges(&tailcfg.PeerChange{
  100. NodeID: 1,
  101. DERPRegion: 2,
  102. }),
  103. want: muts(NodeMutationDERPHome{1, 2}),
  104. },
  105. {
  106. name: "patch-online",
  107. mr: fromChanges(&tailcfg.PeerChange{
  108. NodeID: 1,
  109. Online: ptr.To(true),
  110. }),
  111. want: muts(NodeMutationOnline{1, true}),
  112. },
  113. {
  114. name: "patch-online-false",
  115. mr: fromChanges(&tailcfg.PeerChange{
  116. NodeID: 1,
  117. Online: ptr.To(false),
  118. }),
  119. want: muts(NodeMutationOnline{1, false}),
  120. },
  121. {
  122. name: "patch-lastseen",
  123. mr: fromChanges(&tailcfg.PeerChange{
  124. NodeID: 1,
  125. LastSeen: ptr.To(time.Unix(12345, 0)),
  126. }),
  127. want: muts(NodeMutationLastSeen{1, time.Unix(12345, 0)}),
  128. },
  129. {
  130. name: "legacy-online-change", // the old pre-Patch style
  131. mr: &tailcfg.MapResponse{
  132. OnlineChange: map[tailcfg.NodeID]bool{
  133. 1: true,
  134. 2: false,
  135. },
  136. },
  137. want: muts(
  138. NodeMutationOnline{1, true},
  139. NodeMutationOnline{2, false},
  140. ),
  141. },
  142. {
  143. name: "legacy-lastseen-change", // the old pre-Patch style
  144. mr: &tailcfg.MapResponse{
  145. PeerSeenChange: map[tailcfg.NodeID]bool{
  146. 1: true,
  147. },
  148. },
  149. want: muts(
  150. NodeMutationLastSeen{1, someTime},
  151. ),
  152. },
  153. {
  154. name: "no-changes",
  155. mr: fromChanges(),
  156. want: make([]NodeMutation, 0), // non-nil to mean want ok but no changes
  157. },
  158. {
  159. name: "not-okay-patch-node-change",
  160. mr: &tailcfg.MapResponse{
  161. Node: &tailcfg.Node{}, // non-nil
  162. PeersChangedPatch: []*tailcfg.PeerChange{{
  163. NodeID: 1,
  164. DERPRegion: 2,
  165. }},
  166. },
  167. want: nil,
  168. },
  169. }
  170. for _, tt := range tests {
  171. t.Run(tt.name, func(t *testing.T) {
  172. got, gotOK := MutationsFromMapResponse(tt.mr, someTime)
  173. wantOK := tt.want != nil
  174. if gotOK != wantOK {
  175. t.Errorf("got ok=%v; want %v", gotOK, wantOK)
  176. } else if got == nil && gotOK {
  177. got = make([]NodeMutation, 0) // for cmd.Diff
  178. }
  179. if diff := cmp.Diff(tt.want, got,
  180. cmp.Comparer(func(a, b netip.Addr) bool { return a == b }),
  181. cmp.Comparer(func(a, b netip.AddrPort) bool { return a == b }),
  182. cmp.AllowUnexported(
  183. NodeMutationEndpoints{},
  184. NodeMutationDERPHome{},
  185. NodeMutationOnline{},
  186. NodeMutationLastSeen{},
  187. )); diff != "" {
  188. t.Errorf("wrong result (-want +got):\n%s", diff)
  189. }
  190. })
  191. }
  192. }