load_test.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // Copyright 2023 The frp Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package config
  15. import (
  16. "fmt"
  17. "strings"
  18. "testing"
  19. "github.com/stretchr/testify/require"
  20. v1 "github.com/fatedier/frp/pkg/config/v1"
  21. )
  22. const tomlServerContent = `
  23. bindAddr = "127.0.0.1"
  24. kcpBindPort = 7000
  25. quicBindPort = 7001
  26. tcpmuxHTTPConnectPort = 7005
  27. custom404Page = "/abc.html"
  28. transport.tcpKeepalive = 10
  29. `
  30. const yamlServerContent = `
  31. bindAddr: 127.0.0.1
  32. kcpBindPort: 7000
  33. quicBindPort: 7001
  34. tcpmuxHTTPConnectPort: 7005
  35. custom404Page: /abc.html
  36. transport:
  37. tcpKeepalive: 10
  38. `
  39. const jsonServerContent = `
  40. {
  41. "bindAddr": "127.0.0.1",
  42. "kcpBindPort": 7000,
  43. "quicBindPort": 7001,
  44. "tcpmuxHTTPConnectPort": 7005,
  45. "custom404Page": "/abc.html",
  46. "transport": {
  47. "tcpKeepalive": 10
  48. }
  49. }
  50. `
  51. func TestLoadServerConfig(t *testing.T) {
  52. tests := []struct {
  53. name string
  54. content string
  55. }{
  56. {"toml", tomlServerContent},
  57. {"yaml", yamlServerContent},
  58. {"json", jsonServerContent},
  59. }
  60. for _, test := range tests {
  61. t.Run(test.name, func(t *testing.T) {
  62. require := require.New(t)
  63. svrCfg := v1.ServerConfig{}
  64. err := LoadConfigure([]byte(test.content), &svrCfg, true)
  65. require.NoError(err)
  66. require.EqualValues("127.0.0.1", svrCfg.BindAddr)
  67. require.EqualValues(7000, svrCfg.KCPBindPort)
  68. require.EqualValues(7001, svrCfg.QUICBindPort)
  69. require.EqualValues(7005, svrCfg.TCPMuxHTTPConnectPort)
  70. require.EqualValues("/abc.html", svrCfg.Custom404Page)
  71. require.EqualValues(10, svrCfg.Transport.TCPKeepAlive)
  72. })
  73. }
  74. }
  75. // Test that loading in strict mode fails when the config is invalid.
  76. func TestLoadServerConfigStrictMode(t *testing.T) {
  77. tests := []struct {
  78. name string
  79. content string
  80. }{
  81. {"toml", tomlServerContent},
  82. {"yaml", yamlServerContent},
  83. {"json", jsonServerContent},
  84. }
  85. for _, strict := range []bool{false, true} {
  86. for _, test := range tests {
  87. t.Run(fmt.Sprintf("%s-strict-%t", test.name, strict), func(t *testing.T) {
  88. require := require.New(t)
  89. // Break the content with an innocent typo
  90. brokenContent := strings.Replace(test.content, "bindAddr", "bindAdur", 1)
  91. svrCfg := v1.ServerConfig{}
  92. err := LoadConfigure([]byte(brokenContent), &svrCfg, strict)
  93. if strict {
  94. require.ErrorContains(err, "bindAdur")
  95. } else {
  96. require.NoError(err)
  97. // BindAddr didn't get parsed because of the typo.
  98. require.EqualValues("", svrCfg.BindAddr)
  99. }
  100. })
  101. }
  102. }
  103. }
  104. func TestRenderWithTemplate(t *testing.T) {
  105. tests := []struct {
  106. name string
  107. content string
  108. want string
  109. }{
  110. {"toml", tomlServerContent, tomlServerContent},
  111. {"yaml", yamlServerContent, yamlServerContent},
  112. {"json", jsonServerContent, jsonServerContent},
  113. {"template numeric", `key = {{ 123 }}`, "key = 123"},
  114. {"template string", `key = {{ "xyz" }}`, "key = xyz"},
  115. {"template quote", `key = {{ printf "%q" "with space" }}`, `key = "with space"`},
  116. }
  117. for _, test := range tests {
  118. t.Run(test.name, func(t *testing.T) {
  119. require := require.New(t)
  120. got, err := RenderWithTemplate([]byte(test.content), nil)
  121. require.NoError(err)
  122. require.EqualValues(test.want, string(got))
  123. })
  124. }
  125. }
  126. func TestCustomStructStrictMode(t *testing.T) {
  127. require := require.New(t)
  128. proxyStr := `
  129. serverPort = 7000
  130. [[proxies]]
  131. name = "test"
  132. type = "tcp"
  133. remotePort = 6000
  134. `
  135. clientCfg := v1.ClientConfig{}
  136. err := LoadConfigure([]byte(proxyStr), &clientCfg, true)
  137. require.NoError(err)
  138. proxyStr += `unknown = "unknown"`
  139. err = LoadConfigure([]byte(proxyStr), &clientCfg, true)
  140. require.Error(err)
  141. visitorStr := `
  142. serverPort = 7000
  143. [[visitors]]
  144. name = "test"
  145. type = "stcp"
  146. bindPort = 6000
  147. serverName = "server"
  148. `
  149. err = LoadConfigure([]byte(visitorStr), &clientCfg, true)
  150. require.NoError(err)
  151. visitorStr += `unknown = "unknown"`
  152. err = LoadConfigure([]byte(visitorStr), &clientCfg, true)
  153. require.Error(err)
  154. pluginStr := `
  155. serverPort = 7000
  156. [[proxies]]
  157. name = "test"
  158. type = "tcp"
  159. remotePort = 6000
  160. [proxies.plugin]
  161. type = "unix_domain_socket"
  162. unixPath = "/tmp/uds.sock"
  163. `
  164. err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
  165. require.NoError(err)
  166. pluginStr += `unknown = "unknown"`
  167. err = LoadConfigure([]byte(pluginStr), &clientCfg, true)
  168. require.Error(err)
  169. }
  170. // TestYAMLMergeInStrictMode tests that YAML merge functionality works
  171. // even in strict mode by properly handling dot-prefixed fields
  172. func TestYAMLMergeInStrictMode(t *testing.T) {
  173. require := require.New(t)
  174. yamlContent := `
  175. serverAddr: "127.0.0.1"
  176. serverPort: 7000
  177. .common: &common
  178. type: stcp
  179. secretKey: "test-secret"
  180. localIP: 127.0.0.1
  181. transport:
  182. useEncryption: true
  183. useCompression: true
  184. proxies:
  185. - name: ssh
  186. localPort: 22
  187. <<: *common
  188. - name: web
  189. localPort: 80
  190. <<: *common
  191. `
  192. clientCfg := v1.ClientConfig{}
  193. // This should work in strict mode
  194. err := LoadConfigure([]byte(yamlContent), &clientCfg, true)
  195. require.NoError(err)
  196. // Verify the merge worked correctly
  197. require.Equal("127.0.0.1", clientCfg.ServerAddr)
  198. require.Equal(7000, clientCfg.ServerPort)
  199. require.Len(clientCfg.Proxies, 2)
  200. // Check first proxy
  201. sshProxy := clientCfg.Proxies[0].ProxyConfigurer
  202. require.Equal("ssh", sshProxy.GetBaseConfig().Name)
  203. require.Equal("stcp", sshProxy.GetBaseConfig().Type)
  204. // Check second proxy
  205. webProxy := clientCfg.Proxies[1].ProxyConfigurer
  206. require.Equal("web", webProxy.GetBaseConfig().Name)
  207. require.Equal("stcp", webProxy.GetBaseConfig().Type)
  208. }
  209. // TestOptimizedYAMLProcessing tests the optimization logic for YAML processing
  210. func TestOptimizedYAMLProcessing(t *testing.T) {
  211. require := require.New(t)
  212. yamlWithDotFields := []byte(`
  213. serverAddr: "127.0.0.1"
  214. .common: &common
  215. type: stcp
  216. proxies:
  217. - name: test
  218. <<: *common
  219. `)
  220. yamlWithoutDotFields := []byte(`
  221. serverAddr: "127.0.0.1"
  222. proxies:
  223. - name: test
  224. type: tcp
  225. localPort: 22
  226. `)
  227. // Test that YAML without dot fields works in strict mode
  228. clientCfg := v1.ClientConfig{}
  229. err := LoadConfigure(yamlWithoutDotFields, &clientCfg, true)
  230. require.NoError(err)
  231. require.Equal("127.0.0.1", clientCfg.ServerAddr)
  232. require.Len(clientCfg.Proxies, 1)
  233. require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
  234. // Test that YAML with dot fields still works in strict mode
  235. err = LoadConfigure(yamlWithDotFields, &clientCfg, true)
  236. require.NoError(err)
  237. require.Equal("127.0.0.1", clientCfg.ServerAddr)
  238. require.Len(clientCfg.Proxies, 1)
  239. require.Equal("test", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Name)
  240. require.Equal("stcp", clientCfg.Proxies[0].ProxyConfigurer.GetBaseConfig().Type)
  241. }
  242. // TestYAMLEdgeCases tests edge cases for YAML parsing, including non-map types
  243. func TestYAMLEdgeCases(t *testing.T) {
  244. require := require.New(t)
  245. // Test array at root (should fail for frp config)
  246. arrayYAML := []byte(`
  247. - item1
  248. - item2
  249. `)
  250. clientCfg := v1.ClientConfig{}
  251. err := LoadConfigure(arrayYAML, &clientCfg, true)
  252. require.Error(err) // Should fail because ClientConfig expects an object
  253. // Test scalar at root (should fail for frp config)
  254. scalarYAML := []byte(`"just a string"`)
  255. err = LoadConfigure(scalarYAML, &clientCfg, true)
  256. require.Error(err) // Should fail because ClientConfig expects an object
  257. // Test empty object (should work)
  258. emptyYAML := []byte(`{}`)
  259. err = LoadConfigure(emptyYAML, &clientCfg, true)
  260. require.NoError(err)
  261. // Test nested structure without dots (should work)
  262. nestedYAML := []byte(`
  263. serverAddr: "127.0.0.1"
  264. serverPort: 7000
  265. `)
  266. err = LoadConfigure(nestedYAML, &clientCfg, true)
  267. require.NoError(err)
  268. require.Equal("127.0.0.1", clientCfg.ServerAddr)
  269. require.Equal(7000, clientCfg.ServerPort)
  270. }