nodemut_test.go 5.0 KB

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