router_test.go 7.8 KB

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