1
0

router_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package server
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "testing"
  6. "github.com/stretchr/testify/suite"
  7. "github.com/imgproxy/imgproxy/v3/errorreport"
  8. "github.com/imgproxy/imgproxy/v3/httpheaders"
  9. "github.com/imgproxy/imgproxy/v3/monitoring"
  10. )
  11. type RouterTestSuite struct {
  12. suite.Suite
  13. router *Router
  14. }
  15. func (s *RouterTestSuite) SetupTest() {
  16. c := NewDefaultConfig()
  17. mc := monitoring.NewDefaultConfig()
  18. m, err := monitoring.New(s.T().Context(), &mc, 1)
  19. s.Require().NoError(err)
  20. erCfg := errorreport.NewDefaultConfig()
  21. er, err := errorreport.New(&erCfg)
  22. s.Require().NoError(err)
  23. c.PathPrefix = "/api"
  24. r, err := NewRouter(&c, m, er)
  25. s.Require().NoError(err)
  26. s.router = r
  27. }
  28. // TestHTTPMethods tests route methods registration and HTTP requests
  29. func (s *RouterTestSuite) TestHTTPMethods() {
  30. var capturedMethod string
  31. var capturedPath string
  32. getHandler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  33. capturedMethod = req.Method
  34. capturedPath = req.URL.Path
  35. rw.WriteHeader(200)
  36. rw.Write([]byte("GET response"))
  37. return nil
  38. }
  39. optionsHandler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  40. capturedMethod = req.Method
  41. capturedPath = req.URL.Path
  42. rw.WriteHeader(200)
  43. rw.Write([]byte("OPTIONS response"))
  44. return nil
  45. }
  46. headHandler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  47. capturedMethod = req.Method
  48. capturedPath = req.URL.Path
  49. rw.WriteHeader(200)
  50. return nil
  51. }
  52. // Register routes with different configurations
  53. s.router.GET("/get-test", getHandler) // exact match
  54. s.router.OPTIONS("/options-test*", optionsHandler) // prefix match
  55. s.router.HEAD("/head-test", headHandler) // exact match
  56. tests := []struct {
  57. name string
  58. requestMethod string
  59. requestPath string
  60. expectedBody string
  61. expectedPath string
  62. }{
  63. {
  64. name: "GET",
  65. requestMethod: http.MethodGet,
  66. requestPath: "/api/get-test",
  67. expectedBody: "GET response",
  68. expectedPath: "/api/get-test",
  69. },
  70. {
  71. name: "OPTIONS",
  72. requestMethod: http.MethodOptions,
  73. requestPath: "/api/options-test",
  74. expectedBody: "OPTIONS response",
  75. expectedPath: "/api/options-test",
  76. },
  77. {
  78. name: "OPTIONSPrefixed",
  79. requestMethod: http.MethodOptions,
  80. requestPath: "/api/options-test/sub",
  81. expectedBody: "OPTIONS response",
  82. expectedPath: "/api/options-test/sub",
  83. },
  84. {
  85. name: "HEAD",
  86. requestMethod: http.MethodHead,
  87. requestPath: "/api/head-test",
  88. expectedBody: "",
  89. expectedPath: "/api/head-test",
  90. },
  91. }
  92. for _, tt := range tests {
  93. s.Run(tt.name, func() {
  94. req := httptest.NewRequest(tt.requestMethod, tt.requestPath, nil)
  95. rw := httptest.NewRecorder()
  96. s.router.ServeHTTP(rw, req)
  97. s.Require().Equal(tt.expectedBody, rw.Body.String())
  98. s.Require().Equal(tt.requestMethod, capturedMethod)
  99. s.Require().Equal(tt.expectedPath, capturedPath)
  100. })
  101. }
  102. }
  103. // TestMiddlewareOrder checks middleware ordering and functionality
  104. func (s *RouterTestSuite) TestMiddlewareOrder() {
  105. var order []string
  106. middleware1 := func(next RouteHandler) RouteHandler {
  107. return func(reqID string, rw ResponseWriter, req *http.Request) error {
  108. order = append(order, "middleware1")
  109. return next(reqID, rw, req)
  110. }
  111. }
  112. middleware2 := func(next RouteHandler) RouteHandler {
  113. return func(reqID string, rw ResponseWriter, req *http.Request) error {
  114. order = append(order, "middleware2")
  115. return next(reqID, rw, req)
  116. }
  117. }
  118. handler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  119. order = append(order, "handler")
  120. rw.WriteHeader(200)
  121. return nil
  122. }
  123. s.router.GET("/test", handler, middleware2, middleware1)
  124. req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
  125. rw := httptest.NewRecorder()
  126. s.router.ServeHTTP(rw, req)
  127. // Middleware should execute in the order they are passed (first added first)
  128. s.Require().Equal([]string{"middleware1", "middleware2", "handler"}, order)
  129. }
  130. // TestServeHTTP tests ServeHTTP method
  131. func (s *RouterTestSuite) TestServeHTTP() {
  132. handler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  133. rw.Header().Set("Custom-Header", "test-value")
  134. rw.WriteHeader(200)
  135. rw.Write([]byte("success"))
  136. return nil
  137. }
  138. s.router.GET("/test", handler)
  139. req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
  140. rw := httptest.NewRecorder()
  141. s.router.ServeHTTP(rw, req)
  142. s.Require().Equal(200, rw.Code)
  143. s.Require().Equal("success", rw.Body.String())
  144. s.Require().Equal("test-value", rw.Header().Get("Custom-Header"))
  145. s.Require().Equal(defaultServerName, rw.Header().Get(httpheaders.Server))
  146. s.Require().NotEmpty(rw.Header().Get(httpheaders.XRequestID))
  147. }
  148. // TestRequestID checks request ID generation and validation
  149. func (s *RouterTestSuite) TestRequestID() {
  150. handler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  151. rw.WriteHeader(200)
  152. return nil
  153. }
  154. s.router.GET("/test", handler)
  155. // Test request ID passthrough (if present)
  156. req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
  157. req.Header.Set(httpheaders.XRequestID, "valid-id-123")
  158. rw := httptest.NewRecorder()
  159. s.router.ServeHTTP(rw, req)
  160. s.Require().Equal("valid-id-123", rw.Header().Get(httpheaders.XRequestID))
  161. // Test invalid request ID (should generate a new one)
  162. req = httptest.NewRequest(http.MethodGet, "/api/test", nil)
  163. req.Header.Set(httpheaders.XRequestID, "invalid id with spaces!")
  164. rw = httptest.NewRecorder()
  165. s.router.ServeHTTP(rw, req)
  166. generatedID := rw.Header().Get(httpheaders.XRequestID)
  167. s.Require().NotEqual("invalid id with spaces!", generatedID)
  168. s.Require().NotEmpty(generatedID)
  169. // Test no request ID (should generate a new one)
  170. req = httptest.NewRequest(http.MethodGet, "/api/test", nil)
  171. rw = httptest.NewRecorder()
  172. s.router.ServeHTTP(rw, req)
  173. generatedID = rw.Header().Get(httpheaders.XRequestID)
  174. s.Require().NotEmpty(generatedID)
  175. s.Require().Regexp(`^[A-Za-z0-9_\-]+$`, generatedID)
  176. }
  177. // TestLambdaRequestIDExtraction checks AWS lambda request id extraction
  178. func (s *RouterTestSuite) TestLambdaRequestIDExtraction() {
  179. handler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  180. rw.WriteHeader(200)
  181. return nil
  182. }
  183. s.router.GET("/test", handler)
  184. // Test with valid Lambda context
  185. req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
  186. req.Header.Set(httpheaders.XAmznRequestContextHeader, `{"requestId":"lambda-req-123"}`)
  187. rw := httptest.NewRecorder()
  188. s.router.ServeHTTP(rw, req)
  189. s.Require().Equal("lambda-req-123", rw.Header().Get(httpheaders.XRequestID))
  190. }
  191. // Test IP address handling
  192. func (s *RouterTestSuite) TestReplaceIP() {
  193. var capturedRemoteAddr string
  194. handler := func(reqID string, rw ResponseWriter, req *http.Request) error {
  195. capturedRemoteAddr = req.RemoteAddr
  196. rw.WriteHeader(200)
  197. return nil
  198. }
  199. s.router.GET("/test", handler)
  200. tests := []struct {
  201. name string
  202. originalAddr string
  203. headers map[string]string
  204. expectedAddr string
  205. }{
  206. {
  207. name: "CFConnectingIP",
  208. originalAddr: "original:8080",
  209. headers: map[string]string{
  210. httpheaders.CFConnectingIP: "1.2.3.4",
  211. },
  212. expectedAddr: "1.2.3.4:8080",
  213. },
  214. {
  215. name: "XForwardedForMulti",
  216. originalAddr: "original:8080",
  217. headers: map[string]string{
  218. httpheaders.XForwardedFor: "5.6.7.8, 9.10.11.12",
  219. },
  220. expectedAddr: "5.6.7.8:8080",
  221. },
  222. {
  223. name: "XForwardedForSingle",
  224. originalAddr: "original:8080",
  225. headers: map[string]string{
  226. httpheaders.XForwardedFor: "13.14.15.16",
  227. },
  228. expectedAddr: "13.14.15.16:8080",
  229. },
  230. {
  231. name: "XRealIP",
  232. originalAddr: "original:8080",
  233. headers: map[string]string{
  234. httpheaders.XRealIP: "17.18.19.20",
  235. },
  236. expectedAddr: "17.18.19.20:8080",
  237. },
  238. }
  239. for _, tt := range tests {
  240. s.Run(tt.name, func() {
  241. req := httptest.NewRequest(http.MethodGet, "/api/test", nil)
  242. req.RemoteAddr = tt.originalAddr
  243. for header, value := range tt.headers {
  244. req.Header.Set(header, value)
  245. }
  246. rw := httptest.NewRecorder()
  247. s.router.ServeHTTP(rw, req)
  248. s.Require().Equal(tt.expectedAddr, capturedRemoteAddr)
  249. })
  250. }
  251. }
  252. // TestRouteOrder checks exact/non-exact insertion order
  253. func (s *RouterTestSuite) TestRouteOrder() {
  254. h := func(reqID string, rw ResponseWriter, req *http.Request) error {
  255. return nil
  256. }
  257. s.router.GET("/test*", h)
  258. s.router.GET("/test/path", h)
  259. s.router.GET("/test/path/nested", h)
  260. s.Require().Equal("/api/test/path", s.router.routes[0].path)
  261. s.Require().Equal("/api/test/path/nested", s.router.routes[1].path)
  262. s.Require().Equal("/api/test", s.router.routes[2].path)
  263. }
  264. func TestRouterSuite(t *testing.T) {
  265. suite.Run(t, new(RouterTestSuite))
  266. }