config_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. package config
  2. import (
  3. "testing"
  4. )
  5. func TestParseListenersCfg(t *testing.T) {
  6. tests := []struct {
  7. name string
  8. bosListeners []string
  9. bosAdvertisedListeners []string
  10. bosAdvertisedHostsSSL []string
  11. kerberosListeners []string
  12. want []Listener
  13. wantErr bool
  14. errContains string
  15. }{
  16. {
  17. name: "valid single listener with kerberos",
  18. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  19. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  20. bosAdvertisedHostsSSL: []string{},
  21. kerberosListeners: []string{"LOCAL://0.0.0.0:1088"},
  22. want: []Listener{
  23. {
  24. BOSListenAddress: "0.0.0.0:5190",
  25. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  26. KerberosListenAddress: "0.0.0.0:1088",
  27. },
  28. },
  29. wantErr: false,
  30. },
  31. {
  32. name: "valid single listener without kerberos",
  33. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  34. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  35. bosAdvertisedHostsSSL: []string{},
  36. kerberosListeners: []string{},
  37. want: []Listener{
  38. {
  39. BOSListenAddress: "0.0.0.0:5190",
  40. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  41. KerberosListenAddress: "",
  42. },
  43. },
  44. wantErr: false,
  45. },
  46. {
  47. name: "valid multiple listeners with mixed kerberos",
  48. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191"},
  49. bosAdvertisedListeners: []string{"LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  50. bosAdvertisedHostsSSL: []string{},
  51. kerberosListeners: []string{"LAN://192.168.1.10:1088"},
  52. want: []Listener{
  53. {
  54. BOSListenAddress: "192.168.1.10:5190",
  55. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  56. KerberosListenAddress: "192.168.1.10:1088",
  57. },
  58. {
  59. BOSListenAddress: "0.0.0.0:5191",
  60. BOSAdvertisedHostPlain: "example.com:5191",
  61. KerberosListenAddress: "",
  62. },
  63. },
  64. wantErr: false,
  65. },
  66. {
  67. name: "missing BOS advertised host",
  68. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  69. bosAdvertisedListeners: []string{},
  70. bosAdvertisedHostsSSL: []string{},
  71. kerberosListeners: []string{},
  72. want: nil,
  73. wantErr: true,
  74. errContains: "missing BOS advertise address",
  75. },
  76. {
  77. name: "missing BOS listen address",
  78. bosListeners: []string{},
  79. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  80. bosAdvertisedHostsSSL: []string{},
  81. kerberosListeners: []string{},
  82. want: nil,
  83. wantErr: true,
  84. errContains: "missing BOS listen address for listener `local://`",
  85. },
  86. {
  87. name: "duplicate listener definition in BOS",
  88. bosListeners: []string{"LOCAL://0.0.0.0:5190", "LOCAL://0.0.0.0:5191"},
  89. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  90. bosAdvertisedHostsSSL: []string{},
  91. kerberosListeners: []string{},
  92. want: nil,
  93. wantErr: true,
  94. errContains: "duplicate listener definition",
  95. },
  96. {
  97. name: "duplicate listener definition in advertised",
  98. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  99. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190", "LOCAL://127.0.0.1:5191"},
  100. bosAdvertisedHostsSSL: []string{},
  101. kerberosListeners: []string{},
  102. want: nil,
  103. wantErr: true,
  104. errContains: "duplicate listener definition",
  105. },
  106. {
  107. name: "duplicate listener definition in kerberos",
  108. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  109. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  110. bosAdvertisedHostsSSL: []string{},
  111. kerberosListeners: []string{"LOCAL://0.0.0.0:1088", "LOCAL://0.0.0.0:1089"},
  112. want: nil,
  113. wantErr: true,
  114. errContains: "duplicate listener definition",
  115. },
  116. {
  117. name: "invalid URI format in BOS",
  118. bosListeners: []string{"invalid-uri"},
  119. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  120. bosAdvertisedHostsSSL: []string{},
  121. kerberosListeners: []string{},
  122. want: nil,
  123. wantErr: true,
  124. errContains: "missing scheme. Valid format",
  125. },
  126. {
  127. name: "invalid URI format in advertised",
  128. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  129. bosAdvertisedListeners: []string{"invalid-uri"},
  130. bosAdvertisedHostsSSL: []string{},
  131. kerberosListeners: []string{},
  132. want: nil,
  133. wantErr: true,
  134. errContains: "missing scheme. Valid format",
  135. },
  136. {
  137. name: "invalid URI format in kerberos",
  138. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  139. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  140. bosAdvertisedHostsSSL: []string{},
  141. kerberosListeners: []string{"invalid-uri"},
  142. want: nil,
  143. wantErr: true,
  144. errContains: "missing scheme. Valid format",
  145. },
  146. {
  147. name: "URI with underscore in scheme",
  148. bosListeners: []string{"LOCAL_://0.0.0.0:5190"},
  149. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  150. bosAdvertisedHostsSSL: []string{},
  151. kerberosListeners: []string{},
  152. want: nil,
  153. wantErr: true,
  154. errContains: "Valid format: SCHEME://HOST:PORT",
  155. },
  156. {
  157. name: "BOS listener missing port",
  158. bosListeners: []string{"LOCAL://0.0.0.0"},
  159. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  160. bosAdvertisedHostsSSL: []string{},
  161. kerberosListeners: []string{},
  162. want: nil,
  163. wantErr: true,
  164. errContains: "missing port",
  165. },
  166. {
  167. name: "BOS listener missing host",
  168. bosListeners: []string{"LOCAL://:5190"},
  169. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  170. bosAdvertisedHostsSSL: []string{},
  171. kerberosListeners: []string{},
  172. want: nil,
  173. wantErr: true,
  174. errContains: "missing host",
  175. },
  176. {
  177. name: "complex multi-listener setup",
  178. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191", "DOCKER://172.17.0.1:5192"},
  179. bosAdvertisedListeners: []string{"DOCKER://172.17.0.1:5192", "LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  180. bosAdvertisedHostsSSL: []string{},
  181. kerberosListeners: []string{"WAN://0.0.0.0:1089", "LAN://192.168.1.10:1088"},
  182. want: []Listener{
  183. {
  184. BOSListenAddress: "192.168.1.10:5190",
  185. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  186. BOSAdvertisedHostSSL: "",
  187. KerberosListenAddress: "192.168.1.10:1088",
  188. HasSSL: false,
  189. },
  190. {
  191. BOSListenAddress: "0.0.0.0:5191",
  192. BOSAdvertisedHostPlain: "example.com:5191",
  193. BOSAdvertisedHostSSL: "",
  194. KerberosListenAddress: "0.0.0.0:1089",
  195. HasSSL: false,
  196. },
  197. {
  198. BOSListenAddress: "172.17.0.1:5192",
  199. BOSAdvertisedHostPlain: "172.17.0.1:5192",
  200. BOSAdvertisedHostSSL: "",
  201. KerberosListenAddress: "",
  202. HasSSL: false,
  203. },
  204. },
  205. wantErr: false,
  206. },
  207. {
  208. name: "empty strings for all inputs",
  209. bosListeners: []string{},
  210. bosAdvertisedListeners: []string{},
  211. bosAdvertisedHostsSSL: []string{},
  212. kerberosListeners: []string{},
  213. want: nil,
  214. wantErr: true,
  215. errContains: "at least one BOS listener is required",
  216. },
  217. {
  218. name: "whitespace-only strings",
  219. bosListeners: []string{" "},
  220. bosAdvertisedListeners: []string{" "},
  221. bosAdvertisedHostsSSL: []string{},
  222. kerberosListeners: []string{" "},
  223. want: nil,
  224. wantErr: true,
  225. errContains: "at least one BOS listener is required",
  226. },
  227. {
  228. name: "only kerberos listeners provided",
  229. bosListeners: []string{},
  230. bosAdvertisedListeners: []string{},
  231. bosAdvertisedHostsSSL: []string{},
  232. kerberosListeners: []string{"LOCAL://0.0.0.0:1088"},
  233. want: nil,
  234. wantErr: true,
  235. errContains: "missing BOS advertise address for listener `local://`",
  236. },
  237. {
  238. name: "valid single listener with SSL",
  239. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  240. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  241. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  242. kerberosListeners: []string{},
  243. want: []Listener{
  244. {
  245. BOSListenAddress: "0.0.0.0:5190",
  246. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  247. BOSAdvertisedHostSSL: "127.0.0.1:5193",
  248. KerberosListenAddress: "",
  249. HasSSL: true,
  250. },
  251. },
  252. wantErr: false,
  253. },
  254. {
  255. name: "valid multiple listeners with mixed SSL",
  256. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191"},
  257. bosAdvertisedListeners: []string{"LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  258. bosAdvertisedHostsSSL: []string{"LAN://192.168.1.10:5193"},
  259. kerberosListeners: []string{},
  260. want: []Listener{
  261. {
  262. BOSListenAddress: "192.168.1.10:5190",
  263. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  264. BOSAdvertisedHostSSL: "192.168.1.10:5193",
  265. KerberosListenAddress: "",
  266. HasSSL: true,
  267. },
  268. {
  269. BOSListenAddress: "0.0.0.0:5191",
  270. BOSAdvertisedHostPlain: "example.com:5191",
  271. BOSAdvertisedHostSSL: "",
  272. KerberosListenAddress: "",
  273. HasSSL: false,
  274. },
  275. },
  276. wantErr: false,
  277. },
  278. {
  279. name: "SSL host without corresponding BOS listener",
  280. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  281. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  282. bosAdvertisedHostsSSL: []string{"WAN://ssl.example.com:5193"},
  283. kerberosListeners: []string{},
  284. want: nil,
  285. wantErr: true,
  286. errContains: "missing BOS advertise address for listener `wan://`",
  287. },
  288. {
  289. name: "duplicate SSL listener definition",
  290. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  291. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  292. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193", "LOCAL://127.0.0.1:5194"},
  293. kerberosListeners: []string{},
  294. want: nil,
  295. wantErr: true,
  296. errContains: "duplicate listener definition",
  297. },
  298. {
  299. name: "invalid URI format in SSL",
  300. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  301. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  302. bosAdvertisedHostsSSL: []string{"invalid-uri"},
  303. kerberosListeners: []string{},
  304. want: nil,
  305. wantErr: true,
  306. errContains: "missing scheme. Valid format",
  307. },
  308. {
  309. name: "SSL listener missing port",
  310. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  311. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  312. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1"},
  313. kerberosListeners: []string{},
  314. want: nil,
  315. wantErr: true,
  316. errContains: "missing port",
  317. },
  318. {
  319. name: "SSL listener missing host",
  320. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  321. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  322. bosAdvertisedHostsSSL: []string{"LOCAL://:5193"},
  323. kerberosListeners: []string{},
  324. want: nil,
  325. wantErr: true,
  326. errContains: "missing host",
  327. },
  328. {
  329. name: "complex multi-listener setup with SSL",
  330. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191", "DOCKER://172.17.0.1:5192"},
  331. bosAdvertisedListeners: []string{"DOCKER://172.17.0.1:5192", "LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  332. bosAdvertisedHostsSSL: []string{"LAN://192.168.1.10:5193", "WAN://ssl.example.com:5194"},
  333. kerberosListeners: []string{"WAN://0.0.0.0:1089", "LAN://192.168.1.10:1088"},
  334. want: []Listener{
  335. {
  336. BOSListenAddress: "192.168.1.10:5190",
  337. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  338. BOSAdvertisedHostSSL: "192.168.1.10:5193",
  339. KerberosListenAddress: "192.168.1.10:1088",
  340. HasSSL: true,
  341. },
  342. {
  343. BOSListenAddress: "0.0.0.0:5191",
  344. BOSAdvertisedHostPlain: "example.com:5191",
  345. BOSAdvertisedHostSSL: "ssl.example.com:5194",
  346. KerberosListenAddress: "0.0.0.0:1089",
  347. HasSSL: true,
  348. },
  349. {
  350. BOSListenAddress: "172.17.0.1:5192",
  351. BOSAdvertisedHostPlain: "172.17.0.1:5192",
  352. BOSAdvertisedHostSSL: "",
  353. KerberosListenAddress: "",
  354. HasSSL: false,
  355. },
  356. },
  357. wantErr: false,
  358. },
  359. }
  360. for _, tt := range tests {
  361. t.Run(tt.name, func(t *testing.T) {
  362. config := &Config{
  363. BOSListeners: tt.bosListeners,
  364. BOSAdvertisedHostsPlain: tt.bosAdvertisedListeners,
  365. BOSAdvertisedHostsSSL: tt.bosAdvertisedHostsSSL,
  366. KerberosListeners: tt.kerberosListeners,
  367. }
  368. got, err := config.ParseListenersCfg()
  369. if tt.wantErr {
  370. if err == nil {
  371. t.Errorf("ParseListenersCfg() expected error but got none")
  372. return
  373. }
  374. if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
  375. t.Errorf("ParseListenersCfg() error = %v, want error containing %q", err, tt.errContains)
  376. }
  377. return
  378. }
  379. if err != nil {
  380. t.Errorf("ParseListenersCfg() unexpected error = %v", err)
  381. return
  382. }
  383. if len(got) != len(tt.want) {
  384. t.Errorf("ParseListenersCfg() returned %d listeners, want %d", len(got), len(tt.want))
  385. return
  386. }
  387. // Create maps for easier comparison
  388. gotMap := make(map[string]Listener)
  389. wantMap := make(map[string]Listener)
  390. for _, l := range got {
  391. key := l.BOSListenAddress + "|" + l.BOSAdvertisedHostPlain
  392. gotMap[key] = l
  393. }
  394. for _, l := range tt.want {
  395. key := l.BOSListenAddress + "|" + l.BOSAdvertisedHostPlain
  396. wantMap[key] = l
  397. }
  398. for key, wantListener := range wantMap {
  399. gotListener, exists := gotMap[key]
  400. if !exists {
  401. t.Errorf("ParseListenersCfg() missing listener with key %s", key)
  402. continue
  403. }
  404. if gotListener.BOSListenAddress != wantListener.BOSListenAddress {
  405. t.Errorf("ParseListenersCfg() BOSListenAddress = %v, want %v", gotListener.BOSListenAddress, wantListener.BOSListenAddress)
  406. }
  407. if gotListener.BOSAdvertisedHostPlain != wantListener.BOSAdvertisedHostPlain {
  408. t.Errorf("ParseListenersCfg() BOSAdvertisedHostPlain = %v, want %v", gotListener.BOSAdvertisedHostPlain, wantListener.BOSAdvertisedHostPlain)
  409. }
  410. if gotListener.BOSAdvertisedHostSSL != wantListener.BOSAdvertisedHostSSL {
  411. t.Errorf("ParseListenersCfg() BOSAdvertisedHostSSL = %v, want %v", gotListener.BOSAdvertisedHostSSL, wantListener.BOSAdvertisedHostSSL)
  412. }
  413. if gotListener.HasSSL != wantListener.HasSSL {
  414. t.Errorf("ParseListenersCfg() HasSSL = %v, want %v", gotListener.HasSSL, wantListener.HasSSL)
  415. }
  416. if gotListener.KerberosListenAddress != wantListener.KerberosListenAddress {
  417. t.Errorf("ParseListenersCfg() KerberosListenAddress = %v, want %v", gotListener.KerberosListenAddress, wantListener.KerberosListenAddress)
  418. }
  419. }
  420. })
  421. }
  422. }
  423. func TestConfigValidate(t *testing.T) {
  424. tests := []struct {
  425. name string
  426. config Config
  427. wantErr bool
  428. errContains string
  429. }{
  430. {
  431. name: "valid config with all fields",
  432. config: Config{
  433. TOCListeners: []string{"0.0.0.0:9898", "192.168.1.10:9899"},
  434. APIListener: "127.0.0.1:8080",
  435. },
  436. wantErr: false,
  437. },
  438. {
  439. name: "valid config with single TOC listener",
  440. config: Config{
  441. TOCListeners: []string{"0.0.0.0:9898"},
  442. APIListener: "127.0.0.1:8080",
  443. },
  444. wantErr: false,
  445. },
  446. {
  447. name: "valid config with empty TOC listeners",
  448. config: Config{
  449. TOCListeners: []string{},
  450. APIListener: "127.0.0.1:8080",
  451. },
  452. wantErr: false,
  453. },
  454. {
  455. name: "valid config with empty API listener",
  456. config: Config{
  457. TOCListeners: []string{"0.0.0.0:9898"},
  458. APIListener: "",
  459. },
  460. wantErr: true,
  461. errContains: "APIListener is required and cannot be empty",
  462. },
  463. {
  464. name: "valid config with all empty",
  465. config: Config{
  466. TOCListeners: []string{},
  467. APIListener: "",
  468. },
  469. wantErr: true,
  470. errContains: "APIListener is required and cannot be empty",
  471. },
  472. {
  473. name: "invalid TOC listener - missing port",
  474. config: Config{
  475. TOCListeners: []string{"0.0.0.0"},
  476. APIListener: "127.0.0.1:8080",
  477. },
  478. wantErr: true,
  479. errContains: "invalid TOC listener \"0.0.0.0\": address 0.0.0.0: missing port in address",
  480. },
  481. {
  482. name: "invalid TOC listener - missing host",
  483. config: Config{
  484. TOCListeners: []string{":9898"},
  485. APIListener: "127.0.0.1:8080",
  486. },
  487. wantErr: true,
  488. errContains: "invalid TOC listener \":9898\": missing host",
  489. },
  490. {
  491. name: "invalid TOC listener - malformed",
  492. config: Config{
  493. TOCListeners: []string{"invalid-format"},
  494. APIListener: "127.0.0.1:8080",
  495. },
  496. wantErr: true,
  497. errContains: "invalid TOC listener \"invalid-format\": address invalid-format: missing port in address",
  498. },
  499. {
  500. name: "invalid TOC listener in comma-separated list",
  501. config: Config{
  502. TOCListeners: []string{"0.0.0.0:9898", "invalid-format", "192.168.1.10:9899"},
  503. APIListener: "127.0.0.1:8080",
  504. },
  505. wantErr: true,
  506. errContains: "invalid TOC listener \"invalid-format\": address invalid-format: missing port in address",
  507. },
  508. {
  509. name: "invalid API listener - missing port",
  510. config: Config{
  511. TOCListeners: []string{"0.0.0.0:9898"},
  512. APIListener: "127.0.0.1",
  513. },
  514. wantErr: true,
  515. errContains: "invalid API listener \"127.0.0.1\": address 127.0.0.1: missing port in address",
  516. },
  517. {
  518. name: "invalid API listener - missing host",
  519. config: Config{
  520. TOCListeners: []string{"0.0.0.0:9898"},
  521. APIListener: ":8080",
  522. },
  523. wantErr: true,
  524. errContains: "invalid API listener \":8080\": missing host",
  525. },
  526. {
  527. name: "invalid API listener - malformed",
  528. config: Config{
  529. TOCListeners: []string{"0.0.0.0:9898"},
  530. APIListener: "invalid-format",
  531. },
  532. wantErr: true,
  533. errContains: "invalid API listener \"invalid-format\": address invalid-format: missing port in address",
  534. },
  535. {
  536. name: "whitespace-only TOC listeners",
  537. config: Config{
  538. TOCListeners: []string{" ", " ", " "},
  539. APIListener: "127.0.0.1:8080",
  540. },
  541. wantErr: false,
  542. },
  543. {
  544. name: "whitespace-only API listener",
  545. config: Config{
  546. TOCListeners: []string{"0.0.0.0:9898"},
  547. APIListener: " ",
  548. },
  549. wantErr: true,
  550. errContains: "APIListener is required and cannot be empty",
  551. },
  552. }
  553. for _, tt := range tests {
  554. t.Run(tt.name, func(t *testing.T) {
  555. err := tt.config.Validate()
  556. if tt.wantErr {
  557. if err == nil {
  558. t.Errorf("Config.Validate() expected error but got none")
  559. return
  560. }
  561. if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
  562. t.Errorf("Config.Validate() error = %v, want error containing %q", err, tt.errContains)
  563. }
  564. return
  565. }
  566. if err != nil {
  567. t.Errorf("Config.Validate() unexpected error = %v", err)
  568. }
  569. })
  570. }
  571. }
  572. // Helper function to check if a string contains a substring
  573. func contains(s, substr string) bool {
  574. return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
  575. (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
  576. func() bool {
  577. for i := 1; i <= len(s)-len(substr); i++ {
  578. if s[i:i+len(substr)] == substr {
  579. return true
  580. }
  581. }
  582. return false
  583. }())))
  584. }