tailcfg_test.go 21 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tailcfg_test
  4. import (
  5. "encoding/json"
  6. "net/netip"
  7. "os"
  8. "reflect"
  9. "regexp"
  10. "strconv"
  11. "testing"
  12. "time"
  13. . "tailscale.com/tailcfg"
  14. "tailscale.com/tstest/deptest"
  15. "tailscale.com/types/key"
  16. "tailscale.com/types/opt"
  17. "tailscale.com/types/ptr"
  18. "tailscale.com/util/must"
  19. )
  20. func fieldsOf(t reflect.Type) (fields []string) {
  21. for i := range t.NumField() {
  22. fields = append(fields, t.Field(i).Name)
  23. }
  24. return
  25. }
  26. func TestHostinfoEqual(t *testing.T) {
  27. hiHandles := []string{
  28. "IPNVersion",
  29. "FrontendLogID",
  30. "BackendLogID",
  31. "OS",
  32. "OSVersion",
  33. "Container",
  34. "Env",
  35. "Distro",
  36. "DistroVersion",
  37. "DistroCodeName",
  38. "App",
  39. "Desktop",
  40. "Package",
  41. "DeviceModel",
  42. "PushDeviceToken",
  43. "Hostname",
  44. "ShieldsUp",
  45. "ShareeNode",
  46. "NoLogsNoSupport",
  47. "WireIngress",
  48. "IngressEnabled",
  49. "AllowsUpdate",
  50. "Machine",
  51. "GoArch",
  52. "GoArchVar",
  53. "GoVersion",
  54. "RoutableIPs",
  55. "RequestTags",
  56. "WoLMACs",
  57. "Services",
  58. "NetInfo",
  59. "SSH_HostKeys",
  60. "Cloud",
  61. "Userspace",
  62. "UserspaceRouter",
  63. "AppConnector",
  64. "ServicesHash",
  65. "PeerRelay",
  66. "ExitNodeID",
  67. "Location",
  68. "TPM",
  69. "StateEncrypted",
  70. }
  71. if have := fieldsOf(reflect.TypeFor[Hostinfo]()); !reflect.DeepEqual(have, hiHandles) {
  72. t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
  73. have, hiHandles)
  74. }
  75. nets := func(strs ...string) (ns []netip.Prefix) {
  76. for _, s := range strs {
  77. n, err := netip.ParsePrefix(s)
  78. if err != nil {
  79. panic(err)
  80. }
  81. ns = append(ns, n)
  82. }
  83. return ns
  84. }
  85. tests := []struct {
  86. a, b *Hostinfo
  87. want bool
  88. }{
  89. {
  90. nil,
  91. nil,
  92. true,
  93. },
  94. {
  95. &Hostinfo{},
  96. nil,
  97. false,
  98. },
  99. {
  100. nil,
  101. &Hostinfo{},
  102. false,
  103. },
  104. {
  105. &Hostinfo{},
  106. &Hostinfo{},
  107. true,
  108. },
  109. {
  110. &Hostinfo{IPNVersion: "1"},
  111. &Hostinfo{IPNVersion: "2"},
  112. false,
  113. },
  114. {
  115. &Hostinfo{IPNVersion: "2"},
  116. &Hostinfo{IPNVersion: "2"},
  117. true,
  118. },
  119. {
  120. &Hostinfo{FrontendLogID: "1"},
  121. &Hostinfo{FrontendLogID: "2"},
  122. false,
  123. },
  124. {
  125. &Hostinfo{FrontendLogID: "2"},
  126. &Hostinfo{FrontendLogID: "2"},
  127. true,
  128. },
  129. {
  130. &Hostinfo{BackendLogID: "1"},
  131. &Hostinfo{BackendLogID: "2"},
  132. false,
  133. },
  134. {
  135. &Hostinfo{BackendLogID: "2"},
  136. &Hostinfo{BackendLogID: "2"},
  137. true,
  138. },
  139. {
  140. &Hostinfo{OS: "windows"},
  141. &Hostinfo{OS: "linux"},
  142. false,
  143. },
  144. {
  145. &Hostinfo{OS: "windows"},
  146. &Hostinfo{OS: "windows"},
  147. true,
  148. },
  149. {
  150. &Hostinfo{Hostname: "vega"},
  151. &Hostinfo{Hostname: "iris"},
  152. false,
  153. },
  154. {
  155. &Hostinfo{Hostname: "vega"},
  156. &Hostinfo{Hostname: "vega"},
  157. true,
  158. },
  159. {
  160. &Hostinfo{RoutableIPs: nil},
  161. &Hostinfo{RoutableIPs: nets("10.0.0.0/16")},
  162. false,
  163. },
  164. {
  165. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  166. &Hostinfo{RoutableIPs: nets("10.2.0.0/16", "192.168.2.0/24")},
  167. false,
  168. },
  169. {
  170. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  171. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.2.0/24")},
  172. false,
  173. },
  174. {
  175. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  176. &Hostinfo{RoutableIPs: nets("10.1.0.0/16", "192.168.1.0/24")},
  177. true,
  178. },
  179. {
  180. &Hostinfo{RequestTags: []string{"abc", "def"}},
  181. &Hostinfo{RequestTags: []string{"abc", "def"}},
  182. true,
  183. },
  184. {
  185. &Hostinfo{RequestTags: []string{"abc", "def"}},
  186. &Hostinfo{RequestTags: []string{"abc", "123"}},
  187. false,
  188. },
  189. {
  190. &Hostinfo{RequestTags: []string{}},
  191. &Hostinfo{RequestTags: []string{"abc"}},
  192. false,
  193. },
  194. {
  195. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  196. &Hostinfo{Services: []Service{{Proto: UDP, Port: 2345, Description: "bar"}}},
  197. false,
  198. },
  199. {
  200. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  201. &Hostinfo{Services: []Service{{Proto: TCP, Port: 1234, Description: "foo"}}},
  202. true,
  203. },
  204. {
  205. &Hostinfo{ShareeNode: true},
  206. &Hostinfo{},
  207. false,
  208. },
  209. {
  210. &Hostinfo{SSH_HostKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO.... root@bar"}},
  211. &Hostinfo{},
  212. false,
  213. },
  214. {
  215. &Hostinfo{App: "golink"},
  216. &Hostinfo{App: "abc"},
  217. false,
  218. },
  219. {
  220. &Hostinfo{App: "golink"},
  221. &Hostinfo{App: "golink"},
  222. true,
  223. },
  224. {
  225. &Hostinfo{AppConnector: opt.Bool("true")},
  226. &Hostinfo{AppConnector: opt.Bool("true")},
  227. true,
  228. },
  229. {
  230. &Hostinfo{AppConnector: opt.Bool("true")},
  231. &Hostinfo{AppConnector: opt.Bool("false")},
  232. false,
  233. },
  234. {
  235. &Hostinfo{PeerRelay: true},
  236. &Hostinfo{PeerRelay: true},
  237. true,
  238. },
  239. {
  240. &Hostinfo{PeerRelay: true},
  241. &Hostinfo{PeerRelay: false},
  242. false,
  243. },
  244. {
  245. &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"},
  246. &Hostinfo{ServicesHash: "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049"},
  247. true,
  248. },
  249. {
  250. &Hostinfo{ServicesHash: "084c799cd551dd1d8d5c5f9a5d593b2e931f5e36122ee5c793c1d08a19839cc0"},
  251. &Hostinfo{},
  252. false,
  253. },
  254. {
  255. &Hostinfo{IngressEnabled: true},
  256. &Hostinfo{},
  257. false,
  258. },
  259. {
  260. &Hostinfo{IngressEnabled: true},
  261. &Hostinfo{IngressEnabled: true},
  262. true,
  263. },
  264. {
  265. &Hostinfo{IngressEnabled: false},
  266. &Hostinfo{},
  267. true,
  268. },
  269. {
  270. &Hostinfo{IngressEnabled: false},
  271. &Hostinfo{IngressEnabled: true},
  272. false,
  273. },
  274. {
  275. &Hostinfo{ExitNodeID: "stable-exit"},
  276. &Hostinfo{ExitNodeID: "stable-exit"},
  277. true,
  278. },
  279. {
  280. &Hostinfo{ExitNodeID: ""},
  281. &Hostinfo{},
  282. true,
  283. },
  284. {
  285. &Hostinfo{ExitNodeID: ""},
  286. &Hostinfo{ExitNodeID: "stable-exit"},
  287. false,
  288. },
  289. }
  290. for i, tt := range tests {
  291. got := tt.a.Equal(tt.b)
  292. if got != tt.want {
  293. t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
  294. }
  295. }
  296. }
  297. func TestHostinfoTailscaleSSHEnabled(t *testing.T) {
  298. tests := []struct {
  299. hi *Hostinfo
  300. want bool
  301. }{
  302. {
  303. nil,
  304. false,
  305. },
  306. {
  307. &Hostinfo{},
  308. false,
  309. },
  310. {
  311. &Hostinfo{SSH_HostKeys: []string{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO.... root@bar"}},
  312. true,
  313. },
  314. }
  315. for i, tt := range tests {
  316. got := tt.hi.TailscaleSSHEnabled()
  317. if got != tt.want {
  318. t.Errorf("%d. got %v; want %v", i, got, tt.want)
  319. }
  320. }
  321. }
  322. func TestNodeEqual(t *testing.T) {
  323. nodeHandles := []string{
  324. "ID", "StableID", "Name", "User", "Sharer",
  325. "Key", "KeyExpiry", "KeySignature", "Machine", "DiscoKey",
  326. "Addresses", "AllowedIPs", "Endpoints", "LegacyDERPString", "HomeDERP", "Hostinfo",
  327. "Created", "Cap", "Tags", "PrimaryRoutes",
  328. "LastSeen", "Online", "MachineAuthorized",
  329. "Capabilities", "CapMap",
  330. "UnsignedPeerAPIOnly",
  331. "ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
  332. "DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
  333. "SelfNodeV6MasqAddrForThisPeer", "IsWireGuardOnly", "IsJailed", "ExitNodeDNSResolvers",
  334. }
  335. if have := fieldsOf(reflect.TypeFor[Node]()); !reflect.DeepEqual(have, nodeHandles) {
  336. t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
  337. have, nodeHandles)
  338. }
  339. n1 := key.NewNode().Public()
  340. m1 := key.NewMachine().Public()
  341. now := time.Now()
  342. tests := []struct {
  343. a, b *Node
  344. want bool
  345. }{
  346. {
  347. &Node{},
  348. nil,
  349. false,
  350. },
  351. {
  352. nil,
  353. &Node{},
  354. false,
  355. },
  356. {
  357. &Node{},
  358. &Node{},
  359. true,
  360. },
  361. {
  362. &Node{},
  363. &Node{},
  364. true,
  365. },
  366. {
  367. &Node{ID: 1},
  368. &Node{},
  369. false,
  370. },
  371. {
  372. &Node{ID: 1},
  373. &Node{ID: 1},
  374. true,
  375. },
  376. {
  377. &Node{StableID: "node-abcd"},
  378. &Node{},
  379. false,
  380. },
  381. {
  382. &Node{StableID: "node-abcd"},
  383. &Node{StableID: "node-abcd"},
  384. true,
  385. },
  386. {
  387. &Node{User: 0},
  388. &Node{User: 1},
  389. false,
  390. },
  391. {
  392. &Node{User: 1},
  393. &Node{User: 1},
  394. true,
  395. },
  396. {
  397. &Node{Key: n1},
  398. &Node{Key: key.NewNode().Public()},
  399. false,
  400. },
  401. {
  402. &Node{Key: n1},
  403. &Node{Key: n1},
  404. true,
  405. },
  406. {
  407. &Node{KeyExpiry: now},
  408. &Node{KeyExpiry: now.Add(60 * time.Second)},
  409. false,
  410. },
  411. {
  412. &Node{KeyExpiry: now},
  413. &Node{KeyExpiry: now},
  414. true,
  415. },
  416. {
  417. &Node{Machine: m1},
  418. &Node{Machine: key.NewMachine().Public()},
  419. false,
  420. },
  421. {
  422. &Node{Machine: m1},
  423. &Node{Machine: m1},
  424. true,
  425. },
  426. {
  427. &Node{Addresses: []netip.Prefix{}},
  428. &Node{Addresses: nil},
  429. false,
  430. },
  431. {
  432. &Node{Addresses: []netip.Prefix{}},
  433. &Node{Addresses: []netip.Prefix{}},
  434. true,
  435. },
  436. {
  437. &Node{AllowedIPs: []netip.Prefix{}},
  438. &Node{AllowedIPs: nil},
  439. false,
  440. },
  441. {
  442. &Node{Addresses: []netip.Prefix{}},
  443. &Node{Addresses: []netip.Prefix{}},
  444. true,
  445. },
  446. {
  447. &Node{Endpoints: []netip.AddrPort{}},
  448. &Node{Endpoints: nil},
  449. false,
  450. },
  451. {
  452. &Node{Endpoints: []netip.AddrPort{}},
  453. &Node{Endpoints: []netip.AddrPort{}},
  454. true,
  455. },
  456. {
  457. &Node{Hostinfo: (&Hostinfo{Hostname: "alice"}).View()},
  458. &Node{Hostinfo: (&Hostinfo{Hostname: "bob"}).View()},
  459. false,
  460. },
  461. {
  462. &Node{Hostinfo: (&Hostinfo{}).View()},
  463. &Node{Hostinfo: (&Hostinfo{}).View()},
  464. true,
  465. },
  466. {
  467. &Node{Created: now},
  468. &Node{Created: now.Add(60 * time.Second)},
  469. false,
  470. },
  471. {
  472. &Node{Created: now},
  473. &Node{Created: now},
  474. true,
  475. },
  476. {
  477. &Node{LastSeen: &now},
  478. &Node{LastSeen: nil},
  479. false,
  480. },
  481. {
  482. &Node{LastSeen: &now},
  483. &Node{LastSeen: &now},
  484. true,
  485. },
  486. {
  487. &Node{LegacyDERPString: "foo"},
  488. &Node{LegacyDERPString: "bar"},
  489. false,
  490. },
  491. {
  492. &Node{HomeDERP: 1},
  493. &Node{HomeDERP: 2},
  494. false,
  495. },
  496. {
  497. &Node{Tags: []string{"tag:foo"}},
  498. &Node{Tags: []string{"tag:foo"}},
  499. true,
  500. },
  501. {
  502. &Node{Tags: []string{"tag:foo", "tag:bar"}},
  503. &Node{Tags: []string{"tag:bar"}},
  504. false,
  505. },
  506. {
  507. &Node{Tags: []string{"tag:foo"}},
  508. &Node{Tags: []string{"tag:bar"}},
  509. false,
  510. },
  511. {
  512. &Node{Tags: []string{"tag:foo"}},
  513. &Node{},
  514. false,
  515. },
  516. {
  517. &Node{Expired: true},
  518. &Node{},
  519. false,
  520. },
  521. {
  522. &Node{},
  523. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  524. false,
  525. },
  526. {
  527. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  528. &Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
  529. true,
  530. },
  531. {
  532. &Node{},
  533. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  534. false,
  535. },
  536. {
  537. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  538. &Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
  539. true,
  540. },
  541. {
  542. &Node{
  543. CapMap: NodeCapMap{
  544. "foo": []RawMessage{`"foo"`},
  545. },
  546. },
  547. &Node{
  548. CapMap: NodeCapMap{
  549. "foo": []RawMessage{`"foo"`},
  550. },
  551. },
  552. true,
  553. },
  554. {
  555. &Node{
  556. CapMap: NodeCapMap{
  557. "bar": []RawMessage{`"foo"`},
  558. },
  559. },
  560. &Node{
  561. CapMap: NodeCapMap{
  562. "foo": []RawMessage{`"bar"`},
  563. },
  564. },
  565. false,
  566. },
  567. {
  568. &Node{
  569. CapMap: NodeCapMap{
  570. "foo": nil,
  571. },
  572. },
  573. &Node{
  574. CapMap: NodeCapMap{
  575. "foo": []RawMessage{`"bar"`},
  576. },
  577. },
  578. false,
  579. },
  580. {
  581. &Node{IsJailed: true},
  582. &Node{IsJailed: true},
  583. true,
  584. },
  585. {
  586. &Node{IsJailed: false},
  587. &Node{IsJailed: true},
  588. false,
  589. },
  590. }
  591. for i, tt := range tests {
  592. got := tt.a.Equal(tt.b)
  593. if got != tt.want {
  594. t.Errorf("%d. Equal = %v; want %v", i, got, tt.want)
  595. }
  596. }
  597. }
  598. func TestNetInfoFields(t *testing.T) {
  599. handled := []string{
  600. "MappingVariesByDestIP",
  601. "WorkingIPv6",
  602. "OSHasIPv6",
  603. "WorkingUDP",
  604. "WorkingICMPv4",
  605. "HavePortMap",
  606. "UPnP",
  607. "PMP",
  608. "PCP",
  609. "PreferredDERP",
  610. "LinkType",
  611. "DERPLatency",
  612. "FirewallMode",
  613. }
  614. if have := fieldsOf(reflect.TypeFor[NetInfo]()); !reflect.DeepEqual(have, handled) {
  615. t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n",
  616. have, handled)
  617. }
  618. }
  619. func TestCloneUser(t *testing.T) {
  620. tests := []struct {
  621. name string
  622. u *User
  623. }{
  624. {"nil_logins", &User{}},
  625. }
  626. for _, tt := range tests {
  627. t.Run(tt.name, func(t *testing.T) {
  628. u2 := tt.u.Clone()
  629. if !reflect.DeepEqual(tt.u, u2) {
  630. t.Errorf("not equal")
  631. }
  632. })
  633. }
  634. }
  635. func TestCloneNode(t *testing.T) {
  636. tests := []struct {
  637. name string
  638. v *Node
  639. }{
  640. {"nil_fields", &Node{}},
  641. {"zero_fields", &Node{
  642. Addresses: make([]netip.Prefix, 0),
  643. AllowedIPs: make([]netip.Prefix, 0),
  644. Endpoints: make([]netip.AddrPort, 0),
  645. }},
  646. }
  647. for _, tt := range tests {
  648. t.Run(tt.name, func(t *testing.T) {
  649. v2 := tt.v.Clone()
  650. if !reflect.DeepEqual(tt.v, v2) {
  651. t.Errorf("not equal")
  652. }
  653. })
  654. }
  655. }
  656. func TestEndpointTypeMarshal(t *testing.T) {
  657. eps := []EndpointType{
  658. EndpointUnknownType,
  659. EndpointLocal,
  660. EndpointSTUN,
  661. EndpointPortmapped,
  662. EndpointSTUN4LocalPort,
  663. }
  664. got, err := json.Marshal(eps)
  665. if err != nil {
  666. t.Fatal(err)
  667. }
  668. const want = `[0,1,2,3,4]`
  669. if string(got) != want {
  670. t.Errorf("got %s; want %s", got, want)
  671. }
  672. }
  673. func TestRegisterRequestNilClone(t *testing.T) {
  674. var nilReq *RegisterRequest
  675. got := nilReq.Clone()
  676. if got != nil {
  677. t.Errorf("got = %v; want nil", got)
  678. }
  679. }
  680. // Tests that CurrentCapabilityVersion is bumped when the comment block above it gets bumped.
  681. // We've screwed this up several times.
  682. func TestCurrentCapabilityVersion(t *testing.T) {
  683. f := must.Get(os.ReadFile("tailcfg.go"))
  684. matches := regexp.MustCompile(`(?m)^//[\s-]+(\d+): \d\d\d\d-\d\d-\d\d: `).FindAllStringSubmatch(string(f), -1)
  685. max := 0
  686. for _, m := range matches {
  687. n := must.Get(strconv.Atoi(m[1]))
  688. if n > max {
  689. max = n
  690. }
  691. }
  692. if CapabilityVersion(max) != CurrentCapabilityVersion {
  693. t.Errorf("CurrentCapabilityVersion = %d; want %d", CurrentCapabilityVersion, max)
  694. }
  695. }
  696. func TestUnmarshalHealth(t *testing.T) {
  697. tests := []struct {
  698. in string // MapResponse JSON
  699. want []string // MapResponse.Health wanted value post-unmarshal
  700. }{
  701. {in: `{}`},
  702. {in: `{"Health":null}`},
  703. {in: `{"Health":[]}`, want: []string{}},
  704. {in: `{"Health":["bad"]}`, want: []string{"bad"}},
  705. }
  706. for _, tt := range tests {
  707. var mr MapResponse
  708. if err := json.Unmarshal([]byte(tt.in), &mr); err != nil {
  709. t.Fatal(err)
  710. }
  711. if !reflect.DeepEqual(mr.Health, tt.want) {
  712. t.Errorf("for %#q: got %v; want %v", tt.in, mr.Health, tt.want)
  713. }
  714. }
  715. }
  716. func TestRawMessage(t *testing.T) {
  717. // Create a few types of json.RawMessages and then marshal them back and
  718. // forth to make sure they round-trip.
  719. type rule struct {
  720. Ports []int `json:",omitempty"`
  721. }
  722. tests := []struct {
  723. name string
  724. val map[string][]rule
  725. wire map[string][]RawMessage
  726. }{
  727. {
  728. name: "nil",
  729. val: nil,
  730. wire: nil,
  731. },
  732. {
  733. name: "empty",
  734. val: map[string][]rule{},
  735. wire: map[string][]RawMessage{},
  736. },
  737. {
  738. name: "one",
  739. val: map[string][]rule{
  740. "foo": {{Ports: []int{1, 2, 3}}},
  741. },
  742. wire: map[string][]RawMessage{
  743. "foo": {
  744. `{"Ports":[1,2,3]}`,
  745. },
  746. },
  747. },
  748. {
  749. name: "many",
  750. val: map[string][]rule{
  751. "foo": {{Ports: []int{1, 2, 3}}},
  752. "bar": {{Ports: []int{4, 5, 6}}, {Ports: []int{7, 8, 9}}},
  753. "baz": nil,
  754. "abc": {},
  755. "def": {{}},
  756. },
  757. wire: map[string][]RawMessage{
  758. "foo": {
  759. `{"Ports":[1,2,3]}`,
  760. },
  761. "bar": {
  762. `{"Ports":[4,5,6]}`,
  763. `{"Ports":[7,8,9]}`,
  764. },
  765. "baz": nil,
  766. "abc": {},
  767. "def": {"{}"},
  768. },
  769. },
  770. }
  771. for _, tc := range tests {
  772. t.Run(tc.name, func(t *testing.T) {
  773. j := must.Get(json.Marshal(tc.val))
  774. var gotWire map[string][]RawMessage
  775. if err := json.Unmarshal(j, &gotWire); err != nil {
  776. t.Fatalf("unmarshal: %v", err)
  777. }
  778. if !reflect.DeepEqual(gotWire, tc.wire) {
  779. t.Errorf("got %#v; want %#v", gotWire, tc.wire)
  780. }
  781. j = must.Get(json.Marshal(tc.wire))
  782. var gotVal map[string][]rule
  783. if err := json.Unmarshal(j, &gotVal); err != nil {
  784. t.Fatalf("unmarshal: %v", err)
  785. }
  786. if !reflect.DeepEqual(gotVal, tc.val) {
  787. t.Errorf("got %#v; want %#v", gotVal, tc.val)
  788. }
  789. })
  790. }
  791. }
  792. func TestMarshalToRawMessageAndBack(t *testing.T) {
  793. type inner struct {
  794. Groups []string `json:"groups,omitempty"`
  795. }
  796. testip := netip.MustParseAddrPort("1.2.3.4:80")
  797. type testRule struct {
  798. Ports []int `json:"ports,omitempty"`
  799. ToggleOn bool `json:"toggleOn,omitempty"`
  800. Name string `json:"name,omitempty"`
  801. Groups inner `json:"groups,omitempty"`
  802. Addrs []netip.AddrPort `json:"addrs"`
  803. }
  804. tests := []struct {
  805. name string
  806. capType PeerCapability
  807. val testRule
  808. }{
  809. {
  810. name: "empty",
  811. val: testRule{},
  812. capType: PeerCapability("foo"),
  813. },
  814. {
  815. name: "some values",
  816. val: testRule{Ports: []int{80, 443}, Name: "foo"},
  817. capType: PeerCapability("foo"),
  818. },
  819. {
  820. name: "all values",
  821. val: testRule{Ports: []int{80, 443}, Name: "foo", ToggleOn: true, Groups: inner{Groups: []string{"foo", "bar"}}, Addrs: []netip.AddrPort{testip}},
  822. capType: PeerCapability("foo"),
  823. },
  824. }
  825. for _, tc := range tests {
  826. t.Run(tc.name, func(t *testing.T) {
  827. raw, err := MarshalCapJSON(tc.val)
  828. if err != nil {
  829. t.Fatalf("unexpected error marshalling raw message: %v", err)
  830. }
  831. cap := PeerCapMap{tc.capType: []RawMessage{raw}}
  832. after, err := UnmarshalCapJSON[testRule](cap, tc.capType)
  833. if err != nil {
  834. t.Fatalf("unexpected error unmarshaling raw message: %v", err)
  835. }
  836. if !reflect.DeepEqual([]testRule{tc.val}, after) {
  837. t.Errorf("got %#v; want %#v", after, []testRule{tc.val})
  838. }
  839. })
  840. }
  841. }
  842. func TestDeps(t *testing.T) {
  843. deptest.DepChecker{
  844. BadDeps: map[string]string{
  845. // Make sure we don't again accidentally bring in a dependency on
  846. // drive or its transitive dependencies
  847. "testing": "do not use testing package in production code",
  848. "tailscale.com/drive/driveimpl": "https://github.com/tailscale/tailscale/pull/10631",
  849. "github.com/studio-b12/gowebdav": "https://github.com/tailscale/tailscale/pull/10631",
  850. },
  851. }.Check(t)
  852. }
  853. func TestCheckTag(t *testing.T) {
  854. tests := []struct {
  855. name string
  856. tag string
  857. want bool
  858. }{
  859. {"empty", "", false},
  860. {"good", "tag:foo", true},
  861. {"bad", "tag:", false},
  862. {"no_leading_num", "tag:1foo", false},
  863. {"no_punctuation", "tag:foa@bar", false},
  864. }
  865. for _, tt := range tests {
  866. t.Run(tt.name, func(t *testing.T) {
  867. err := CheckTag(tt.tag)
  868. if err == nil && !tt.want {
  869. t.Errorf("got nil; want error")
  870. } else if err != nil && tt.want {
  871. t.Errorf("got %v; want nil", err)
  872. }
  873. })
  874. }
  875. }
  876. func TestDisplayMessageEqual(t *testing.T) {
  877. type test struct {
  878. name string
  879. value1 DisplayMessage
  880. value2 DisplayMessage
  881. wantEqual bool
  882. }
  883. for _, test := range []test{
  884. {
  885. name: "same",
  886. value1: DisplayMessage{
  887. Title: "title",
  888. Text: "text",
  889. Severity: SeverityHigh,
  890. ImpactsConnectivity: false,
  891. PrimaryAction: &DisplayMessageAction{
  892. URL: "https://example.com",
  893. Label: "Open",
  894. },
  895. },
  896. value2: DisplayMessage{
  897. Title: "title",
  898. Text: "text",
  899. Severity: SeverityHigh,
  900. ImpactsConnectivity: false,
  901. PrimaryAction: &DisplayMessageAction{
  902. URL: "https://example.com",
  903. Label: "Open",
  904. },
  905. },
  906. wantEqual: true,
  907. },
  908. {
  909. name: "different-title",
  910. value1: DisplayMessage{
  911. Title: "title",
  912. },
  913. value2: DisplayMessage{
  914. Title: "different title",
  915. },
  916. wantEqual: false,
  917. },
  918. {
  919. name: "different-text",
  920. value1: DisplayMessage{
  921. Text: "some text",
  922. },
  923. value2: DisplayMessage{
  924. Text: "different text",
  925. },
  926. wantEqual: false,
  927. },
  928. {
  929. name: "different-severity",
  930. value1: DisplayMessage{
  931. Severity: SeverityHigh,
  932. },
  933. value2: DisplayMessage{
  934. Severity: SeverityMedium,
  935. },
  936. wantEqual: false,
  937. },
  938. {
  939. name: "different-impactsConnectivity",
  940. value1: DisplayMessage{
  941. ImpactsConnectivity: true,
  942. },
  943. value2: DisplayMessage{
  944. ImpactsConnectivity: false,
  945. },
  946. wantEqual: false,
  947. },
  948. {
  949. name: "different-primaryAction-nil-non-nil",
  950. value1: DisplayMessage{},
  951. value2: DisplayMessage{
  952. PrimaryAction: &DisplayMessageAction{
  953. URL: "https://example.com",
  954. Label: "Open",
  955. },
  956. },
  957. wantEqual: false,
  958. },
  959. {
  960. name: "different-primaryAction-url",
  961. value1: DisplayMessage{
  962. PrimaryAction: &DisplayMessageAction{
  963. URL: "https://example.com",
  964. Label: "Open",
  965. },
  966. },
  967. value2: DisplayMessage{
  968. PrimaryAction: &DisplayMessageAction{
  969. URL: "https://zombo.com",
  970. Label: "Open",
  971. },
  972. },
  973. wantEqual: false,
  974. },
  975. {
  976. name: "different-primaryAction-label",
  977. value1: DisplayMessage{
  978. PrimaryAction: &DisplayMessageAction{
  979. URL: "https://example.com",
  980. Label: "Open",
  981. },
  982. },
  983. value2: DisplayMessage{
  984. PrimaryAction: &DisplayMessageAction{
  985. URL: "https://example.com",
  986. Label: "Learn more",
  987. },
  988. },
  989. wantEqual: false,
  990. },
  991. } {
  992. t.Run(test.name, func(t *testing.T) {
  993. got := test.value1.Equal(test.value2)
  994. if got != test.wantEqual {
  995. value1 := must.Get(json.MarshalIndent(test.value1, "", " "))
  996. value2 := must.Get(json.MarshalIndent(test.value2, "", " "))
  997. t.Errorf("value1.Equal(value2): got %t, want %t\nvalue1:\n%s\nvalue2:\n%s", got, test.wantEqual, value1, value2)
  998. }
  999. })
  1000. }
  1001. }