syspolicy_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package syspolicy
  4. import (
  5. "errors"
  6. "slices"
  7. "testing"
  8. "time"
  9. "tailscale.com/types/logger"
  10. "tailscale.com/util/syspolicy/internal/loggerx"
  11. "tailscale.com/util/syspolicy/internal/metrics"
  12. "tailscale.com/util/syspolicy/pkey"
  13. "tailscale.com/util/syspolicy/ptype"
  14. "tailscale.com/util/syspolicy/rsop"
  15. "tailscale.com/util/syspolicy/setting"
  16. "tailscale.com/util/syspolicy/source"
  17. "tailscale.com/util/testenv"
  18. )
  19. var someOtherError = errors.New("error other than not found")
  20. // registerWellKnownSettingsForTest registers all implicit setting definitions
  21. // for the duration of the test.
  22. func registerWellKnownSettingsForTest(tb testenv.TB) {
  23. tb.Helper()
  24. err := setting.SetDefinitionsForTest(tb, implicitDefinitions...)
  25. if err != nil {
  26. tb.Fatalf("Failed to register well-known settings: %v", err)
  27. }
  28. }
  29. func TestGetString(t *testing.T) {
  30. tests := []struct {
  31. name string
  32. key pkey.Key
  33. handlerValue string
  34. handlerError error
  35. defaultValue string
  36. wantValue string
  37. wantError error
  38. wantMetrics []metrics.TestState
  39. }{
  40. {
  41. name: "read existing value",
  42. key: pkey.AdminConsoleVisibility,
  43. handlerValue: "hide",
  44. wantValue: "hide",
  45. wantMetrics: []metrics.TestState{
  46. {Name: "$os_syspolicy_any", Value: 1},
  47. {Name: "$os_syspolicy_AdminConsole", Value: 1},
  48. },
  49. },
  50. {
  51. name: "read non-existing value",
  52. key: pkey.EnableServerMode,
  53. handlerError: ErrNotConfigured,
  54. wantError: nil,
  55. },
  56. {
  57. name: "read non-existing value, non-blank default",
  58. key: pkey.EnableServerMode,
  59. handlerError: ErrNotConfigured,
  60. defaultValue: "test",
  61. wantValue: "test",
  62. wantError: nil,
  63. },
  64. {
  65. name: "reading value returns other error",
  66. key: pkey.NetworkDevicesVisibility,
  67. handlerError: someOtherError,
  68. wantError: someOtherError,
  69. wantMetrics: []metrics.TestState{
  70. {Name: "$os_syspolicy_errors", Value: 1},
  71. {Name: "$os_syspolicy_NetworkDevices_error", Value: 1},
  72. },
  73. },
  74. }
  75. registerWellKnownSettingsForTest(t)
  76. for _, tt := range tests {
  77. t.Run(tt.name, func(t *testing.T) {
  78. h := metrics.NewTestHandler(t)
  79. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  80. s := source.TestSetting[string]{
  81. Key: tt.key,
  82. Value: tt.handlerValue,
  83. Error: tt.handlerError,
  84. }
  85. registerSingleSettingStoreForTest(t, s)
  86. value, err := getString(tt.key, tt.defaultValue)
  87. if !errorsMatchForTest(err, tt.wantError) {
  88. t.Errorf("err=%q, want %q", err, tt.wantError)
  89. }
  90. if value != tt.wantValue {
  91. t.Errorf("value=%v, want %v", value, tt.wantValue)
  92. }
  93. wantMetrics := tt.wantMetrics
  94. if !metrics.ShouldReport() {
  95. // Check that metrics are not reported on platforms
  96. // where they shouldn't be reported.
  97. // As of 2024-09-04, syspolicy only reports metrics
  98. // on Windows and Android.
  99. wantMetrics = nil
  100. }
  101. h.MustEqual(wantMetrics...)
  102. })
  103. }
  104. }
  105. func TestGetUint64(t *testing.T) {
  106. tests := []struct {
  107. name string
  108. key pkey.Key
  109. handlerValue uint64
  110. handlerError error
  111. defaultValue uint64
  112. wantValue uint64
  113. wantError error
  114. }{
  115. {
  116. name: "read existing value",
  117. key: pkey.LogSCMInteractions,
  118. handlerValue: 1,
  119. wantValue: 1,
  120. },
  121. {
  122. name: "read non-existing value",
  123. key: pkey.LogSCMInteractions,
  124. handlerValue: 0,
  125. handlerError: ErrNotConfigured,
  126. wantValue: 0,
  127. },
  128. {
  129. name: "read non-existing value, non-zero default",
  130. key: pkey.LogSCMInteractions,
  131. defaultValue: 2,
  132. handlerError: ErrNotConfigured,
  133. wantValue: 2,
  134. },
  135. {
  136. name: "reading value returns other error",
  137. key: pkey.FlushDNSOnSessionUnlock,
  138. handlerError: someOtherError,
  139. wantError: someOtherError,
  140. },
  141. }
  142. for _, tt := range tests {
  143. t.Run(tt.name, func(t *testing.T) {
  144. // None of the policy settings tested here are integers.
  145. // In fact, we don't have any integer policies as of 2024-10-08.
  146. // However, we can register each of them as an integer policy setting
  147. // for the duration of the test, providing us with something to test against.
  148. if err := setting.SetDefinitionsForTest(t, setting.NewDefinition(tt.key, setting.DeviceSetting, setting.IntegerValue)); err != nil {
  149. t.Fatalf("SetDefinitionsForTest failed: %v", err)
  150. }
  151. s := source.TestSetting[uint64]{
  152. Key: tt.key,
  153. Value: tt.handlerValue,
  154. Error: tt.handlerError,
  155. }
  156. registerSingleSettingStoreForTest(t, s)
  157. value, err := getUint64(tt.key, tt.defaultValue)
  158. if !errorsMatchForTest(err, tt.wantError) {
  159. t.Errorf("err=%q, want %q", err, tt.wantError)
  160. }
  161. if value != tt.wantValue {
  162. t.Errorf("value=%v, want %v", value, tt.wantValue)
  163. }
  164. })
  165. }
  166. }
  167. func TestGetBoolean(t *testing.T) {
  168. tests := []struct {
  169. name string
  170. key pkey.Key
  171. handlerValue bool
  172. handlerError error
  173. defaultValue bool
  174. wantValue bool
  175. wantError error
  176. wantMetrics []metrics.TestState
  177. }{
  178. {
  179. name: "read existing value",
  180. key: pkey.FlushDNSOnSessionUnlock,
  181. handlerValue: true,
  182. wantValue: true,
  183. wantMetrics: []metrics.TestState{
  184. {Name: "$os_syspolicy_any", Value: 1},
  185. {Name: "$os_syspolicy_FlushDNSOnSessionUnlock", Value: 1},
  186. },
  187. },
  188. {
  189. name: "read non-existing value",
  190. key: pkey.LogSCMInteractions,
  191. handlerValue: false,
  192. handlerError: ErrNotConfigured,
  193. wantValue: false,
  194. },
  195. {
  196. name: "reading value returns other error",
  197. key: pkey.FlushDNSOnSessionUnlock,
  198. handlerError: someOtherError,
  199. wantError: someOtherError, // expect error...
  200. defaultValue: true,
  201. wantValue: true, // ...AND default value if the handler fails.
  202. wantMetrics: []metrics.TestState{
  203. {Name: "$os_syspolicy_errors", Value: 1},
  204. {Name: "$os_syspolicy_FlushDNSOnSessionUnlock_error", Value: 1},
  205. },
  206. },
  207. }
  208. registerWellKnownSettingsForTest(t)
  209. for _, tt := range tests {
  210. t.Run(tt.name, func(t *testing.T) {
  211. h := metrics.NewTestHandler(t)
  212. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  213. s := source.TestSetting[bool]{
  214. Key: tt.key,
  215. Value: tt.handlerValue,
  216. Error: tt.handlerError,
  217. }
  218. registerSingleSettingStoreForTest(t, s)
  219. value, err := getBoolean(tt.key, tt.defaultValue)
  220. if !errorsMatchForTest(err, tt.wantError) {
  221. t.Errorf("err=%q, want %q", err, tt.wantError)
  222. }
  223. if value != tt.wantValue {
  224. t.Errorf("value=%v, want %v", value, tt.wantValue)
  225. }
  226. wantMetrics := tt.wantMetrics
  227. if !metrics.ShouldReport() {
  228. // Check that metrics are not reported on platforms
  229. // where they shouldn't be reported.
  230. // As of 2024-09-04, syspolicy only reports metrics
  231. // on Windows and Android.
  232. wantMetrics = nil
  233. }
  234. h.MustEqual(wantMetrics...)
  235. })
  236. }
  237. }
  238. func TestGetPreferenceOption(t *testing.T) {
  239. tests := []struct {
  240. name string
  241. key pkey.Key
  242. handlerValue string
  243. handlerError error
  244. wantValue ptype.PreferenceOption
  245. wantError error
  246. wantMetrics []metrics.TestState
  247. }{
  248. {
  249. name: "always by policy",
  250. key: pkey.EnableIncomingConnections,
  251. handlerValue: "always",
  252. wantValue: ptype.AlwaysByPolicy,
  253. wantMetrics: []metrics.TestState{
  254. {Name: "$os_syspolicy_any", Value: 1},
  255. {Name: "$os_syspolicy_AllowIncomingConnections", Value: 1},
  256. },
  257. },
  258. {
  259. name: "never by policy",
  260. key: pkey.EnableIncomingConnections,
  261. handlerValue: "never",
  262. wantValue: ptype.NeverByPolicy,
  263. wantMetrics: []metrics.TestState{
  264. {Name: "$os_syspolicy_any", Value: 1},
  265. {Name: "$os_syspolicy_AllowIncomingConnections", Value: 1},
  266. },
  267. },
  268. {
  269. name: "use default",
  270. key: pkey.EnableIncomingConnections,
  271. handlerValue: "",
  272. wantValue: ptype.ShowChoiceByPolicy,
  273. wantMetrics: []metrics.TestState{
  274. {Name: "$os_syspolicy_any", Value: 1},
  275. {Name: "$os_syspolicy_AllowIncomingConnections", Value: 1},
  276. },
  277. },
  278. {
  279. name: "read non-existing value",
  280. key: pkey.EnableIncomingConnections,
  281. handlerError: ErrNotConfigured,
  282. wantValue: ptype.ShowChoiceByPolicy,
  283. },
  284. {
  285. name: "other error is returned",
  286. key: pkey.EnableIncomingConnections,
  287. handlerError: someOtherError,
  288. wantValue: ptype.ShowChoiceByPolicy,
  289. wantError: someOtherError,
  290. wantMetrics: []metrics.TestState{
  291. {Name: "$os_syspolicy_errors", Value: 1},
  292. {Name: "$os_syspolicy_AllowIncomingConnections_error", Value: 1},
  293. },
  294. },
  295. }
  296. registerWellKnownSettingsForTest(t)
  297. for _, tt := range tests {
  298. t.Run(tt.name, func(t *testing.T) {
  299. h := metrics.NewTestHandler(t)
  300. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  301. s := source.TestSetting[string]{
  302. Key: tt.key,
  303. Value: tt.handlerValue,
  304. Error: tt.handlerError,
  305. }
  306. registerSingleSettingStoreForTest(t, s)
  307. option, err := getPreferenceOption(tt.key, ptype.ShowChoiceByPolicy)
  308. if !errorsMatchForTest(err, tt.wantError) {
  309. t.Errorf("err=%q, want %q", err, tt.wantError)
  310. }
  311. if option != tt.wantValue {
  312. t.Errorf("option=%v, want %v", option, tt.wantValue)
  313. }
  314. wantMetrics := tt.wantMetrics
  315. if !metrics.ShouldReport() {
  316. // Check that metrics are not reported on platforms
  317. // where they shouldn't be reported.
  318. // As of 2024-09-04, syspolicy only reports metrics
  319. // on Windows and Android.
  320. wantMetrics = nil
  321. }
  322. h.MustEqual(wantMetrics...)
  323. })
  324. }
  325. }
  326. func TestGetVisibility(t *testing.T) {
  327. tests := []struct {
  328. name string
  329. key pkey.Key
  330. handlerValue string
  331. handlerError error
  332. wantValue ptype.Visibility
  333. wantError error
  334. wantMetrics []metrics.TestState
  335. }{
  336. {
  337. name: "hidden by policy",
  338. key: pkey.AdminConsoleVisibility,
  339. handlerValue: "hide",
  340. wantValue: ptype.HiddenByPolicy,
  341. wantMetrics: []metrics.TestState{
  342. {Name: "$os_syspolicy_any", Value: 1},
  343. {Name: "$os_syspolicy_AdminConsole", Value: 1},
  344. },
  345. },
  346. {
  347. name: "visibility default",
  348. key: pkey.AdminConsoleVisibility,
  349. handlerValue: "show",
  350. wantValue: ptype.VisibleByPolicy,
  351. wantMetrics: []metrics.TestState{
  352. {Name: "$os_syspolicy_any", Value: 1},
  353. {Name: "$os_syspolicy_AdminConsole", Value: 1},
  354. },
  355. },
  356. {
  357. name: "read non-existing value",
  358. key: pkey.AdminConsoleVisibility,
  359. handlerValue: "show",
  360. handlerError: ErrNotConfigured,
  361. wantValue: ptype.VisibleByPolicy,
  362. },
  363. {
  364. name: "other error is returned",
  365. key: pkey.AdminConsoleVisibility,
  366. handlerValue: "show",
  367. handlerError: someOtherError,
  368. wantValue: ptype.VisibleByPolicy,
  369. wantError: someOtherError,
  370. wantMetrics: []metrics.TestState{
  371. {Name: "$os_syspolicy_errors", Value: 1},
  372. {Name: "$os_syspolicy_AdminConsole_error", Value: 1},
  373. },
  374. },
  375. }
  376. registerWellKnownSettingsForTest(t)
  377. for _, tt := range tests {
  378. t.Run(tt.name, func(t *testing.T) {
  379. h := metrics.NewTestHandler(t)
  380. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  381. s := source.TestSetting[string]{
  382. Key: tt.key,
  383. Value: tt.handlerValue,
  384. Error: tt.handlerError,
  385. }
  386. registerSingleSettingStoreForTest(t, s)
  387. visibility, err := getVisibility(tt.key)
  388. if !errorsMatchForTest(err, tt.wantError) {
  389. t.Errorf("err=%q, want %q", err, tt.wantError)
  390. }
  391. if visibility != tt.wantValue {
  392. t.Errorf("visibility=%v, want %v", visibility, tt.wantValue)
  393. }
  394. wantMetrics := tt.wantMetrics
  395. if !metrics.ShouldReport() {
  396. // Check that metrics are not reported on platforms
  397. // where they shouldn't be reported.
  398. // As of 2024-09-04, syspolicy only reports metrics
  399. // on Windows and Android.
  400. wantMetrics = nil
  401. }
  402. h.MustEqual(wantMetrics...)
  403. })
  404. }
  405. }
  406. func TestGetDuration(t *testing.T) {
  407. tests := []struct {
  408. name string
  409. key pkey.Key
  410. handlerValue string
  411. handlerError error
  412. defaultValue time.Duration
  413. wantValue time.Duration
  414. wantError error
  415. wantMetrics []metrics.TestState
  416. }{
  417. {
  418. name: "read existing value",
  419. key: pkey.KeyExpirationNoticeTime,
  420. handlerValue: "2h",
  421. wantValue: 2 * time.Hour,
  422. defaultValue: 24 * time.Hour,
  423. wantMetrics: []metrics.TestState{
  424. {Name: "$os_syspolicy_any", Value: 1},
  425. {Name: "$os_syspolicy_KeyExpirationNotice", Value: 1},
  426. },
  427. },
  428. {
  429. name: "invalid duration value",
  430. key: pkey.KeyExpirationNoticeTime,
  431. handlerValue: "-20",
  432. wantValue: 24 * time.Hour,
  433. wantError: errors.New(`time: missing unit in duration "-20"`),
  434. defaultValue: 24 * time.Hour,
  435. wantMetrics: []metrics.TestState{
  436. {Name: "$os_syspolicy_errors", Value: 1},
  437. {Name: "$os_syspolicy_KeyExpirationNotice_error", Value: 1},
  438. },
  439. },
  440. {
  441. name: "read non-existing value",
  442. key: pkey.KeyExpirationNoticeTime,
  443. handlerError: ErrNotConfigured,
  444. wantValue: 24 * time.Hour,
  445. defaultValue: 24 * time.Hour,
  446. },
  447. {
  448. name: "read non-existing value different default",
  449. key: pkey.KeyExpirationNoticeTime,
  450. handlerError: ErrNotConfigured,
  451. wantValue: 0 * time.Second,
  452. defaultValue: 0 * time.Second,
  453. },
  454. {
  455. name: "other error is returned",
  456. key: pkey.KeyExpirationNoticeTime,
  457. handlerError: someOtherError,
  458. wantValue: 24 * time.Hour,
  459. wantError: someOtherError,
  460. defaultValue: 24 * time.Hour,
  461. wantMetrics: []metrics.TestState{
  462. {Name: "$os_syspolicy_errors", Value: 1},
  463. {Name: "$os_syspolicy_KeyExpirationNotice_error", Value: 1},
  464. },
  465. },
  466. }
  467. registerWellKnownSettingsForTest(t)
  468. for _, tt := range tests {
  469. t.Run(tt.name, func(t *testing.T) {
  470. h := metrics.NewTestHandler(t)
  471. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  472. s := source.TestSetting[string]{
  473. Key: tt.key,
  474. Value: tt.handlerValue,
  475. Error: tt.handlerError,
  476. }
  477. registerSingleSettingStoreForTest(t, s)
  478. duration, err := getDuration(tt.key, tt.defaultValue)
  479. if !errorsMatchForTest(err, tt.wantError) {
  480. t.Errorf("err=%q, want %q", err, tt.wantError)
  481. }
  482. if duration != tt.wantValue {
  483. t.Errorf("duration=%v, want %v", duration, tt.wantValue)
  484. }
  485. wantMetrics := tt.wantMetrics
  486. if !metrics.ShouldReport() {
  487. // Check that metrics are not reported on platforms
  488. // where they shouldn't be reported.
  489. // As of 2024-09-04, syspolicy only reports metrics
  490. // on Windows and Android.
  491. wantMetrics = nil
  492. }
  493. h.MustEqual(wantMetrics...)
  494. })
  495. }
  496. }
  497. func TestGetStringArray(t *testing.T) {
  498. tests := []struct {
  499. name string
  500. key pkey.Key
  501. handlerValue []string
  502. handlerError error
  503. defaultValue []string
  504. wantValue []string
  505. wantError error
  506. wantMetrics []metrics.TestState
  507. }{
  508. {
  509. name: "read existing value",
  510. key: pkey.AllowedSuggestedExitNodes,
  511. handlerValue: []string{"foo", "bar"},
  512. wantValue: []string{"foo", "bar"},
  513. wantMetrics: []metrics.TestState{
  514. {Name: "$os_syspolicy_any", Value: 1},
  515. {Name: "$os_syspolicy_AllowedSuggestedExitNodes", Value: 1},
  516. },
  517. },
  518. {
  519. name: "read non-existing value",
  520. key: pkey.AllowedSuggestedExitNodes,
  521. handlerError: ErrNotConfigured,
  522. wantError: nil,
  523. },
  524. {
  525. name: "read non-existing value, non nil default",
  526. key: pkey.AllowedSuggestedExitNodes,
  527. handlerError: ErrNotConfigured,
  528. defaultValue: []string{"foo", "bar"},
  529. wantValue: []string{"foo", "bar"},
  530. wantError: nil,
  531. },
  532. {
  533. name: "reading value returns other error",
  534. key: pkey.AllowedSuggestedExitNodes,
  535. handlerError: someOtherError,
  536. wantError: someOtherError,
  537. wantMetrics: []metrics.TestState{
  538. {Name: "$os_syspolicy_errors", Value: 1},
  539. {Name: "$os_syspolicy_AllowedSuggestedExitNodes_error", Value: 1},
  540. },
  541. },
  542. }
  543. registerWellKnownSettingsForTest(t)
  544. for _, tt := range tests {
  545. t.Run(tt.name, func(t *testing.T) {
  546. h := metrics.NewTestHandler(t)
  547. metrics.SetHooksForTest(t, h.AddMetric, h.SetMetric)
  548. s := source.TestSetting[[]string]{
  549. Key: tt.key,
  550. Value: tt.handlerValue,
  551. Error: tt.handlerError,
  552. }
  553. registerSingleSettingStoreForTest(t, s)
  554. value, err := getStringArray(tt.key, tt.defaultValue)
  555. if !errorsMatchForTest(err, tt.wantError) {
  556. t.Errorf("err=%q, want %q", err, tt.wantError)
  557. }
  558. if !slices.Equal(tt.wantValue, value) {
  559. t.Errorf("value=%v, want %v", value, tt.wantValue)
  560. }
  561. wantMetrics := tt.wantMetrics
  562. if !metrics.ShouldReport() {
  563. // Check that metrics are not reported on platforms
  564. // where they shouldn't be reported.
  565. // As of 2024-09-04, syspolicy only reports metrics
  566. // on Windows and Android.
  567. wantMetrics = nil
  568. }
  569. h.MustEqual(wantMetrics...)
  570. })
  571. }
  572. }
  573. // mustRegisterStoreForTest is like [rsop.RegisterStoreForTest], but it fails the test if the store could not be registered.
  574. func mustRegisterStoreForTest(tb testenv.TB, name string, scope setting.PolicyScope, store source.Store) *rsop.StoreRegistration {
  575. tb.Helper()
  576. reg, err := rsop.RegisterStoreForTest(tb, name, scope, store)
  577. if err != nil {
  578. tb.Fatalf("Failed to register policy store %q as a %v policy source: %v", name, scope, err)
  579. }
  580. return reg
  581. }
  582. func registerSingleSettingStoreForTest[T source.TestValueType](tb testenv.TB, s source.TestSetting[T]) {
  583. policyStore := source.NewTestStoreOf(tb, s)
  584. mustRegisterStoreForTest(tb, "TestStore", setting.DeviceScope, policyStore)
  585. }
  586. func BenchmarkGetString(b *testing.B) {
  587. loggerx.SetForTest(b, logger.Discard, logger.Discard)
  588. registerWellKnownSettingsForTest(b)
  589. wantControlURL := "https://login.tailscale.com"
  590. registerSingleSettingStoreForTest(b, source.TestSettingOf(pkey.ControlURL, wantControlURL))
  591. b.ResetTimer()
  592. for i := 0; i < b.N; i++ {
  593. gotControlURL, _ := getString(pkey.ControlURL, "https://controlplane.tailscale.com")
  594. if gotControlURL != wantControlURL {
  595. b.Fatalf("got %v; want %v", gotControlURL, wantControlURL)
  596. }
  597. }
  598. }
  599. func TestSelectControlURL(t *testing.T) {
  600. tests := []struct {
  601. reg, disk, want string
  602. }{
  603. // Modern default case.
  604. {"", "", "https://controlplane.tailscale.com"},
  605. // For a user who installed prior to Dec 2020, with
  606. // stuff in their registry.
  607. {"https://login.tailscale.com", "", "https://login.tailscale.com"},
  608. // Ignore pre-Dec'20 LoginURL from installer if prefs
  609. // prefs overridden manually to an on-prem control
  610. // server.
  611. {"https://login.tailscale.com", "http://on-prem", "http://on-prem"},
  612. // Something unknown explicitly set in the registry always wins.
  613. {"http://explicit-reg", "", "http://explicit-reg"},
  614. {"http://explicit-reg", "http://on-prem", "http://explicit-reg"},
  615. {"http://explicit-reg", "https://login.tailscale.com", "http://explicit-reg"},
  616. {"http://explicit-reg", "https://controlplane.tailscale.com", "http://explicit-reg"},
  617. // If nothing in the registry, disk wins.
  618. {"", "http://on-prem", "http://on-prem"},
  619. }
  620. for _, tt := range tests {
  621. if got := SelectControlURL(tt.reg, tt.disk); got != tt.want {
  622. t.Errorf("(reg %q, disk %q) = %q; want %q", tt.reg, tt.disk, got, tt.want)
  623. }
  624. }
  625. }
  626. func errorsMatchForTest(got, want error) bool {
  627. if got == nil && want == nil {
  628. return true
  629. }
  630. if got == nil || want == nil {
  631. return false
  632. }
  633. return errors.Is(got, want) || got.Error() == want.Error()
  634. }